Files
onebox/ui/src/app/core/services/theme.service.ts
2025-11-24 19:52:35 +00:00

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');
}
}