// ============================================================================
// 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);
});
}
}