Files
Momento/memento-note/lib/apply-document-theme.ts
Antigravity a77f3ffb6e
Some checks failed
CI / Lint, Unit Tests & Build (push) Failing after 1m15s
CI / Deploy production (on server) (push) Has been skipped
fix: batch priorités — thème, scroll, wizard, agents, slides, charts, packs
Anti-flash dark (color-scheme + applyDocumentTheme), scroll pages publiques,
prompts wizard/slides moins génériques, cron agents rattrapage nextRun null,
slides carnet via notebookId, graphiques à 2 crédits + hint, audit dashboard,
packs Stripe marqués non configurés sans price ID.
2026-07-16 20:48:55 +00:00

111 lines
3.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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'
}
/** Cookie lu par le layout serveur pour éviter le flash clair au changement de route. */
export const THEME_COOKIE_NAME = 'memento-theme'
/**
* Applique le thème sur `<html>` : `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="<id>"` ; `midnight` force aussi `.dark` (variante sombre du thème)
*
* Important : on nenlève JAMAIS `dark` avant de le remettre (sinon flash clair
* dune frame à chaque navigation / re-render de ThemeInitializer).
*/
export function applyDocumentTheme(theme: string): void {
if (typeof document === 'undefined') return
const t = normalizeThemeId(theme)
const root = document.documentElement
const wantsDark =
t === 'dark' ||
t === 'midnight' ||
(t === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches)
// Classe dark : bascule atomique (pas de remove puis add)
if (wantsDark) {
root.classList.add('dark')
} else {
root.classList.remove('dark')
}
try {
root.style.colorScheme = wantsDark ? 'dark' : 'light'
} catch {
/* ignore */
}
// data-theme pour les palettes nommées uniquement
if (t === 'auto' || t === 'dark' || t === 'light') {
root.removeAttribute('data-theme')
} else if ((NAMED_PALETTE_IDS as readonly string[]).includes(t)) {
root.setAttribute('data-theme', t)
} else {
root.removeAttribute('data-theme')
}
root.dataset.appliedTheme = t
}
/** Persiste le thème (localStorage + cookie) pour que le serveur connaisse le dark. */
export function persistThemePreference(theme: string): void {
const t = normalizeThemeId(theme)
try {
localStorage.setItem('theme-preference', t)
} catch {
/* ignore */
}
try {
// 1 an — lu par le layout racine via cookies()
document.cookie = `${THEME_COOKIE_NAME}=${encodeURIComponent(t)}; path=/; max-age=31536000; SameSite=Lax`
} catch {
/* ignore */
}
applyDocumentTheme(t)
}