This commit is contained in:
2025-12-22 10:53:15 +00:00
commit 753a98c67b
63 changed files with 15976 additions and 0 deletions

View File

@@ -0,0 +1,112 @@
/**
* 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();