/**
* Arod Theme Toggle Component
* A theme switcher for light/dark/system modes
*
* Usage:
*
*
* Note: This component manages the theme at the document level
* by setting a data-theme attribute on or toggling a class.
* It also persists the preference in localStorage.
*/
// ============================================
// AROD-THEME - Theme Toggle Component
// ============================================
class ArodTheme extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.themes = ['system', 'light', 'dark', 'high-contrast', 'colorblind', 'sepia'];
this.currentTheme = 'system';
}
connectedCallback() {
this.loadTheme();
this.render();
this.setupEventListeners();
this.applyTheme();
this.watchSystemTheme();
}
disconnectedCallback() {
this.unwatchSystemTheme();
}
render() {
this.shadowRoot.innerHTML = `
`;
}
setupEventListeners() {
const toggle = this.shadowRoot.querySelector('.theme-toggle');
toggle.addEventListener('click', () => {
this.cycleTheme();
});
}
cycleTheme() {
const currentIndex = this.themes.indexOf(this.currentTheme);
const nextIndex = (currentIndex + 1) % this.themes.length;
const nextTheme = this.themes[nextIndex];
this.setTheme(nextTheme);
}
loadTheme() {
// Load from localStorage or default to system
const saved = localStorage.getItem('arod-theme');
this.currentTheme = saved || 'system';
}
setTheme(theme) {
this.currentTheme = theme;
localStorage.setItem('arod-theme', theme);
this.applyTheme();
this.updateUI();
// Dispatch event for other components to react
this.dispatchEvent(new CustomEvent('theme-change', {
detail: { theme },
bubbles: true,
composed: true
}));
}
applyTheme() {
const root = document.documentElement;
let effectiveTheme = this.currentTheme;
if (this.currentTheme === 'system') {
effectiveTheme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
// Set data attribute on html element
root.setAttribute('data-theme', effectiveTheme);
// Also set on this component for styling
this.setAttribute('data-theme', effectiveTheme);
// Update all arod components
document.querySelectorAll('arod-menu, article-sidenotes').forEach(el => {
el.setAttribute('theme', effectiveTheme);
});
this.updateUI();
}
updateUI() {
const icon = this.shadowRoot.querySelector('.theme-icon');
const toggle = this.shadowRoot.querySelector('.theme-toggle');
const tooltip = this.shadowRoot.querySelector('.tooltip');
// Clear existing icon with animation
const existingIcons = icon.querySelectorAll('svg');
existingIcons.forEach(svg => {
svg.classList.remove('entering');
svg.classList.add('exiting');
});
// Add new icon after a short delay for smooth transition
setTimeout(() => {
let iconSvg;
let ariaLabel;
let themeName;
switch (this.currentTheme) {
case 'system':
// Hybrid icon for system mode
iconSvg = ``;
ariaLabel = 'Toggle theme (System)';
themeName = 'System';
break;
case 'light':
// Sun icon for light mode
iconSvg = ``;
ariaLabel = 'Toggle theme (Light)';
themeName = 'Light';
break;
case 'dark':
// Moon icon for dark mode
iconSvg = ``;
ariaLabel = 'Toggle theme (Dark)';
themeName = 'Dark';
break;
case 'high-contrast':
// Eye icon for high contrast
iconSvg = ``;
ariaLabel = 'Toggle theme (High Contrast)';
themeName = 'High Contrast';
break;
case 'colorblind':
// Palette icon for colorblind mode
iconSvg = ``;
ariaLabel = 'Toggle theme (Colorblind)';
themeName = 'Colorblind';
break;
case 'sepia':
// Book icon for sepia/reading mode
iconSvg = ``;
ariaLabel = 'Toggle theme (Sepia)';
themeName = 'Sepia';
break;
default:
// Default to system icon
iconSvg = ``;
ariaLabel = 'Toggle theme';
themeName = 'System';
}
icon.innerHTML = iconSvg;
toggle.setAttribute('aria-label', ariaLabel);
tooltip.textContent = themeName;
}, 150);
}
getEffectiveTheme() {
if (this.currentTheme === 'system') {
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
}
return this.currentTheme;
}
watchSystemTheme() {
this.mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
this.systemThemeHandler = () => {
if (this.currentTheme === 'system') {
this.applyTheme();
}
};
this.mediaQuery.addEventListener('change', this.systemThemeHandler);
}
unwatchSystemTheme() {
if (this.mediaQuery && this.systemThemeHandler) {
this.mediaQuery.removeEventListener('change', this.systemThemeHandler);
}
}
}
// Register component
customElements.define('arod-theme', ArodTheme);