/** * Valeurs persistées pour User.theme / localStorage `theme-preference`. * Une seule source de vérité pour le DOM : évite les écarts entre header, settings et hydratation. */ const THEME_IDS = [ 'light', 'dark', 'auto', 'sepia', 'midnight', 'rose', 'green', 'lavender', 'sand', 'ocean', 'sunset', 'blue', ] as const export type ThemeId = (typeof THEME_IDS)[number] const NAMED_PALETTE_IDS: ThemeId[] = [ 'sepia', 'midnight', 'rose', 'green', 'lavender', 'sand', 'ocean', 'sunset', 'blue', ] export function isThemeId(value: string | null | undefined): value is ThemeId { return value !== undefined && value !== null && (THEME_IDS as readonly string[]).includes(value) } /** Corrige les anciennes valeurs (ex. formulaire « slate ») vers un thème supporté. */ export function normalizeThemeId(raw: string | null | undefined): ThemeId { if (!raw) return 'light' if (raw === 'slate') return 'light' if (isThemeId(raw)) return raw return 'light' } /** * Applique le thème sur `` : `class="dark"` et/ou `data-theme`. * - `light` → papier par défaut (`:root`), pas de `data-theme` * - `dark` → sombre global (`.dark`) * - `auto` → `.dark` si prefers-color-scheme: dark * - palettes nommées → `data-theme=""` ; `midnight` force aussi `.dark` (variante sombre du thème) */ export function applyDocumentTheme(theme: string): void { if (typeof document === 'undefined') return const t = normalizeThemeId(theme) const root = document.documentElement root.classList.remove('dark') root.removeAttribute('data-theme') if (t === 'auto') { if (window.matchMedia('(prefers-color-scheme: dark)').matches) { root.classList.add('dark') } return } if (t === 'dark') { root.classList.add('dark') return } if (t === 'light') { return } if ((NAMED_PALETTE_IDS as readonly string[]).includes(t)) { root.setAttribute('data-theme', t) if (t === 'midnight') { root.classList.add('dark') } } }