/** * ThemeToggle — dark/light mode switch. * * A visitor's own choice is kept in localStorage and always wins. Without one, * the starting mode comes from `mode:` in settings.yaml, which the layout has * already rendered onto as data-default-theme — reading it from the DOM * rather than taking a prop keeps this component mountable anywhere without * threading config through the tree. `auto` follows the operating system. * * It used to hardcode dark, so an operator who preferred light had no way to * give visitors anything else (#512). * * Uses useSyncExternalStore to satisfy React strict mode lint rules. */ 'use client'; import { useSyncExternalStore, useCallback } from 'react'; import { useDictionary } from './I18nProvider'; type Theme = 'dark' | 'light'; /** Module-level theme state — shared across all instances. */ let currentTheme: Theme = 'dark'; const listeners = new Set<() => void>(); function subscribe(callback: () => void): () => void { listeners.add(callback); return () => listeners.delete(callback); } function getSnapshot(): Theme { return currentTheme; } function getServerSnapshot(): Theme { return 'dark'; // SSR default } /** What the OS asks for. Dark unless it says otherwise, as before. */ function systemTheme(): Theme { return typeof window !== 'undefined' && window.matchMedia?.('(prefers-color-scheme: light)').matches ? 'light' : 'dark'; } /** The configured starting mode, read off the the server rendered. */ function configuredTheme(): Theme { const mode = document.documentElement.getAttribute('data-default-theme'); if (mode === 'light' || mode === 'dark') return mode; // 'auto', or an older layout that renders no attribute at all. return systemTheme(); } function applyTheme(theme: Theme) { currentTheme = theme; if (typeof document !== 'undefined') { document.documentElement.setAttribute('data-theme', theme); localStorage.setItem('theme', theme); } listeners.forEach((cb) => cb()); } // Initialize from localStorage on module load (client-side only) if (typeof window !== 'undefined') { const stored = localStorage.getItem('theme'); currentTheme = stored === 'light' || stored === 'dark' ? stored : configuredTheme(); document.documentElement.setAttribute('data-theme', currentTheme); } export function ThemeToggle() { const theme = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot); const t = useDictionary(); const label = theme === 'dark' ? t.theme.switchToLight : t.theme.switchToDark; const toggle = useCallback(() => { applyTheme(theme === 'dark' ? 'light' : 'dark'); }, [theme]); return ( ); }