Enhances employee hours view with dynamic weekly schedule rendering Updates toggle slider and theme switch components with improved interactions Adds more flexible notification and settings configurations for employees Improves user experience by streamlining UI controls and schedule display
131 lines
3.6 KiB
TypeScript
131 lines
3.6 KiB
TypeScript
/**
|
|
* Theme Controller
|
|
*
|
|
* Handles dark/light mode switching and system preference detection
|
|
*/
|
|
|
|
export type Theme = 'light' | 'dark' | 'system';
|
|
|
|
export class ThemeController {
|
|
private static readonly STORAGE_KEY = 'theme-preference';
|
|
private static readonly DARK_CLASS = 'dark-mode';
|
|
private static readonly LIGHT_CLASS = 'light-mode';
|
|
|
|
private root: HTMLElement;
|
|
private themeOptions: NodeListOf<HTMLElement>;
|
|
private themeCheckbox: HTMLInputElement | null;
|
|
|
|
constructor() {
|
|
this.root = document.documentElement;
|
|
this.themeOptions = document.querySelectorAll<HTMLElement>('swp-theme-option');
|
|
this.themeCheckbox = document.getElementById('themeCheckbox') as HTMLInputElement | null;
|
|
|
|
this.applyTheme(this.current);
|
|
this.updateUI();
|
|
this.setupListeners();
|
|
}
|
|
|
|
/**
|
|
* Get the current theme setting
|
|
*/
|
|
get current(): Theme {
|
|
const stored = localStorage.getItem(ThemeController.STORAGE_KEY) as Theme | null;
|
|
if (stored === 'dark' || stored === 'light' || stored === 'system') {
|
|
return stored;
|
|
}
|
|
return 'system';
|
|
}
|
|
|
|
/**
|
|
* Check if dark mode is currently active
|
|
*/
|
|
get isDark(): boolean {
|
|
return this.root.classList.contains(ThemeController.DARK_CLASS) ||
|
|
(this.systemPrefersDark && !this.root.classList.contains(ThemeController.LIGHT_CLASS));
|
|
}
|
|
|
|
/**
|
|
* Check if system prefers dark mode
|
|
*/
|
|
get systemPrefersDark(): boolean {
|
|
return window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
}
|
|
|
|
/**
|
|
* Set theme and persist preference
|
|
*/
|
|
set(theme: Theme): void {
|
|
localStorage.setItem(ThemeController.STORAGE_KEY, theme);
|
|
this.applyTheme(theme);
|
|
this.updateUI();
|
|
}
|
|
|
|
/**
|
|
* Toggle between light and dark themes
|
|
*/
|
|
toggle(): void {
|
|
this.set(this.isDark ? 'light' : 'dark');
|
|
}
|
|
|
|
private applyTheme(theme: Theme): void {
|
|
this.root.classList.remove(ThemeController.DARK_CLASS, ThemeController.LIGHT_CLASS);
|
|
|
|
if (theme === 'dark') {
|
|
this.root.classList.add(ThemeController.DARK_CLASS);
|
|
} else if (theme === 'light') {
|
|
this.root.classList.add(ThemeController.LIGHT_CLASS);
|
|
}
|
|
// 'system' leaves both classes off, letting CSS media query handle it
|
|
}
|
|
|
|
private updateUI(): void {
|
|
const darkActive = this.isDark;
|
|
|
|
// Update theme options
|
|
this.themeOptions?.forEach(option => {
|
|
const theme = option.dataset.theme as Theme;
|
|
const isActive = (theme === 'dark' && darkActive) || (theme === 'light' && !darkActive);
|
|
option.classList.toggle('active', isActive);
|
|
});
|
|
|
|
// Update checkbox (checked = dark mode)
|
|
if (this.themeCheckbox) {
|
|
this.themeCheckbox.checked = darkActive;
|
|
}
|
|
}
|
|
|
|
private setupListeners(): void {
|
|
// Theme option clicks
|
|
this.themeOptions.forEach(option => {
|
|
option.addEventListener('click', (e) => this.handleOptionClick(e));
|
|
});
|
|
|
|
// Theme checkbox toggle
|
|
this.themeCheckbox?.addEventListener('change', () => {
|
|
this.set(this.themeCheckbox!.checked ? 'dark' : 'light');
|
|
});
|
|
|
|
// System theme changes
|
|
window.matchMedia('(prefers-color-scheme: dark)')
|
|
.addEventListener('change', () => this.handleSystemChange());
|
|
}
|
|
|
|
private handleOptionClick(e: Event): void {
|
|
const target = e.target as HTMLElement;
|
|
const option = target.closest<HTMLElement>('swp-theme-option');
|
|
|
|
if (option) {
|
|
const theme = option.dataset.theme as Theme;
|
|
if (theme) {
|
|
this.set(theme);
|
|
}
|
|
}
|
|
}
|
|
|
|
private handleSystemChange(): void {
|
|
// Only react to system changes if we're using system preference
|
|
if (this.current === 'system') {
|
|
this.updateUI();
|
|
}
|
|
}
|
|
}
|