import React, { createContext, useContext, useEffect, useState, type ReactNode, } from "react"; import { themes, type Theme, type ThemeName } from "./themes"; interface ThemeContextType { theme: Theme; themeName: ThemeName; setTheme: (name: ThemeName) => void; availableThemes: ThemeName[]; } const ThemeContext = createContext(undefined); interface ThemeProviderProps { children: ReactNode; defaultTheme?: ThemeName; } export const ThemeProvider: React.FC = ({ children, defaultTheme = "light", }) => { const [themeName, setThemeName] = useState(() => { const savedTheme = localStorage.getItem("theme") as ThemeName; if (savedTheme && themes[savedTheme]) { return savedTheme; } if ( window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches ) { return "dark"; } return defaultTheme; }); const theme = themes[themeName]; useEffect(() => { const root = document.documentElement; root.setAttribute("data-theme", themeName); // Set color variables Object.entries(theme.colors).forEach(([key, value]) => { const cssVarName = `--color-${key.replace(/([A-Z])/g, "-$1").toLowerCase()}`; root.style.setProperty(cssVarName, value); }); // Set typography variables Object.entries(theme.typography).forEach(([key, value]) => { const cssVarName = `--${key .replace(/([A-Z])/g, "-$1") .toLowerCase() .replace("font-", "font-") .replace("size", "") .replace("weight", "")}`; root.style.setProperty(cssVarName, value); }); // Set spacing variables Object.entries(theme.spacing).forEach(([key, value]) => { const cssVarName = `--${key.replace(/([A-Z])/g, "-$1").toLowerCase()}`; root.style.setProperty(cssVarName, value); }); // Set radius variables Object.entries(theme.radius).forEach(([key, value]) => { const cssVarName = `--${key.replace(/([A-Z])/g, "-$1").toLowerCase()}`; root.style.setProperty(cssVarName, value); }); // Set transition variables Object.entries(theme.transitions).forEach(([key, value]) => { const cssVarName = `--${key.replace(/([A-Z])/g, "-$1").toLowerCase()}`; root.style.setProperty(cssVarName, value); }); // Legacy variables for backward compatibility root.style.setProperty("--text-color", theme.colors.text); root.style.setProperty("--background-color", theme.colors.background); localStorage.setItem("theme", themeName); }, [themeName, theme]); useEffect(() => { const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); const handleChange = (e: MediaQueryListEvent) => { const savedTheme = localStorage.getItem("theme"); if (!savedTheme) { setThemeName(e.matches ? "dark" : "light"); } }; mediaQuery.addEventListener("change", handleChange); return () => mediaQuery.removeEventListener("change", handleChange); }, []); const setTheme = (name: ThemeName) => { if (themes[name]) { setThemeName(name); } }; const value: ThemeContextType = { theme, themeName, setTheme, availableThemes: Object.keys(themes) as ThemeName[], }; return ( {children} ); }; export const useTheme = (): ThemeContextType => { const context = useContext(ThemeContext); if (context === undefined) { throw new Error("useTheme must be used within a ThemeProvider"); } return context; };