"use client"; import { createContext, useCallback, useContext, useEffect, useState, } from "react"; import { Toaster } from "sonner"; import { DARK_QUERY, getStoredTheme, THEME_STORAGE_KEY, type ResolvedTheme, type Theme, } from "@/lib/theme"; interface ThemeContextValue { /** The user's selection: light, dark, or follow-system. */ theme: Theme; /** The actually-applied theme after resolving "system". */ resolvedTheme: ResolvedTheme; setTheme: (theme: Theme) => void; } const ThemeContext = createContext(null); function applyTheme(resolved: ResolvedTheme) { const root = document.documentElement; root.classList.toggle("dark", resolved === "dark"); root.style.colorScheme = resolved; } export function ThemeProvider({ children }: { children: React.ReactNode }) { // Lazy initializer reads the stored choice on the client's first render, so the // single effect below resolves the right theme on its first run — no // "system → stored" double-apply flash. On the server it returns "system" (no // localStorage); the inline pre-paint script in layout.tsx has already set the // class, so nothing flashes regardless. const [theme, setThemeState] = useState(getStoredTheme); const [resolvedTheme, setResolvedTheme] = useState("light"); useEffect(() => { const mql = window.matchMedia(DARK_QUERY); const apply = () => { const resolved: ResolvedTheme = theme === "system" ? (mql.matches ? "dark" : "light") : theme; setResolvedTheme((current) => current === resolved ? current : resolved ); applyTheme(resolved); }; apply(); // Track live OS changes only while following the system. if (theme !== "system") return; mql.addEventListener("change", apply); return () => mql.removeEventListener("change", apply); }, [theme]); const setTheme = useCallback((next: Theme) => { try { localStorage.setItem(THEME_STORAGE_KEY, next); } catch { // Private mode / storage disabled — fall back to in-memory only. } setThemeState((current) => (current === next ? current : next)); }, []); return ( {children} ); } export function useTheme(): ThemeContextValue { const ctx = useContext(ThemeContext); if (!ctx) { throw new Error("useTheme must be used within a ThemeProvider"); } return ctx; } /** Sonner toaster that follows the resolved theme (must render inside ThemeProvider). */ export function ThemedToaster() { const { resolvedTheme } = useTheme(); return ; }