Commit 06720e6d by PLN (Algolia)

theme: pin the resolved theme before paint; pill label waits for mount

parent 72e60913
......@@ -29,11 +29,14 @@ export const metadata = {
/**
* Resolves the theme before first paint: stored choice wins, else system.
* Keeps the HTML attribute semantics of styles/tokens.css — "dark" set
* explicitly, "light" set explicitly (to opt out of the media query),
* nothing when the reader has no JS (the media query covers them).
* Always pins the resolved value — including "light", which must be set
* explicitly to opt out of the prefers-color-scheme fallback in
* styles/tokens.css (leaving it unpinned paints dark until hydration on
* dark-system machines whose reader chose light). Nothing is set when the
* reader has no JS… except there is no "when": this script only runs with
* JS. The no-JS story is the media query alone, untouched.
*/
const themeBootstrap = `(function(){try{var t=localStorage.getItem('pln:theme');if(t!=='light'&&(t==='dark'||window.matchMedia('(prefers-color-scheme: dark)').matches)){document.documentElement.dataset.theme='dark'}}catch(e){}})()`;
const themeBootstrap = `(function(){try{var t=localStorage.getItem('pln:theme')||'auto';document.documentElement.dataset.theme=t==='auto'?(window.matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light'):t}catch(e){}})()`;
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
......
......@@ -20,14 +20,17 @@ const LABELS = {
* this component only manages the cycle and the persistence.
*/
export default function ThemeSwitcher() {
const [mode, setMode] = useState('auto');
// Null until mounted: SSR would paint the label "auto" even for a reader
// whose stored mode is light/dark — the glimpse this pill used to flash.
// An empty pill for a frame beats a wrong label.
const [mode, setMode] = useState(null);
useEffect(() => {
try {
const stored = window.localStorage.getItem(KEY);
if (MODES.includes(stored)) setMode(stored);
setMode(MODES.includes(stored) ? stored : 'auto');
} catch {
// storage refused — the pill still cycles, unaided
setMode('auto'); // storage refused — the pill still cycles, unaided
}
// Follow the system while in auto.
const media = window.matchMedia('(prefers-color-scheme: dark)');
......@@ -68,13 +71,17 @@ export default function ThemeSwitcher() {
type="button"
onClick={cycle}
className={styles.pill}
aria-label={LABELS[mode]}
title={LABELS[mode]}
aria-label={LABELS[mode] ?? 'Thème'}
title={LABELS[mode] ?? 'Thème'}
>
<span aria-hidden="true" className={styles.icon}>
{ICONS[mode]}
</span>
<span className={styles.mode}>{mode}</span>
{mode && (
<>
<span aria-hidden="true" className={styles.icon}>
{ICONS[mode]}
</span>
<span className={styles.mode}>{mode}</span>
</>
)}
</button>
);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment