From 3e3ca3621c4e6d3acb234e993091b3365bb55eaa Mon Sep 17 00:00:00 2001 From: Andreas Weyer Date: Sun, 1 Feb 2026 20:06:27 +0000 Subject: [PATCH] Add dark mode option --- src/App.tsx | 39 ++++++++++++++++++++++++++-- src/components/language-selector.tsx | 22 +++++++--------- src/components/theme-selector.tsx | 29 +++++++++++++++++++++ src/constants/defaults.ts | 2 ++ src/locales/de.ts | 5 ++++ src/locales/en.ts | 5 ++++ src/styles/global.css | 27 +++++++++++++++++++ 7 files changed, 114 insertions(+), 15 deletions(-) create mode 100644 src/components/theme-selector.tsx diff --git a/src/App.tsx b/src/App.tsx index 05e2777..971cadc 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -17,6 +17,7 @@ import DaySchedule from './components/day-schedule'; import SimulationChart from './components/simulation-chart'; import Settings from './components/settings'; import LanguageSelector from './components/language-selector'; +import ThemeSelector from './components/theme-selector'; import DisclaimerModal from './components/disclaimer-modal'; import DataManagementModal from './components/data-management-modal'; import { Button } from './components/ui/button'; @@ -91,6 +92,33 @@ const MedPlanAssistant = () => { uiSettings } = appState; + // Apply theme based on user preference or system setting + React.useEffect(() => { + const theme = uiSettings.theme || 'system'; + const root = document.documentElement; + + const applyTheme = (isDark: boolean) => { + if (isDark) { + root.classList.add('dark'); + } else { + root.classList.remove('dark'); + } + }; + + if (theme === 'system') { + // Detect system preference + const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); + applyTheme(mediaQuery.matches); + + // Listen for system theme changes + const listener = (e: MediaQueryListEvent) => applyTheme(e.matches); + mediaQuery.addEventListener('change', listener); + return () => mediaQuery.removeEventListener('change', listener); + } else { + applyTheme(theme === 'dark'); + } + }, [uiSettings.theme]); + const { showDayTimeOnXAxis, chartView, @@ -137,11 +165,18 @@ const MedPlanAssistant = () => {
-
+

{t('appTitle')}

- +
+ updateUiSetting('theme', theme)} + t={t} + /> + +

{t('appSubtitle')}

diff --git a/src/components/language-selector.tsx b/src/components/language-selector.tsx index 0289fdd..2166564 100644 --- a/src/components/language-selector.tsx +++ b/src/components/language-selector.tsx @@ -10,22 +10,18 @@ import React from 'react'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select'; -import { Label } from './ui/label'; const LanguageSelector = ({ currentLanguage, onLanguageChange, t }: any) => { return ( -
- - -
+ ); }; diff --git a/src/components/theme-selector.tsx b/src/components/theme-selector.tsx new file mode 100644 index 0000000..270b5a9 --- /dev/null +++ b/src/components/theme-selector.tsx @@ -0,0 +1,29 @@ +/** + * Theme Selector Component + * + * Provides UI for switching between light/dark/system theme modes. + * Uses shadcn/ui Select component. + * + * @author Andreas Weyer + * @license MIT + */ + +import React from 'react'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select'; + +const ThemeSelector = ({ currentTheme, onThemeChange, t }: any) => { + return ( + + ); +}; + +export default ThemeSelector; diff --git a/src/constants/defaults.ts b/src/constants/defaults.ts index 2630134..ef4925e 100644 --- a/src/constants/defaults.ts +++ b/src/constants/defaults.ts @@ -99,6 +99,7 @@ export interface UiSettings { showTherapeuticRange?: boolean; steadyStateDaysEnabled?: boolean; stickyChart: boolean; + theme?: 'light' | 'dark' | 'system'; } export interface AppState { @@ -171,5 +172,6 @@ export const getDefaultState = (): AppState => ({ showTherapeuticRange: false, steadyStateDaysEnabled: true, stickyChart: false, + theme: 'system', } }); diff --git a/src/locales/de.ts b/src/locales/de.ts index e65bef0..b82ec9d 100644 --- a/src/locales/de.ts +++ b/src/locales/de.ts @@ -17,6 +17,11 @@ export const de = { languageSelectorEN: "English", languageSelectorDE: "Deutsch", + // Theme selector + themeSelectorLight: "☀️ Hell", + themeSelectorDark: "🌙 Dunkel", + themeSelectorSystem: "💻 System", + // Dose Schedule myPlan: "Mein Plan", morning: "Morgens", diff --git a/src/locales/en.ts b/src/locales/en.ts index 8158a8c..8cd3fb5 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -17,6 +17,11 @@ export const en = { languageSelectorEN: "English", languageSelectorDE: "Deutsch", + // Theme selector + themeSelectorLight: "☀️ Light", + themeSelectorDark: "🌙 Dark", + themeSelectorSystem: "💻 System", + // Dose Schedule myPlan: "My Plan", morning: "Morning", diff --git a/src/styles/global.css b/src/styles/global.css index 73ea027..4a33a11 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -31,6 +31,33 @@ --radius: 0.625rem; } + .dark { + --background: 0 0% 10%; + --foreground: 0 0% 95%; + --card: 0 0% 14%; + --card-foreground: 0 0% 95%; + --popover: 0 0% 12%; + --popover-foreground: 0 0% 95%; + --primary: 217 91% 60%; + --primary-foreground: 0 0% 100%; + --secondary: 220 15% 20%; + --secondary-foreground: 0 0% 90%; + --muted: 220 10% 18%; + --muted-foreground: 0 0% 60%; + --accent: 220 10% 18%; + --accent-foreground: 0 0% 90%; + --destructive: 0 84% 60%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 25%; + --input: 0 0% 25%; + --ring: 0 0% 40%; + --chart-1: 12 76% 61%; + --chart-2: 173 58% 39%; + --chart-3: 197 37% 24%; + --chart-4: 43 74% 66%; + --chart-5: 27 87% 67%; + } + * { border-color: hsl(var(--border)); }