import { useEffect, useState } from "react"; import { applyResolvedTheme, prefersDark, readThemePreference, resolveTheme, storeThemePreference, type ThemePreference, } from "../theme.ts"; const ORDER: ThemePreference[] = ["system", "light", "dark"]; const LABEL: Record = { system: "Theme: follows your system", light: "Theme: light", dark: "Theme: dark", }; /** * Cycles system -> light -> dark. Three states rather than a two-way switch * because "follow the system" is a real answer and the only way to get back to * it, once you have chosen, is to have somewhere to choose it. * * While the preference is "system" this keeps listening, so the app follows * the OS switching to dark at sunset without a reload. */ export function ThemeToggle() { const [preference, setPreference] = useState(readThemePreference); useEffect(() => { applyResolvedTheme(resolveTheme(preference, prefersDark())); if (preference !== "system") return; const media = window.matchMedia("(prefers-color-scheme: dark)"); const follow = () => applyResolvedTheme(resolveTheme("system", media.matches)); media.addEventListener("change", follow); return () => media.removeEventListener("change", follow); }, [preference]); function cycle() { const next = ORDER[(ORDER.indexOf(preference) + 1) % ORDER.length]!; setPreference(next); storeThemePreference(next); } const resolved = resolveTheme(preference, prefersDark()); return ( ); } /* Inline so the app ships no icon library for three glyphs. currentColor throughout, so they follow the theme without a second set of tokens. */ function SunGlyph() { return ( ); } function MoonGlyph() { return ( ); } function MonitorGlyph() { return ( ); }