import { createContext, useContext, useEffect, useLayoutEffect, useMemo, useState, type ReactNode, } from "react"; import type { ColorSchemePreference } from "../../application/ports/in/application-api.ts"; import { normalizeColorSchemePreference, resolveColorScheme, } from "../../application/policies/color-scheme.ts"; import { useApplication } from "./application-provider.tsx"; export type ThemeContextValue = Readonly<{ preference: ColorSchemePreference; resolvedTheme: "light" | "dark"; setPreference(preference: ColorSchemePreference): void; }>; const ThemeContext = createContext(null); function systemPrefersDark(): boolean { return ( typeof window.matchMedia === "function" && window.matchMedia("(prefers-color-scheme: dark)").matches ); } export function ThemeProvider({ children, }: Readonly<{ children: ReactNode }>) { const { preferences } = useApplication(); const [preference, updatePreference] = useState( preferences.getColorScheme, ); const [darkSystemTheme, setDarkSystemTheme] = useState(systemPrefersDark); const resolvedTheme = resolveColorScheme(preference, darkSystemTheme); useEffect(() => { if (typeof window.matchMedia !== "function") return undefined; const query = window.matchMedia("(prefers-color-scheme: dark)"); const update = (event: MediaQueryListEvent) => setDarkSystemTheme(event.matches); setDarkSystemTheme(query.matches); query.addEventListener("change", update); return () => query.removeEventListener("change", update); }, []); useLayoutEffect(() => { document.documentElement.dataset.theme = resolvedTheme; document.documentElement.dataset.themePreference = preference; document.documentElement.style.colorScheme = resolvedTheme; }, [preference, resolvedTheme]); const value = useMemo( () => Object.freeze({ preference, resolvedTheme, setPreference(next: ColorSchemePreference) { const normalized = normalizeColorSchemePreference(next); updatePreference(normalized); preferences.setColorScheme(normalized); }, }), [preference, preferences, resolvedTheme], ); return {children}; } export function useTheme(): ThemeContextValue { const theme = useContext(ThemeContext); if (!theme) throw new Error("ThemeProvider is required"); return theme; }