104 lines
2.3 KiB
TypeScript
104 lines
2.3 KiB
TypeScript
"use client";
|
|
|
|
import {
|
|
createContext,
|
|
useContext,
|
|
useState,
|
|
useEffect,
|
|
useCallback,
|
|
type ReactNode,
|
|
} from "react";
|
|
import { themes, getTheme, DEFAULT_THEME, type Theme } from "@/lib/themes";
|
|
|
|
interface ThemeContextValue {
|
|
theme: Theme;
|
|
themeName: string;
|
|
setTheme: (name: string) => void;
|
|
}
|
|
|
|
const ThemeContext = createContext<ThemeContextValue>({
|
|
theme: themes[0],
|
|
themeName: DEFAULT_THEME,
|
|
setTheme: () => {},
|
|
});
|
|
|
|
export function useTheme() {
|
|
return useContext(ThemeContext);
|
|
}
|
|
|
|
// Unique ID for the dynamic style element
|
|
const STYLE_ID = "theme-gradient-style";
|
|
|
|
function applyTheme(theme: Theme) {
|
|
const root = document.documentElement;
|
|
|
|
// Toggle dark class based on theme
|
|
if (theme.isDark) {
|
|
root.classList.add("dark");
|
|
} else {
|
|
root.classList.remove("dark");
|
|
}
|
|
|
|
// Set all CSS custom properties from the theme
|
|
for (const [key, value] of Object.entries(theme.vars)) {
|
|
root.style.setProperty(key, value);
|
|
}
|
|
|
|
// Set the body background
|
|
document.body.style.background = theme.bodyBg;
|
|
|
|
// Set the gradient via a style element (body::before can't be styled inline)
|
|
let styleEl = document.getElementById(STYLE_ID) as HTMLStyleElement | null;
|
|
if (!styleEl) {
|
|
styleEl = document.createElement("style");
|
|
styleEl.id = STYLE_ID;
|
|
document.head.appendChild(styleEl);
|
|
}
|
|
styleEl.textContent = `
|
|
body::before {
|
|
background: ${theme.bgGradient} !important;
|
|
}
|
|
`;
|
|
|
|
// Store in localStorage
|
|
try {
|
|
localStorage.setItem("homelab-theme", theme.name);
|
|
} catch {
|
|
// ignore storage errors
|
|
}
|
|
}
|
|
|
|
export function ThemeProvider({ children }: { children: ReactNode }) {
|
|
const [themeName, setThemeName] = useState(DEFAULT_THEME);
|
|
const theme = getTheme(themeName);
|
|
|
|
// Load saved theme on mount
|
|
useEffect(() => {
|
|
try {
|
|
const saved = localStorage.getItem("homelab-theme");
|
|
if (saved && themes.some((t) => t.name === saved)) {
|
|
setThemeName(saved);
|
|
}
|
|
} catch {
|
|
// ignore
|
|
}
|
|
}, []);
|
|
|
|
// Apply theme whenever it changes
|
|
useEffect(() => {
|
|
applyTheme(theme);
|
|
}, [theme]);
|
|
|
|
const setTheme = useCallback((name: string) => {
|
|
if (themes.some((t) => t.name === name)) {
|
|
setThemeName(name);
|
|
}
|
|
}, []);
|
|
|
|
return (
|
|
<ThemeContext.Provider value={{ theme, themeName, setTheme }}>
|
|
{children}
|
|
</ThemeContext.Provider>
|
|
);
|
|
}
|