65 lines
1.8 KiB
TypeScript
65 lines
1.8 KiB
TypeScript
|
|
import { Injectable, signal, effect } from '@angular/core';
|
||
|
|
|
||
|
|
export type Theme = 'light' | 'dark' | 'system';
|
||
|
|
|
||
|
|
@Injectable({ providedIn: 'root' })
|
||
|
|
export class ThemeService {
|
||
|
|
theme = signal<Theme>(this.loadTheme());
|
||
|
|
|
||
|
|
constructor() {
|
||
|
|
effect(() => {
|
||
|
|
this.applyTheme(this.theme());
|
||
|
|
});
|
||
|
|
|
||
|
|
// Listen for system preference changes
|
||
|
|
if (typeof window !== 'undefined') {
|
||
|
|
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
|
||
|
|
if (this.theme() === 'system') {
|
||
|
|
this.applyTheme('system');
|
||
|
|
}
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private loadTheme(): Theme {
|
||
|
|
if (typeof localStorage === 'undefined') return 'system';
|
||
|
|
const stored = localStorage.getItem('onebox-theme');
|
||
|
|
if (stored === 'light' || stored === 'dark' || stored === 'system') {
|
||
|
|
return stored;
|
||
|
|
}
|
||
|
|
return 'system';
|
||
|
|
}
|
||
|
|
|
||
|
|
setTheme(theme: Theme): void {
|
||
|
|
this.theme.set(theme);
|
||
|
|
if (typeof localStorage !== 'undefined') {
|
||
|
|
localStorage.setItem('onebox-theme', theme);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
toggle(): void {
|
||
|
|
const resolved = this.resolvedTheme();
|
||
|
|
this.setTheme(resolved === 'dark' ? 'light' : 'dark');
|
||
|
|
}
|
||
|
|
|
||
|
|
isDark(): boolean {
|
||
|
|
return this.resolvedTheme() === 'dark';
|
||
|
|
}
|
||
|
|
|
||
|
|
resolvedTheme(): 'light' | 'dark' {
|
||
|
|
if (this.theme() === 'system') {
|
||
|
|
if (typeof window === 'undefined') return 'light';
|
||
|
|
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
||
|
|
}
|
||
|
|
return this.theme() as 'light' | 'dark';
|
||
|
|
}
|
||
|
|
|
||
|
|
private applyTheme(theme: Theme): void {
|
||
|
|
if (typeof document === 'undefined') return;
|
||
|
|
const resolved = theme === 'system'
|
||
|
|
? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light')
|
||
|
|
: theme;
|
||
|
|
document.documentElement.classList.toggle('dark', resolved === 'dark');
|
||
|
|
}
|
||
|
|
}
|