From b097d89318826839a8da7f5799369b6d9205ff41 Mon Sep 17 00:00:00 2001 From: Till-JS <101404291+Till-JS@users.noreply.github.com> Date: Thu, 29 Jan 2026 18:50:27 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(matrix-web):=20add=20theme=20m?= =?UTF-8?q?ode=20selector=20in=20settings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace placeholder appearance section with actual theme controls: - Light mode button with sun icon - Dark mode button with moon icon - System mode button (follows OS preference) - Shows current active mode status Uses shared-theme store for consistent theming across the app. Co-Authored-By: Claude Opus 4.5 --- .../src/routes/(app)/settings/+page.svelte | 58 +++++++++++- .../apps/web/src/lib/stores/theme.ts | 80 +++++++--------- .../apps/web/src/lib/stores/theme.svelte.ts | 93 +------------------ .../apps/web/src/routes/themes/+page.svelte | 3 +- docs/CONSOLIDATION_OPPORTUNITIES.md | 59 ++++++++++-- 5 files changed, 143 insertions(+), 150 deletions(-) diff --git a/apps/matrix/apps/web/src/routes/(app)/settings/+page.svelte b/apps/matrix/apps/web/src/routes/(app)/settings/+page.svelte index b3af567a4..6abec4776 100644 --- a/apps/matrix/apps/web/src/routes/(app)/settings/+page.svelte +++ b/apps/matrix/apps/web/src/routes/(app)/settings/+page.svelte @@ -17,6 +17,9 @@ BellRinging, SpeakerHigh, Eye, + Sun, + Moon, + Desktop, } from '@manacore/shared-icons'; import { VerificationDialog, RecoveryKeyDialog } from '$lib/components/crypto'; import { @@ -27,6 +30,7 @@ isNotificationSupported, } from '$lib/notifications'; import { browser } from '$app/environment'; + import { theme } from '$lib/stores/theme'; let verificationDialogOpen = $state(false); let recoveryDialogOpen = $state(false); @@ -208,14 +212,62 @@ - +
-
+

Erscheinungsbild

-

Theme-Einstellungen folgen bald...

+ +
+

Wähle dein bevorzugtes Farbschema

