153 lines
6.1 KiB
React
153 lines
6.1 KiB
React
import { useTheme } from '../context/ThemeContext.jsx';
|
|
import { useUi } from '../context/UiContext.jsx';
|
|
|
|
/* ── Theme options ─────────────────────────────────────────── */
|
|
const THEMES = [
|
|
{
|
|
mode: 'light',
|
|
label: 'Clair',
|
|
desc: 'Interface lumineuse',
|
|
preview: (
|
|
<svg viewBox="0 0 56 36" width="56" height="36" aria-hidden="true">
|
|
<rect width="56" height="36" rx="5" fill="#f0f4ff"/>
|
|
<rect x="2" y="2" width="12" height="32" rx="3" fill="#1e3a8a"/>
|
|
<rect x="16" y="2" width="38" height="8" rx="2" fill="#ffffff" opacity=".9"/>
|
|
<rect x="16" y="12" width="38" height="5" rx="2" fill="#c7d2e8"/>
|
|
<rect x="16" y="19" width="28" height="5" rx="2" fill="#c7d2e8"/>
|
|
<rect x="16" y="26" width="20" height="5" rx="2" fill="#c7d2e8"/>
|
|
</svg>
|
|
),
|
|
},
|
|
{
|
|
mode: 'dark',
|
|
label: 'Sombre',
|
|
desc: 'Interface nocturne',
|
|
preview: (
|
|
<svg viewBox="0 0 56 36" width="56" height="36" aria-hidden="true">
|
|
<rect width="56" height="36" rx="5" fill="#060e1f"/>
|
|
<rect x="2" y="2" width="12" height="32" rx="3" fill="#0d1629"/>
|
|
<rect x="16" y="2" width="38" height="8" rx="2" fill="#111c35" opacity=".9"/>
|
|
<rect x="16" y="12" width="38" height="5" rx="2" fill="#1e3a6a"/>
|
|
<rect x="16" y="19" width="28" height="5" rx="2" fill="#1e3a6a"/>
|
|
<rect x="16" y="26" width="20" height="5" rx="2" fill="#1e3a6a"/>
|
|
</svg>
|
|
),
|
|
},
|
|
{
|
|
mode: 'system',
|
|
label: 'Système',
|
|
desc: 'Suit les préférences OS',
|
|
preview: (
|
|
<svg viewBox="0 0 56 36" width="56" height="36" aria-hidden="true">
|
|
<defs>
|
|
<linearGradient id="split" x1="0" x2="1" y1="0" y2="0">
|
|
<stop offset="50%" stopColor="#f0f4ff"/>
|
|
<stop offset="50%" stopColor="#060e1f"/>
|
|
</linearGradient>
|
|
</defs>
|
|
<rect width="56" height="36" rx="5" fill="url(#split)"/>
|
|
<rect x="2" y="2" width="12" height="32" rx="3" fill="#1e3a8a"/>
|
|
<rect x="16" y="2" width="18" height="8" rx="2" fill="#ffffff" opacity=".9"/>
|
|
<rect x="36" y="2" width="18" height="8" rx="2" fill="#111c35" opacity=".9"/>
|
|
<rect x="16" y="12" width="18" height="5" rx="2" fill="#c7d2e8"/>
|
|
<rect x="36" y="12" width="18" height="5" rx="2" fill="#1e3a6a"/>
|
|
</svg>
|
|
),
|
|
},
|
|
];
|
|
|
|
/* ── Font scale options ────────────────────────────────────── */
|
|
const FONTS = [
|
|
{
|
|
scale: 'compact',
|
|
label: 'Normal',
|
|
desc: 'Interface compacte',
|
|
sizes: { body: 12, table: 11 },
|
|
},
|
|
{
|
|
scale: 'medium',
|
|
label: 'Moyen',
|
|
desc: 'Taille intermédiaire',
|
|
sizes: { body: 13, table: 12 },
|
|
},
|
|
{
|
|
scale: 'large',
|
|
label: 'Grand',
|
|
desc: 'Meilleure lisibilité',
|
|
sizes: { body: 14, table: 13 },
|
|
},
|
|
];
|
|
|
|
/* ── Component ─────────────────────────────────────────────── */
|
|
export default function Preferences() {
|
|
const { mode, setMode } = useTheme();
|
|
const { fontScale, setFontScale } = useUi();
|
|
|
|
return (
|
|
<>
|
|
<div className="topbar">
|
|
<h2>Interface</h2>
|
|
</div>
|
|
|
|
{/* ── Thème ──────────────────────────────────────────── */}
|
|
<div className="card">
|
|
<h3 style={{ margin: '0 0 4px' }}>Apparence</h3>
|
|
<p style={{ margin: '0 0 16px', color: 'var(--text-muted)', fontSize: 'var(--fs-sm)' }}>
|
|
Choisissez le thème visuel de l'application.
|
|
</p>
|
|
<div className="pref-options">
|
|
{THEMES.map((t) => (
|
|
<button
|
|
key={t.mode}
|
|
type="button"
|
|
className={`pref-option${mode === t.mode ? ' active' : ''}`}
|
|
onClick={() => setMode(t.mode)}
|
|
aria-pressed={mode === t.mode}
|
|
>
|
|
{t.preview}
|
|
<span style={{ fontWeight: 700, fontSize: 'var(--fs-sm)', marginTop: 4 }}>{t.label}</span>
|
|
<span style={{ fontSize: 'var(--fs-xs)', color: 'var(--text-muted)', fontWeight: 400 }}>{t.desc}</span>
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* ── Police ─────────────────────────────────────────── */}
|
|
<div className="card">
|
|
<h3 style={{ margin: '0 0 4px' }}>Taille du texte</h3>
|
|
<p style={{ margin: '0 0 16px', color: 'var(--text-muted)', fontSize: 'var(--fs-sm)' }}>
|
|
Le niveau <strong>Grand</strong> est recommandé pour les personnes malvoyantes.
|
|
Les niveaux inférieurs permettent d'afficher plus de données à l'écran.
|
|
</p>
|
|
<div className="pref-options">
|
|
{FONTS.map((f) => (
|
|
<button
|
|
key={f.scale}
|
|
type="button"
|
|
className={`pref-option${fontScale === f.scale ? ' active' : ''}`}
|
|
onClick={() => setFontScale(f.scale)}
|
|
aria-pressed={fontScale === f.scale}
|
|
>
|
|
{/* Live-size text preview */}
|
|
<div style={{
|
|
display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 4,
|
|
width: '100%', padding: '8px 0 4px',
|
|
borderBottom: '1px solid var(--border)', marginBottom: 4,
|
|
}}>
|
|
<span className="font-preview" style={{ fontSize: f.sizes.body }}>
|
|
Aa — {f.label}
|
|
</span>
|
|
<span style={{ fontSize: f.sizes.table, color: 'var(--text-muted)' }}>
|
|
Tableau {f.sizes.table}px · Corps {f.sizes.body}px
|
|
</span>
|
|
</div>
|
|
<span style={{ fontWeight: 700, fontSize: 'var(--fs-sm)', marginTop: 2 }}>{f.label}</span>
|
|
<span style={{ fontSize: 'var(--fs-xs)', color: 'var(--text-muted)', fontWeight: 400 }}>{f.desc}</span>
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|