"use client"; import * as React from "react"; import { DesktopIcon, MoonIcon, SunIcon } from "@radix-ui/react-icons"; import * as z from "zod/v4"; import { Button } from "./button"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "./dropdown-menu"; const ThemeModeSchema = z.enum(["light", "dark", "auto"]); const themeKey = "theme-mode"; export type ThemeMode = z.output; export type ResolvedTheme = Exclude; const getStoredThemeMode = (): ThemeMode => { if (typeof window === "undefined") return "auto"; try { const storedTheme = localStorage.getItem(themeKey); return ThemeModeSchema.parse(storedTheme); } catch { return "auto"; } }; const setStoredThemeMode = (theme: ThemeMode) => { try { const parsedTheme = ThemeModeSchema.parse(theme); localStorage.setItem(themeKey, parsedTheme); } catch { // Silently fail if localStorage is unavailable } }; const getSystemTheme = () => { if (typeof window === "undefined") return "light"; return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"; }; const updateThemeClass = (themeMode: ThemeMode) => { const root = document.documentElement; root.classList.remove("light", "dark", "auto"); const newTheme = themeMode === "auto" ? getSystemTheme() : themeMode; root.classList.add(newTheme); if (themeMode === "auto") { root.classList.add("auto"); } }; const setupPreferredListener = () => { const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); const handler = () => updateThemeClass("auto"); mediaQuery.addEventListener("change", handler); return () => mediaQuery.removeEventListener("change", handler); }; const getNextTheme = (current: ThemeMode): ThemeMode => { const themes: ThemeMode[] = getSystemTheme() === "dark" ? ["auto", "light", "dark"] : ["auto", "dark", "light"]; // eslint-disable-next-line @typescript-eslint/no-non-null-assertion return themes[(themes.indexOf(current) + 1) % themes.length]!; }; export const themeDetectorScript = (function () { function themeFn() { const isValidTheme = (theme: string): theme is ThemeMode => { const validThemes = ["light", "dark", "auto"] as const; return validThemes.includes(theme as ThemeMode); }; const storedTheme = localStorage.getItem("theme-mode") ?? "auto"; const validTheme = isValidTheme(storedTheme) ? storedTheme : "auto"; if (validTheme === "auto") { const autoTheme = window.matchMedia("(prefers-color-scheme: dark)") .matches ? "dark" : "light"; document.documentElement.classList.add(autoTheme, "auto"); } else { document.documentElement.classList.add(validTheme); } } return `(${themeFn.toString()})();`; })(); interface ThemeContextProps { themeMode: ThemeMode; resolvedTheme: ResolvedTheme; setTheme: (theme: ThemeMode) => void; toggleMode: () => void; } const ThemeContext = React.createContext( undefined, ); export function ThemeProvider({ children }: React.PropsWithChildren) { const [themeMode, setThemeMode] = React.useState(getStoredThemeMode); React.useEffect(() => { if (themeMode !== "auto") return; return setupPreferredListener(); }, [themeMode]); const resolvedTheme = themeMode === "auto" ? getSystemTheme() : themeMode; const setTheme = (newTheme: ThemeMode) => { setThemeMode(newTheme); setStoredThemeMode(newTheme); updateThemeClass(newTheme); }; const toggleMode = () => { setTheme(getNextTheme(themeMode)); }; return (