From 6ee8582ce537e6217223b1024fe8d2364f2cf5e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=BChn?= Date: Thu, 10 Sep 2026 17:34:40 +0200 Subject: [PATCH] Theme-Toggle: Dark/Light-Mode pro Nutzer (localStorage, System-Default, FOUC-Schutz) --- frontend/src/App.jsx | 13 ++++--- frontend/src/components/Sidebar.jsx | 22 +++++++++++ frontend/src/context/ThemeContext.jsx | 53 +++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 5 deletions(-) create mode 100644 frontend/src/context/ThemeContext.jsx diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 5042dab..b0647da 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,6 +1,7 @@ import React, { useState, useEffect } from 'react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { AuthProvider, useAuth } from './context/AuthContext'; +import { ThemeProvider } from './context/ThemeContext'; import { ToastProvider, useToast } from './components/Toast'; import { setGlobalErrorHandler } from './utils/api'; import AuthModal from './components/AuthModal'; @@ -139,11 +140,13 @@ function AppContent() { export default function App() { return ( - - - - - + + + + + + + ); } diff --git a/frontend/src/components/Sidebar.jsx b/frontend/src/components/Sidebar.jsx index 87c295f..aa14d07 100644 --- a/frontend/src/components/Sidebar.jsx +++ b/frontend/src/components/Sidebar.jsx @@ -1,5 +1,6 @@ import React, { useState } from 'react'; import { useAuth } from '../context/AuthContext'; +import { useTheme } from '../context/ThemeContext'; // Punkt 5: SVG Icons für jeden Tab const tabIcons = { @@ -22,6 +23,7 @@ const tabIcons = { export default function Sidebar({ activeTab, onTabChange }) { const { user, logout } = useAuth(); + const { isDark, toggleTheme } = useTheme(); // Punkt 19: Collapsible Sidebar State const [collapsed, setCollapsed] = useState(false); @@ -101,6 +103,26 @@ export default function Sidebar({ activeTab, onTabChange }) { )} + {/* Theme-Toggle: Dark/Light-Mode, individuelle Einstellung pro Nutzer (localStorage) */} + {!collapsed && ( + + )} + {collapsed && ( + + )} diff --git a/frontend/src/context/ThemeContext.jsx b/frontend/src/context/ThemeContext.jsx new file mode 100644 index 0000000..c637962 --- /dev/null +++ b/frontend/src/context/ThemeContext.jsx @@ -0,0 +1,53 @@ +import React, { createContext, useContext, useEffect, useState } from 'react'; + +/** + * Theme-Context: Dark/Light-Mode-Umschaltung für daisyUI (data-theme auf ). + * - Persistenz in localStorage ('workflow_theme': 'dark' | 'light') + * - Ohne gespeicherte Wahl folgt das Theme der Systemeinstellung (prefers-color-scheme) + * - FOUC-Schutz: initiales data-theme wird synchron vor dem ersten Render gesetzt + */ + +const STORAGE_KEY = 'workflow_theme'; + +// Synchron beim Modul-Load ausführen — verhindert einen Theme-Flash beim Start +function resolveInitialTheme() { + try { + const saved = localStorage.getItem(STORAGE_KEY); + if (saved === 'dark' || saved === 'light') return saved; + } catch (e) { /* localStorage nicht verfügbar — Fallback unten */ } + if (typeof window !== 'undefined' && window.matchMedia?.('(prefers-color-scheme: dark)').matches) { + return 'dark'; + } + return 'light'; +} + +// Direkt beim Laden das Theme setzen (vor React-Render) +const initialTheme = resolveInitialTheme(); +document.documentElement.setAttribute('data-theme', initialTheme); + +const ThemeContext = createContext(null); + +export function ThemeProvider({ children }) { + const [theme, setTheme] = useState(initialTheme); + + useEffect(() => { + document.documentElement.setAttribute('data-theme', theme); + try { + localStorage.setItem(STORAGE_KEY, theme); + } catch (e) { /* Speichern fehlgeschlagen — Theme bleibt für diese Session aktiv */ } + }, [theme]); + + const toggleTheme = () => setTheme((t) => (t === 'dark' ? 'light' : 'dark')); + + return ( + + {children} + + ); +} + +export function useTheme() { + const ctx = useContext(ThemeContext); + if (!ctx) throw new Error('useTheme must be used within ThemeProvider'); + return ctx; +} \ No newline at end of file