feat(theme): add CSS variable tokens and theme classes

- Define semantic color tokens (--color-bg, --color-text, etc.)
- Add .theme-dark, .theme-light, .theme-system CSS classes
- Create theme.ts utility for theme application
- Add early boot theme script to prevent FOUC
- Support system preference with live updates
This commit is contained in:
Gigi
2025-10-14 09:11:38 +02:00
parent 67a4e17055
commit 994d834a0b
11 changed files with 270 additions and 78 deletions

64
src/utils/theme.ts Normal file
View File

@@ -0,0 +1,64 @@
export type Theme = 'dark' | 'light' | 'system'
let mediaQueryListener: ((e: MediaQueryListEvent) => void) | null = null
/**
* Get the system's current theme preference
*/
export function getSystemTheme(): 'dark' | 'light' {
if (typeof window === 'undefined') return 'dark'
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
}
/**
* Apply theme to the document root element
* Handles 'system' theme by listening to OS preference changes
*/
export function applyTheme(theme: Theme): void {
const root = document.documentElement
// Remove existing theme classes
root.classList.remove('theme-dark', 'theme-light', 'theme-system')
// Clean up previous media query listener if exists
if (mediaQueryListener) {
window.matchMedia('(prefers-color-scheme: dark)').removeEventListener('change', mediaQueryListener)
mediaQueryListener = null
}
if (theme === 'system') {
root.classList.add('theme-system')
// Listen for system theme changes
mediaQueryListener = (e: MediaQueryListEvent) => {
console.log('🎨 System theme changed to:', e.matches ? 'dark' : 'light')
// The CSS media query handles the color changes
// We just need to update localStorage for no-FOUC on next load
localStorage.setItem('theme-system-current', e.matches ? 'dark' : 'light')
}
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', mediaQueryListener)
// Store current system theme for no-FOUC on next boot
localStorage.setItem('theme-system-current', getSystemTheme())
} else {
root.classList.add(`theme-${theme}`)
}
// Persist to localStorage for early boot application
localStorage.setItem('theme', theme)
console.log('🎨 Applied theme:', theme)
}
/**
* Get the current theme from localStorage or default to 'system'
*/
export function getStoredTheme(): Theme {
const stored = localStorage.getItem('theme')
if (stored === 'dark' || stored === 'light' || stored === 'system') {
return stored
}
return 'system'
}