+ + +
+ + + + + +
+ + +
+ {#if theme.mode === 'system'} + Aktuell: {theme.isDark ? 'Dunkel' : 'Hell'} (basierend auf System) + {:else} + Aktuell: {theme.isDark ? 'Dunkel' : 'Hell'} + {/if} +
+
diff --git a/apps/questions/apps/web/src/lib/stores/theme.ts b/apps/questions/apps/web/src/lib/stores/theme.ts index 61984650e..838ae5c38 100644 --- a/apps/questions/apps/web/src/lib/stores/theme.ts +++ b/apps/questions/apps/web/src/lib/stores/theme.ts @@ -1,61 +1,47 @@ -import { browser } from '$app/environment'; +/** + * Theme Store - Manages theme state + * Uses shared theme store from @manacore/shared-theme + */ -type Theme = 'light' | 'dark' | 'system'; +import { createThemeStore, type ThemeMode } from '@manacore/shared-theme'; -function getInitialTheme(): Theme { - if (!browser) return 'system'; - - const stored = localStorage.getItem('theme') as Theme | null; - if (stored && ['light', 'dark', 'system'].includes(stored)) { - return stored; - } - return 'system'; -} - -function applyTheme(theme: Theme) { - if (!browser) return; - - const root = document.documentElement; - const systemDark = window.matchMedia('(prefers-color-scheme: dark)').matches; - const isDark = theme === 'dark' || (theme === 'system' && systemDark); - - if (isDark) { - root.classList.add('dark'); - } else { - root.classList.remove('dark'); - } -} - -let currentTheme: Theme = 'system'; +const sharedTheme = createThemeStore({ appId: 'questions' }); +// Wrapper to maintain backward-compatible API export const theme = { + // Legacy API (current → mode) get current() { - return currentTheme; + return sharedTheme.mode; }, - initialize() { - currentTheme = getInitialTheme(); - applyTheme(currentTheme); - - if (browser) { - window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => { - if (currentTheme === 'system') { - applyTheme('system'); - } - }); - } + // Forward all other getters + get mode() { + return sharedTheme.mode; + }, + get isDark() { + return sharedTheme.isDark; + }, + get variant() { + return sharedTheme.variant; + }, + get variants() { + return sharedTheme.variants; }, - set(newTheme: Theme) { - currentTheme = newTheme; - if (browser) { - localStorage.setItem('theme', newTheme); - } - applyTheme(newTheme); + // Legacy API (set → setMode) + set(newTheme: ThemeMode) { + sharedTheme.setMode(newTheme); }, + // Legacy API (toggle → toggleMode) toggle() { - const next = currentTheme === 'light' ? 'dark' : 'light'; - this.set(next); + sharedTheme.toggleMode(); }, + + // Forward new API + initialize: sharedTheme.initialize, + setMode: sharedTheme.setMode, + setVariant: sharedTheme.setVariant, + toggleMode: sharedTheme.toggleMode, + cycleMode: sharedTheme.cycleMode, }; diff --git a/apps/storage/apps/web/src/lib/stores/theme.svelte.ts b/apps/storage/apps/web/src/lib/stores/theme.svelte.ts index 2b29f70ba..ea2678fed 100644 --- a/apps/storage/apps/web/src/lib/stores/theme.svelte.ts +++ b/apps/storage/apps/web/src/lib/stores/theme.svelte.ts @@ -1,95 +1,8 @@ /** * Theme Store - Manages theme state + * Uses shared theme store from @manacore/shared-theme */ -import { browser } from '$app/environment'; -import { - THEME_DEFINITIONS, - THEME_VARIANTS, - type ThemeMode, - type ThemeVariant, - DEFAULT_VARIANT, -} from '@manacore/shared-theme'; +import { createThemeStore } from '@manacore/shared-theme'; -const STORAGE_KEY_MODE = 'storage-theme-mode'; -const STORAGE_KEY_VARIANT = 'storage-theme-variant'; - -function createThemeStore() { - let mode = $state('system'); - let variant = $state(DEFAULT_VARIANT); - let systemPrefersDark = $state(false); - - function getSystemPreference(): boolean { - if (!browser) return false; - return window.matchMedia('(prefers-color-scheme: dark)').matches; - } - - function applyTheme() { - if (!browser) return; - - const isDarkMode = mode === 'dark' || (mode === 'system' && systemPrefersDark); - const themeClass = isDarkMode - ? THEME_DEFINITIONS[variant].darkClass - : THEME_DEFINITIONS[variant].lightClass; - - document.documentElement.className = themeClass; - } - - return { - get mode() { - return mode; - }, - get variant() { - return variant; - }, - get isDark() { - return mode === 'dark' || (mode === 'system' && systemPrefersDark); - }, - get variants() { - return THEME_VARIANTS; - }, - - initialize() { - if (!browser) return; - - const savedMode = localStorage.getItem(STORAGE_KEY_MODE) as ThemeMode | null; - const savedVariant = localStorage.getItem(STORAGE_KEY_VARIANT) as ThemeVariant | null; - - if (savedMode) mode = savedMode; - if (savedVariant && savedVariant in THEME_DEFINITIONS) variant = savedVariant; - - systemPrefersDark = getSystemPreference(); - - const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); - mediaQuery.addEventListener('change', (e) => { - systemPrefersDark = e.matches; - applyTheme(); - }); - - applyTheme(); - }, - - setMode(newMode: ThemeMode) { - mode = newMode; - if (browser) { - localStorage.setItem(STORAGE_KEY_MODE, newMode); - } - applyTheme(); - }, - - setVariant(newVariant: ThemeVariant) { - variant = newVariant; - if (browser) { - localStorage.setItem(STORAGE_KEY_VARIANT, newVariant); - } - applyTheme(); - }, - - toggleMode() { - const newMode = mode === 'dark' ? 'light' : 'dark'; - this.setMode(newMode); - }, - }; -} - -export const theme = createThemeStore(); +export const theme = createThemeStore({ appId: 'storage' }); diff --git a/apps/storage/apps/web/src/routes/themes/+page.svelte b/apps/storage/apps/web/src/routes/themes/+page.svelte index 6fb349cc0..78d5542e1 100644 --- a/apps/storage/apps/web/src/routes/themes/+page.svelte +++ b/apps/storage/apps/web/src/routes/themes/+page.svelte @@ -27,7 +27,8 @@ >
{#if theme.variant === variant}
diff --git a/docs/CONSOLIDATION_OPPORTUNITIES.md b/docs/CONSOLIDATION_OPPORTUNITIES.md index 306f10442..f258ad043 100644 --- a/docs/CONSOLIDATION_OPPORTUNITIES.md +++ b/docs/CONSOLIDATION_OPPORTUNITIES.md @@ -235,13 +235,30 @@ export const { isSidebarMode, isNavCollapsed } = createSimpleNavigationStores({ --- -### 2.3 NIEDRIG: Theme Stores Migration +### ~~2.3 NIEDRIG: Theme Stores Migration~~ ✅ ERLEDIGT (~150 LOC gespart) -**Problem:** 2 Apps nutzen nicht `@manacore/shared-theme`: -- `apps/storage/apps/web/src/lib/stores/theme.svelte.ts` (96 LOC - custom) -- `apps/questions/apps/web/src/lib/stores/theme.ts` (custom) +**Status:** 2 Apps zu `createThemeStore()` aus `@manacore/shared-theme` migriert (29.01.2026) -**Aktion:** Migriere zu `createThemeStore()` aus `@manacore/shared-theme` +**Migrierte Apps:** +- ✅ `apps/storage/apps/web/src/lib/stores/theme.svelte.ts` (96 → 7 LOC = 89 LOC) +- ✅ `apps/questions/apps/web/src/lib/stores/theme.ts` (62 → 47 LOC = 15 LOC, mit Rückwärtskompatibilität) + +**Zusätzlich gefixt:** +- ✅ `apps/storage/apps/web/src/routes/themes/+page.svelte` - Bug mit `def.colors.primary` → `def.light.primary` + +**Questions Wrapper (Rückwärtskompatibilität):** +```typescript +// Legacy API (current, set, toggle) wird auf neue API gemappt +export const theme = { + get current() { return sharedTheme.mode; }, // Legacy + get mode() { return sharedTheme.mode; }, // New + set(newTheme) { sharedTheme.setMode(newTheme); }, // Legacy + toggle() { sharedTheme.toggleMode(); }, // Legacy + // ... new API forwarded +}; +``` + +**Einsparung:** ~104 LOC (158 → 54 LOC) --- @@ -290,13 +307,37 @@ export const { isSidebarMode, isNavCollapsed } = createSimpleNavigationStores({ --- -### 3.3 MITTEL: AppSlider Cleanup (240 LOC) +### ~~3.3 MITTEL: AppSlider Cleanup~~ ✅ ANALYSIERT (Keine Aktion nötig) -**Problem:** 8 Apps haben lokale `AppSlider.svelte` Kopien, obwohl shared-ui Version existiert. +**Status:** Nach Analyse: Die lokalen AppSlider.svelte Dateien sind KEINE Duplikate (29.01.2026) -**Betroffene Apps:** calendar, chat, contacts, manadeck, manacore, picture, presi, todo +**Ergebnis der Analyse:** +Die 8 lokalen `AppSlider.svelte` Dateien sind **Lokalisierungs-Wrapper**, nicht Duplikate: +- Sie importieren `AppSlider` aus `@manacore/shared-ui` +- Sie mappen `MANA_APPS` aus `@manacore/shared-branding` zu deutschen Labels +- Sie übergeben deutsche Lokalisierung (`APP_STATUS_LABELS.de`, `APP_SLIDER_LABELS.de`) an die shared Komponente -**Aktion:** Verifiziere Import aus `@manacore/shared-ui`, lösche lokale Kopien. +**Beispiel (apps/chat/apps/web):** +```svelte + + + +``` + +**Fazit:** Korrekte Architektur - shared-ui stellt die Komponente bereit, Apps liefern lokalisierte Daten. ---