"use client"; import * as React from "react"; import { PortalLayerContainerProvider, TooltipProvider, } from "@repo/ui/primitives"; export const TOOLCRAFT_THEME_PREFERENCE_STORAGE_KEY = "appearance.theme.v1" as const; export const TOOLCRAFT_DEFAULT_THEME_PREFERENCE = "dark" as const; export type ToolcraftThemePreference = "dark" | "light" | "system"; export type ToolcraftResolvedTheme = "dark" | "light"; export type ToolcraftThemeContextValue = { initialized: boolean; resolvedTheme: ToolcraftResolvedTheme; setThemePreference: (themePreference: ToolcraftThemePreference) => void; themePreference: ToolcraftThemePreference; toggleResolvedTheme: () => void; }; const colorSchemeMediaQuery = "(prefers-color-scheme: dark)"; const ToolcraftThemeContext = React.createContext(null); function isToolcraftThemePreference( value: unknown, ): value is ToolcraftThemePreference { return value === "dark" || value === "light" || value === "system"; } function getSystemResolvedTheme(): ToolcraftResolvedTheme { if (typeof window === "undefined" || typeof window.matchMedia !== "function") { return TOOLCRAFT_DEFAULT_THEME_PREFERENCE; } return window.matchMedia(colorSchemeMediaQuery).matches ? "dark" : "light"; } function resolveThemePreference( themePreference: ToolcraftThemePreference, ): ToolcraftResolvedTheme { return themePreference === "system" ? getSystemResolvedTheme() : themePreference; } function readStoredThemePreference(): ToolcraftThemePreference | null { if (typeof window === "undefined") { return null; } try { const rawValue = window.localStorage.getItem(TOOLCRAFT_THEME_PREFERENCE_STORAGE_KEY); return isToolcraftThemePreference(rawValue) ? rawValue : null; } catch { return null; } } function writeStoredThemePreference(themePreference: ToolcraftThemePreference): void { if (typeof window === "undefined") { return; } try { window.localStorage.setItem(TOOLCRAFT_THEME_PREFERENCE_STORAGE_KEY, themePreference); } catch { // Keep the active theme in memory if storage is unavailable. } } function getInitialThemePreference(): ToolcraftThemePreference { return readStoredThemePreference() ?? TOOLCRAFT_DEFAULT_THEME_PREFERENCE; } export function ToolcraftThemeProvider({ children, }: { children: React.ReactNode; }): React.JSX.Element { const [initialized, setInitialized] = React.useState(false); const [themePreference, setThemePreferenceState] = React.useState(getInitialThemePreference); const [resolvedTheme, setResolvedTheme] = React.useState(() => resolveThemePreference(getInitialThemePreference()), ); const setThemePreference = React.useCallback( (nextThemePreference: ToolcraftThemePreference): void => { const nextResolvedTheme = resolveThemePreference(nextThemePreference); writeStoredThemePreference(nextThemePreference); setThemePreferenceState(nextThemePreference); setResolvedTheme(nextResolvedTheme); setInitialized(true); }, [], ); const toggleResolvedTheme = React.useCallback((): void => { setThemePreference(resolvedTheme === "dark" ? "light" : "dark"); }, [resolvedTheme, setThemePreference]); React.useEffect(() => { setThemePreference(themePreference); }, [setThemePreference, themePreference]); React.useEffect(() => { if (typeof window === "undefined" || typeof window.matchMedia !== "function") { return undefined; } const mediaQueryList = window.matchMedia(colorSchemeMediaQuery); const handleChange = (): void => { if (themePreference !== "system") { return; } const nextResolvedTheme = getSystemResolvedTheme(); setResolvedTheme(nextResolvedTheme); }; mediaQueryList.addEventListener("change", handleChange); return () => { mediaQueryList.removeEventListener("change", handleChange); }; }, [themePreference]); const value = React.useMemo( () => ({ initialized, resolvedTheme, setThemePreference, themePreference, toggleResolvedTheme, }), [initialized, resolvedTheme, setThemePreference, themePreference, toggleResolvedTheme], ); const portalRootRef = React.useRef(null); return (
{children} ); } export function useToolcraftTheme(): ToolcraftThemeContextValue { const context = React.useContext(ToolcraftThemeContext); if (!context) { throw new Error("useToolcraftTheme must be used within ToolcraftThemeProvider"); } return context; }