initial
This commit is contained in:
112
ts_web/services/theme-service.ts
Normal file
112
ts_web/services/theme-service.ts
Normal 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();
|
||||
Reference in New Issue
Block a user