Sanitized mirror from private repository - 2026-04-18 11:19:59 UTC
This commit is contained in:
103
dashboard/ui/components/theme-provider.tsx
Normal file
103
dashboard/ui/components/theme-provider.tsx
Normal file
@@ -0,0 +1,103 @@
|
||||
"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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user