INSIGHT-MVP/packages/frontend/src/theme/ThemeContext.tsx
Thomas Reitz 3bedda2b9d feat: dark mode, collapsible sidebar, branding customization
- 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>
2026-03-10 11:47:51 +01:00

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>
);
}