mirror of
http://172.20.10.11:3000/gitadmin/INSIGHT-MVP.git
synced 2026-06-25 08:46:39 +02:00
- Add Light/Dark/System theme toggle with ThemeContext and CSS variables - Sidebar fully collapsible (icons-only mode, persisted in localStorage) - Anwendungen section collapsible with chevron toggle - Admin "Anpassungen" page: logo upload, sidebar color picker with presets - Backend branding endpoints (GET/POST /settings/branding) stored in Redis - Optional custom icon upload for external links (click icon field) - Backend favicon proxy with HTML parsing for reliable icon loading - Dark mode CSS variables for all components - Login page SSO button and error styles use CSS variables Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
78 lines
1.9 KiB
TypeScript
78 lines
1.9 KiB
TypeScript
import {
|
|
createContext,
|
|
useContext,
|
|
useState,
|
|
useEffect,
|
|
type ReactNode,
|
|
} from 'react';
|
|
|
|
type ThemeMode = 'light' | 'dark' | 'system';
|
|
|
|
interface ThemeContextType {
|
|
mode: ThemeMode;
|
|
setMode: (mode: ThemeMode) => void;
|
|
/** Tatsaechlich aktives Theme (resolved from system) */
|
|
resolved: 'light' | 'dark';
|
|
}
|
|
|
|
const ThemeContext = createContext<ThemeContextType>({
|
|
mode: 'system',
|
|
setMode: () => {},
|
|
resolved: 'light',
|
|
});
|
|
|
|
export function useTheme() {
|
|
return useContext(ThemeContext);
|
|
}
|
|
|
|
function getSystemTheme(): 'light' | 'dark' {
|
|
if (
|
|
typeof window !== 'undefined' &&
|
|
window.matchMedia('(prefers-color-scheme: dark)').matches
|
|
) {
|
|
return 'dark';
|
|
}
|
|
return 'light';
|
|
}
|
|
|
|
export function ThemeProvider({ children }: { children: ReactNode }) {
|
|
const [mode, setModeState] = useState<ThemeMode>(() => {
|
|
const saved = localStorage.getItem('theme-mode');
|
|
if (saved === 'light' || saved === 'dark' || saved === 'system') {
|
|
return saved;
|
|
}
|
|
return 'system';
|
|
});
|
|
|
|
const [systemTheme, setSystemTheme] = useState<'light' | 'dark'>(
|
|
getSystemTheme,
|
|
);
|
|
|
|
// Listen for system theme changes
|
|
useEffect(() => {
|
|
const mq = window.matchMedia('(prefers-color-scheme: dark)');
|
|
const handler = (e: MediaQueryListEvent) => {
|
|
setSystemTheme(e.matches ? 'dark' : 'light');
|
|
};
|
|
mq.addEventListener('change', handler);
|
|
return () => mq.removeEventListener('change', handler);
|
|
}, []);
|
|
|
|
const resolved = mode === 'system' ? systemTheme : mode;
|
|
|
|
// Apply theme to document
|
|
useEffect(() => {
|
|
document.documentElement.setAttribute('data-theme', resolved);
|
|
}, [resolved]);
|
|
|
|
const setMode = (newMode: ThemeMode) => {
|
|
setModeState(newMode);
|
|
localStorage.setItem('theme-mode', newMode);
|
|
};
|
|
|
|
return (
|
|
<ThemeContext.Provider value={{ mode, setMode, resolved }}>
|
|
{children}
|
|
</ThemeContext.Provider>
|
|
);
|
|
}
|