76 lines
1.9 KiB
TypeScript
76 lines
1.9 KiB
TypeScript
"use client";
|
|
|
|
import type { ReactNode } from "react";
|
|
import { createContext, useContext, useEffect, useState } from "react";
|
|
import { ThemeMode, themeVariables } from "../theme";
|
|
|
|
type ThemeContextValue = {
|
|
theme: ThemeMode;
|
|
setTheme: (theme: ThemeMode) => void;
|
|
toggle: () => void;
|
|
};
|
|
|
|
const ThemeContext = createContext<ThemeContextValue | undefined>(undefined);
|
|
|
|
function applyTheme(theme: ThemeMode) {
|
|
if (typeof document === "undefined") return;
|
|
const root = document.documentElement;
|
|
const variables = themeVariables[theme];
|
|
Object.entries(variables).forEach(([key, value]) => {
|
|
root.style.setProperty(key, String(value));
|
|
});
|
|
root.dataset.theme = theme;
|
|
root.style.colorScheme = theme;
|
|
}
|
|
|
|
function detectTheme(): ThemeMode {
|
|
if (typeof window === "undefined") return "dark";
|
|
const stored = window.localStorage.getItem("allai-theme");
|
|
if (stored === "light" || stored === "dark") {
|
|
return stored;
|
|
}
|
|
return window.matchMedia("(prefers-color-scheme: light)").matches ? "light" : "dark";
|
|
}
|
|
|
|
export function ThemeProvider({ initialTheme = "dark", children }: { initialTheme?: ThemeMode; children: ReactNode }) {
|
|
const [theme, setThemeState] = useState<ThemeMode>(initialTheme);
|
|
|
|
useEffect(() => {
|
|
const next = detectTheme();
|
|
setThemeState(next);
|
|
applyTheme(next);
|
|
}, []);
|
|
|
|
function setTheme(next: ThemeMode) {
|
|
setThemeState(next);
|
|
if (typeof window !== "undefined") {
|
|
window.localStorage.setItem("allai-theme", next);
|
|
applyTheme(next);
|
|
}
|
|
}
|
|
|
|
function toggle() {
|
|
setTheme(theme === "light" ? "dark" : "light");
|
|
}
|
|
|
|
return (
|
|
<ThemeContext.Provider
|
|
value={{
|
|
theme,
|
|
setTheme,
|
|
toggle
|
|
}}
|
|
>
|
|
{children}
|
|
</ThemeContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useTheme() {
|
|
const context = useContext(ThemeContext);
|
|
if (!context) {
|
|
throw new Error("useTheme must be used within a ThemeProvider");
|
|
}
|
|
return context;
|
|
}
|