71 lines
1.6 KiB
TypeScript
71 lines
1.6 KiB
TypeScript
import { browser } from '$app/environment';
|
|
|
|
type Theme = 'light' | 'dark' | 'system';
|
|
type ResolvedTheme = 'light' | 'dark';
|
|
|
|
class ThemeStore {
|
|
current = $state<Theme>('system');
|
|
|
|
constructor() {
|
|
if (browser) {
|
|
const stored = localStorage.getItem('theme') as Theme | null;
|
|
this.current = stored || 'system';
|
|
this.applyTheme();
|
|
|
|
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
|
mediaQuery.addEventListener('change', () => {
|
|
if (this.current === 'system') {
|
|
this.applyTheme();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
private applyTheme() {
|
|
if (!browser) return;
|
|
|
|
const isDark = this.current === 'dark' ||
|
|
(this.current === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
|
|
|
if (isDark) {
|
|
document.documentElement.classList.add('dark');
|
|
} else {
|
|
document.documentElement.classList.remove('dark');
|
|
}
|
|
}
|
|
|
|
getResolvedTheme(): ResolvedTheme {
|
|
if (!browser) return 'light';
|
|
|
|
const isDark = this.current === 'dark' ||
|
|
(this.current === 'system' && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
|
return isDark ? 'dark' : 'light';
|
|
}
|
|
|
|
toggle() {
|
|
// Cycle through: light -> dark -> system -> light
|
|
if (this.current === 'light') {
|
|
this.current = 'dark';
|
|
} else if (this.current === 'dark') {
|
|
this.current = 'system';
|
|
} else {
|
|
this.current = 'light';
|
|
}
|
|
|
|
if (browser) {
|
|
localStorage.setItem('theme', this.current);
|
|
this.applyTheme();
|
|
}
|
|
}
|
|
|
|
set(theme: Theme) {
|
|
this.current = theme;
|
|
if (browser) {
|
|
localStorage.setItem('theme', this.current);
|
|
}
|
|
this.applyTheme();
|
|
}
|
|
}
|
|
|
|
export const themeStore = new ThemeStore();
|