// ============================================================================ // Stylescape | Theme Toggler // ============================================================================ // Manages dark/light theme switching with localStorage persistence. // Supports data-theme-toggle attributes for declarative configuration. // ============================================================================ /** * Available theme values */ export type Theme = "dark" | "light"; /** * Configuration options for theme toggling */ export interface ThemeTogglerOptions { /** Custom storage key for theme preference */ storageKey?: string; /** Custom attribute name on element */ themeAttribute?: string; /** Default theme if none is stored */ defaultTheme?: Theme; /** Callback when theme changes */ onChange?: (theme: Theme) => void; } /** * Static utility class for managing dark/light theme switching. * Persists user preference in localStorage. * * @example JavaScript * ```typescript * // Initialize with a toggle switch * ThemeToggler.initializeToggleSwitch("darkModeToggle") * * // Or register to initialize on page load * ThemeToggler.registerOnLoad() * * // Programmatic control * ThemeToggler.toggle() * ThemeToggler.setTheme("dark") * const current = ThemeToggler.getCurrentTheme() * ``` * * @example HTML with data-theme-toggle * ```html * * * ``` */ export class ThemeToggler { private static readonly THEME_ATTRIBUTE = "theme"; private static readonly DARK_THEME = "dark"; private static readonly LIGHT_THEME = "light"; private static readonly STORAGE_KEY = "preferredTheme"; private static readonly htmlElement = document.documentElement; private constructor() { // Prevent instantiation } /** * Toggle between dark and light themes. * Updates both the DOM attribute and localStorage. */ static toggle(): void { const newTheme = ThemeToggler.getCurrentTheme() === ThemeToggler.DARK_THEME ? ThemeToggler.LIGHT_THEME : ThemeToggler.DARK_THEME; ThemeToggler.setTheme(newTheme); } /** * Set theme explicitly to a specific value. * * @param theme - The theme to set ("dark" or "light") */ static setTheme(theme: string): void { ThemeToggler.htmlElement.dataset[ThemeToggler.THEME_ATTRIBUTE] = theme; localStorage.setItem(ThemeToggler.STORAGE_KEY, theme); } /** * Get the currently active theme. * Checks DOM attribute first, then localStorage, defaults to light. * * @returns The current theme ("dark" or "light") */ static getCurrentTheme(): string { return ( ThemeToggler.htmlElement.dataset[ThemeToggler.THEME_ATTRIBUTE] || localStorage.getItem(ThemeToggler.STORAGE_KEY) || ThemeToggler.LIGHT_THEME ); } /** * Sync the toggle input checkbox state with the current theme. * * @param toggle - The checkbox input element to sync */ private static syncToggleState(toggle: HTMLInputElement): void { const currentTheme = ThemeToggler.getCurrentTheme(); toggle.checked = currentTheme === ThemeToggler.DARK_THEME; } /** * Initialize a toggle switch (input[type=checkbox]) by ID or data attribute * @param toggleId The ID of the toggle (default: 'themeToggle') */ static initializeToggleSwitch(toggleId = "themeToggle"): void { let toggle = document.getElementById( toggleId, ) as HTMLInputElement | null; if (!toggle) { toggle = document.querySelector( "[data-theme-toggle]", ) as HTMLInputElement | null; } if (!toggle) { // console.warn( // `ThemeToggler: No toggle element found for ID '${toggleId}' or [data-theme-toggle].`, // ) return; } ThemeToggler.syncToggleState(toggle); toggle.addEventListener("change", () => { ThemeToggler.toggle(); }); } /** * Register initialization to occur after full page load * Recommended if HTML elements may load later */ static registerOnLoad(toggleId = "themeToggle"): void { window.addEventListener("load", () => { ThemeToggler.initializeToggleSwitch(toggleId); }); } }