import { type ReactNode, createContext, useCallback, useContext, useEffect, useState, } from "react"; type Theme = "light" | "dark" | "system"; type ThemeContextValue = { theme: Theme; isDark: boolean; setTheme: (theme: Theme) => void; }; const ThemeContext = createContext(undefined); function getSystemDark(): boolean { return window.matchMedia("(prefers-color-scheme: dark)").matches; } function getInitialTheme(): Theme { if (!import.meta.env.DEV) return "system"; const previewTheme = new URLSearchParams(window.location.search).get( "previewTheme", ); return previewTheme === "light" || previewTheme === "dark" ? previewTheme : "system"; } function applyClass(isDark: boolean) { const root = document.documentElement; root.classList.remove("light", "dark"); root.classList.add(isDark ? "dark" : "light"); } export function ThemeProvider({ children }: { children: ReactNode }) { const [theme, setThemeState] = useState(getInitialTheme); const [isDark, setIsDark] = useState(() => { const initialTheme = getInitialTheme(); const dark = initialTheme === "system" ? getSystemDark() : initialTheme === "dark"; applyClass(dark); return dark; }); useEffect(() => { const dark = theme === "system" ? getSystemDark() : theme === "dark"; applyClass(dark); setIsDark(dark); }, [theme]); useEffect(() => { if (theme !== "system") return; const mq = window.matchMedia("(prefers-color-scheme: dark)"); const handler = (e: MediaQueryListEvent) => { applyClass(e.matches); setIsDark(e.matches); }; mq.addEventListener("change", handler); return () => mq.removeEventListener("change", handler); }, [theme]); const setTheme = useCallback((t: Theme) => { setThemeState(t); }, []); return ( {children} ); } export function useTheme() { const context = useContext(ThemeContext); if (!context) { throw new Error("useTheme must be used within a ThemeProvider"); } return context; }