mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:21:09 +02:00
feat: implement unified theme system across all web apps
SUMMARY: Create a unified theming architecture with two new shared packages (@manacore/shared-theme and @manacore/shared-theme-ui) that provides consistent theming across all 4 web applications while allowing app-specific primary color customization. NEW PACKAGES: @manacore/shared-theme: - Svelte 5 Runes-based theme store factory - 4 theme variants: Lume (Gold), Nature (Green), Stone (Blue Gray), Ocean (Blue) - 3 theme modes: Light, Dark, System (auto-detect) - HSL-based color system with 18 semantic tokens - localStorage persistence per app - System preference detection via matchMedia - Pre-defined configs for all apps (memoro, manacore, manadeck, maerchenzauber) @manacore/shared-theme-ui: - ThemeToggle: Light/dark mode toggle button - ThemeSelector: Visual theme variant selector with color dots - ThemeModeSelector: Segmented control for light/dark/system UPDATED PACKAGES: @manacore/shared-tailwind: - Converted from HEX to HSL-based CSS variables - Updated preset.js with hsl(var(--color-*)) syntax - themes.css now contains all 4 theme variants with light/dark modes APP MIGRATIONS: memoro/web: - Replaced 145 LOC theme store with 25 LOC shared implementation - Deleted local ThemeSelector.svelte and ThemeToggle.svelte - Primary color: Gold (47 95% 58%) manacore/web: - Replaced 80 LOC theme store with 25 LOC shared implementation - Deleted local ThemeToggle.svelte - Primary color: Indigo (239 84% 67%) manadeck/web: - Added new theme store using shared package - Primary color: Indigo (239 84% 67%) maerchenzauber/web: - Added new theme store using shared package - Primary color: Purple (280 60% 55%) All app layouts updated with theme.initialize() in onMount. BENEFITS: - ~225 LOC of app-specific code reduced to ~100 LOC total - Single source of truth for theme logic - Consistent theming experience across all apps - Easy to add new theme variants - App-specific primary colors preserved 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
ef70a1af0b
commit
96e0aceb93
31 changed files with 2993 additions and 1089 deletions
|
|
@ -1,40 +0,0 @@
|
|||
<script lang="ts">
|
||||
import { theme } from '$lib/stores/theme';
|
||||
|
||||
let currentTheme = $derived($theme);
|
||||
|
||||
function toggleTheme() {
|
||||
theme.toggleMode();
|
||||
}
|
||||
</script>
|
||||
|
||||
<button
|
||||
onclick={toggleTheme}
|
||||
class="rounded-lg p-2 transition-colors hover:bg-gray-200 dark:hover:bg-gray-700"
|
||||
aria-label="Toggle theme"
|
||||
title={currentTheme.effectiveMode === 'light'
|
||||
? 'Switch to dark mode'
|
||||
: 'Switch to light mode'}
|
||||
>
|
||||
{#if currentTheme.effectiveMode === 'light'}
|
||||
<!-- Moon Icon (Dark Mode) -->
|
||||
<svg class="h-5 w-5 text-gray-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"
|
||||
/>
|
||||
</svg>
|
||||
{:else}
|
||||
<!-- Sun Icon (Light Mode) -->
|
||||
<svg class="h-5 w-5 text-gray-300" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"
|
||||
/>
|
||||
</svg>
|
||||
{/if}
|
||||
</button>
|
||||
|
|
@ -1,79 +1,25 @@
|
|||
import { writable, derived } from 'svelte/store';
|
||||
import { browser } from '$app/environment';
|
||||
/**
|
||||
* ManaCore Theme Store
|
||||
*
|
||||
* Uses the shared theme system with ManaCore's indigo primary color.
|
||||
*/
|
||||
import { createThemeStore } from '@manacore/shared-theme';
|
||||
|
||||
type ThemeMode = 'light' | 'dark' | 'system';
|
||||
// Re-export types for convenience
|
||||
export type { ThemeMode, ThemeVariant, EffectiveMode } from '@manacore/shared-theme';
|
||||
|
||||
interface ThemeState {
|
||||
mode: ThemeMode;
|
||||
effectiveMode: 'light' | 'dark';
|
||||
}
|
||||
|
||||
function createThemeStore() {
|
||||
const getInitialMode = (): ThemeMode => {
|
||||
if (browser) {
|
||||
const stored = localStorage.getItem('theme-mode');
|
||||
if (stored === 'light' || stored === 'dark' || stored === 'system') {
|
||||
return stored;
|
||||
}
|
||||
}
|
||||
return 'system';
|
||||
};
|
||||
|
||||
const getSystemPreference = (): 'light' | 'dark' => {
|
||||
if (browser && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
return 'dark';
|
||||
}
|
||||
return 'light';
|
||||
};
|
||||
|
||||
const mode = writable<ThemeMode>(getInitialMode());
|
||||
|
||||
const effectiveMode = derived(mode, ($mode) => {
|
||||
if ($mode === 'system') {
|
||||
return getSystemPreference();
|
||||
}
|
||||
return $mode;
|
||||
});
|
||||
|
||||
const state = derived([mode, effectiveMode], ([$mode, $effectiveMode]) => ({
|
||||
mode: $mode,
|
||||
effectiveMode: $effectiveMode
|
||||
}));
|
||||
|
||||
// Apply theme to document
|
||||
if (browser) {
|
||||
effectiveMode.subscribe((effective) => {
|
||||
if (effective === 'dark') {
|
||||
document.documentElement.classList.add('dark');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
});
|
||||
|
||||
// Listen for system preference changes
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
|
||||
mode.update((m) => m); // Trigger re-evaluation
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
subscribe: state.subscribe,
|
||||
setMode: (newMode: ThemeMode) => {
|
||||
mode.set(newMode);
|
||||
if (browser) {
|
||||
localStorage.setItem('theme-mode', newMode);
|
||||
}
|
||||
},
|
||||
toggleMode: () => {
|
||||
mode.update((current) => {
|
||||
const newMode = current === 'light' ? 'dark' : current === 'dark' ? 'system' : 'light';
|
||||
if (browser) {
|
||||
localStorage.setItem('theme-mode', newMode);
|
||||
}
|
||||
return newMode;
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const theme = createThemeStore();
|
||||
/**
|
||||
* ManaCore theme store instance
|
||||
*
|
||||
* - Default variant: ocean (blue)
|
||||
* - Custom primary: Indigo (#6366f1)
|
||||
* - All 4 theme variants available
|
||||
*/
|
||||
export const theme = createThemeStore({
|
||||
appId: 'manacore',
|
||||
defaultVariant: 'ocean',
|
||||
primaryColor: {
|
||||
light: '239 84% 67%', // Indigo #6366f1
|
||||
dark: '239 84% 67%',
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,10 +2,15 @@
|
|||
import '../app.css';
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { onMount } from 'svelte';
|
||||
import { theme } from '$lib/stores/theme';
|
||||
|
||||
let { data, children } = $props();
|
||||
|
||||
onMount(() => {
|
||||
// Initialize theme
|
||||
const cleanupTheme = theme.initialize();
|
||||
|
||||
// Setup auth state change listener
|
||||
const {
|
||||
data: { subscription }
|
||||
} = data.supabase.auth.onAuthStateChange(async (event, session) => {
|
||||
|
|
@ -16,7 +21,10 @@
|
|||
}
|
||||
});
|
||||
|
||||
return () => subscription.unsubscribe();
|
||||
return () => {
|
||||
cleanupTheme();
|
||||
subscription.unsubscribe();
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue