feat: add dark/light theme toggle with system preference support (#18)
Add theme switching with three modes (system/light/dark) using Tailwind v4 class-based dark mode. Theme preference is persisted in localStorage and defaults to the system's prefers-color-scheme. All components updated with dark: variants for consistent dark mode rendering including SVG elements. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
86
src/lib/stores/theme.svelte.ts
Normal file
86
src/lib/stores/theme.svelte.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
export type ThemeMode = 'light' | 'dark' | 'system';
|
||||
|
||||
const STORAGE_KEY = 'llm-multiverse-theme';
|
||||
|
||||
function createThemeStore() {
|
||||
let mode: ThemeMode = $state('system');
|
||||
let resolvedDark = $state(false);
|
||||
|
||||
function applyTheme(isDark: boolean) {
|
||||
if (typeof document === 'undefined') return;
|
||||
const root = document.documentElement;
|
||||
// Add transition class for smooth switching
|
||||
root.classList.add('theme-transition');
|
||||
if (isDark) {
|
||||
root.classList.add('dark');
|
||||
} else {
|
||||
root.classList.remove('dark');
|
||||
}
|
||||
// Remove transition class after animation completes
|
||||
setTimeout(() => {
|
||||
root.classList.remove('theme-transition');
|
||||
}, 300);
|
||||
}
|
||||
|
||||
function getSystemPreference(): boolean {
|
||||
if (typeof window === 'undefined') return false;
|
||||
return window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||
}
|
||||
|
||||
function init() {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
// Load saved preference
|
||||
const saved = localStorage.getItem(STORAGE_KEY) as ThemeMode | null;
|
||||
if (saved === 'light' || saved === 'dark' || saved === 'system') {
|
||||
mode = saved;
|
||||
} else {
|
||||
mode = 'system';
|
||||
}
|
||||
|
||||
// Listen for system preference changes
|
||||
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||||
mediaQuery.addEventListener('change', (e) => {
|
||||
if (mode === 'system') {
|
||||
resolvedDark = e.matches;
|
||||
applyTheme(resolvedDark);
|
||||
}
|
||||
});
|
||||
|
||||
// Apply initial theme
|
||||
resolvedDark =
|
||||
mode === 'dark' ? true : mode === 'light' ? false : getSystemPreference();
|
||||
applyTheme(resolvedDark);
|
||||
}
|
||||
|
||||
function setMode(newMode: ThemeMode) {
|
||||
mode = newMode;
|
||||
if (typeof window !== 'undefined') {
|
||||
localStorage.setItem(STORAGE_KEY, newMode);
|
||||
}
|
||||
resolvedDark =
|
||||
newMode === 'dark' ? true : newMode === 'light' ? false : getSystemPreference();
|
||||
applyTheme(resolvedDark);
|
||||
}
|
||||
|
||||
function cycle() {
|
||||
const order: ThemeMode[] = ['system', 'light', 'dark'];
|
||||
const idx = order.indexOf(mode);
|
||||
const next = order[(idx + 1) % order.length];
|
||||
setMode(next);
|
||||
}
|
||||
|
||||
return {
|
||||
get mode() {
|
||||
return mode;
|
||||
},
|
||||
get isDark() {
|
||||
return resolvedDark;
|
||||
},
|
||||
init,
|
||||
setMode,
|
||||
cycle
|
||||
};
|
||||
}
|
||||
|
||||
export const themeStore = createThemeStore();
|
||||
Reference in New Issue
Block a user