/** * Theme types */ export type Theme = 'light' | 'dark' | 'system'; /** * Theme service for managing light/dark mode * Singleton pattern with subscription support */ class ThemeService { private static instance: ThemeService; private currentTheme: Theme = 'system'; private listeners: Set<(theme: Theme, isDark: boolean) => void> = new Set(); private mediaQuery: MediaQueryList; private storageKey = 'dees-theme'; private constructor() { this.mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); this.loadTheme(); this.mediaQuery.addEventListener('change', () => this.applyTheme()); } /** * Get the singleton instance */ static getInstance(): ThemeService { if (!ThemeService.instance) { ThemeService.instance = new ThemeService(); } return ThemeService.instance; } /** * Load theme from localStorage */ private loadTheme() { const saved = localStorage.getItem(this.storageKey) as Theme; this.currentTheme = saved || 'system'; this.applyTheme(); } /** * Apply the current theme to the document */ private applyTheme() { const isDark = this.isDark(); if (isDark) { document.documentElement.setAttribute('data-theme', 'dark'); } else { document.documentElement.removeAttribute('data-theme'); } this.notifyListeners(); } /** * Check if dark mode is currently active */ isDark(): boolean { if (this.currentTheme === 'dark') return true; if (this.currentTheme === 'light') return false; return this.mediaQuery.matches; } /** * Get the current theme setting */ getTheme(): Theme { return this.currentTheme; } /** * Set the theme */ setTheme(theme: Theme) { this.currentTheme = theme; localStorage.setItem(this.storageKey, theme); this.applyTheme(); } /** * Toggle through themes: light -> dark -> system -> light */ toggleTheme() { const themes: Theme[] = ['light', 'dark', 'system']; const currentIndex = themes.indexOf(this.currentTheme); const nextIndex = (currentIndex + 1) % themes.length; this.setTheme(themes[nextIndex]); } /** * Subscribe to theme changes * Returns unsubscribe function */ subscribe(callback: (theme: Theme, isDark: boolean) => void): () => void { this.listeners.add(callback); // Call immediately with current state callback(this.currentTheme, this.isDark()); return () => this.listeners.delete(callback); } /** * Notify all listeners of theme change */ private notifyListeners() { this.listeners.forEach(callback => callback(this.currentTheme, this.isDark())); } } /** * Singleton theme service instance */ export const themeService = ThemeService.getInstance();