"use client"; import * as React from "react"; export type Theme = "dark" | "light" | "system"; export type ResolvedTheme = Exclude; const DEFAULT_STORAGE_KEY = "jason-ui-theme"; const mediaQuery = "(prefers-color-scheme: light)"; function systemTheme(): ResolvedTheme { return typeof window !== "undefined" && window.matchMedia(mediaQuery).matches ? "light" : "dark"; } function resolveTheme(theme: Theme): ResolvedTheme { return theme === "system" ? systemTheme() : theme; } /** Apply a theme immediately. For persisted React state, use ThemeProvider. */ export function setTheme(theme: Theme): ResolvedTheme { const resolved = resolveTheme(theme); if (typeof document === "undefined") return resolved; const root = document.documentElement; root.classList.toggle("light", resolved === "light"); root.classList.toggle("dark", resolved === "dark"); root.style.colorScheme = resolved; return resolved; } /** Toggle the currently rendered mode. */ export function toggleTheme(): ResolvedTheme { const current = typeof document !== "undefined" && document.documentElement.classList.contains("light"); return setTheme(current ? "dark" : "light"); } export interface ThemeContextValue { theme: Theme; resolvedTheme: ResolvedTheme; setTheme: (theme: Theme) => void; toggleTheme: () => void; } const ThemeContext = React.createContext(null); export function useOptionalTheme() { return React.useContext(ThemeContext); } export function ThemeProvider({ children, defaultTheme = "system", storageKey = DEFAULT_STORAGE_KEY, }: { children: React.ReactNode; defaultTheme?: Theme; storageKey?: string; }) { const [theme, updateTheme] = React.useState(defaultTheme); const [resolvedTheme, updateResolvedTheme] = React.useState(() => resolveTheme(defaultTheme)); React.useEffect(() => { const stored = window.localStorage.getItem(storageKey); if (stored === "dark" || stored === "light" || stored === "system") updateTheme(stored); }, [storageKey]); React.useEffect(() => { const query = window.matchMedia(mediaQuery); const apply = () => updateResolvedTheme(setTheme(theme)); apply(); if (theme !== "system") return; query.addEventListener("change", apply); return () => query.removeEventListener("change", apply); }, [theme]); const changeTheme = React.useCallback((next: Theme) => { window.localStorage.setItem(storageKey, next); updateTheme(next); }, [storageKey]); const toggle = React.useCallback(() => { changeTheme(resolvedTheme === "light" ? "dark" : "light"); }, [changeTheme, resolvedTheme]); const value = React.useMemo( () => ({ theme, resolvedTheme, setTheme: changeTheme, toggleTheme: toggle }), [changeTheme, resolvedTheme, theme, toggle] ); return {children}; } export function useTheme(): ThemeContextValue { const context = useOptionalTheme(); if (!context) throw new Error("useTheme must be used inside ThemeProvider"); return context; } /** Inline this before app CSS to avoid a light/dark flash during hydration. */ export function ThemeScript({ defaultTheme = "system", storageKey = DEFAULT_STORAGE_KEY, }: { defaultTheme?: Theme; storageKey?: string; }) { const script = `(()=>{try{const k=${JSON.stringify(storageKey)},d=${JSON.stringify(defaultTheme)},s=localStorage.getItem(k)||d,m=s==="system"?(matchMedia(${JSON.stringify(mediaQuery)}).matches?"light":"dark"):s,e=document.documentElement;e.classList.remove("light","dark");e.classList.add(m);e.style.colorScheme=m}catch{}})();`; return