mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-19 17:46:36 +03:00
Make theme switcher three-way (auto/light/dark)
Default is "auto", which inherits from the OS via prefers-color-scheme and follows it live. Clicking cycles auto → light → dark → auto; the explicit choice persists in localStorage while auto stores nothing. The toggle shows the active preference (half-circle / sun / moon) keyed on a new data-theme-pref attribute, so "auto" is its own visible state rather than collapsing into whatever the OS resolved to. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
58e9fceede
commit
1d5d745823
+53
-15
@@ -1,21 +1,48 @@
|
||||
// Theme toggle — light / dark with localStorage persistence.
|
||||
// Theme switcher — three-way: auto / light / dark with localStorage persistence.
|
||||
// "auto" (the default) inherits from the OS via prefers-color-scheme and is only
|
||||
// overridden once the user clicks the toggle. Clicking cycles auto → light → dark → auto.
|
||||
|
||||
const STORAGE_KEY = 'impeccable-theme';
|
||||
|
||||
export function getStoredTheme() {
|
||||
return localStorage.getItem(STORAGE_KEY);
|
||||
export function getStoredPref() {
|
||||
const v = localStorage.getItem(STORAGE_KEY);
|
||||
return v === 'light' || v === 'dark' ? v : 'auto';
|
||||
}
|
||||
|
||||
export function setStoredTheme(theme) {
|
||||
localStorage.setItem(STORAGE_KEY, theme);
|
||||
export function setStoredPref(pref) {
|
||||
if (pref === 'auto') localStorage.removeItem(STORAGE_KEY);
|
||||
else localStorage.setItem(STORAGE_KEY, pref);
|
||||
}
|
||||
|
||||
export function applyTheme(theme) {
|
||||
function systemTheme() {
|
||||
return window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches
|
||||
? 'light'
|
||||
: 'dark';
|
||||
}
|
||||
|
||||
export function resolveTheme(pref) {
|
||||
return pref === 'auto' ? systemTheme() : pref;
|
||||
}
|
||||
|
||||
const LABELS = {
|
||||
auto: 'Theme: auto, matching your system. Click to switch to light mode.',
|
||||
light: 'Theme: light. Click to switch to dark mode.',
|
||||
dark: 'Theme: dark. Click to switch back to auto.',
|
||||
};
|
||||
|
||||
const TITLES = {
|
||||
auto: 'Theme: auto (matches system)',
|
||||
light: 'Theme: light',
|
||||
dark: 'Theme: dark',
|
||||
};
|
||||
|
||||
export function applyTheme(pref) {
|
||||
const html = document.documentElement;
|
||||
const resolved = theme === 'light' ? 'light' : 'dark';
|
||||
const resolved = resolveTheme(pref);
|
||||
|
||||
html.classList.remove('light', 'dark');
|
||||
html.classList.add(resolved);
|
||||
html.setAttribute('data-theme-pref', pref);
|
||||
|
||||
const meta = document.querySelector('meta[name="theme-color"]');
|
||||
if (meta) {
|
||||
@@ -23,21 +50,32 @@ export function applyTheme(theme) {
|
||||
}
|
||||
|
||||
document.querySelectorAll('[data-theme-toggle]').forEach((btn) => {
|
||||
const next = resolved === 'light' ? 'dark' : 'light';
|
||||
btn.setAttribute('aria-label', resolved === 'light' ? 'Switch to dark mode' : 'Switch to light mode');
|
||||
btn.setAttribute('title', resolved === 'light' ? 'Dark mode' : 'Light mode');
|
||||
btn.dataset.nextTheme = next;
|
||||
btn.setAttribute('aria-label', LABELS[pref]);
|
||||
btn.setAttribute('title', TITLES[pref]);
|
||||
});
|
||||
}
|
||||
|
||||
function nextPref(pref) {
|
||||
return pref === 'auto' ? 'light' : pref === 'light' ? 'dark' : 'auto';
|
||||
}
|
||||
|
||||
export function initThemeToggle() {
|
||||
const stored = getStoredTheme();
|
||||
applyTheme(stored === 'light' ? 'light' : 'dark');
|
||||
applyTheme(getStoredPref());
|
||||
|
||||
// While in auto, follow the OS as it flips.
|
||||
if (window.matchMedia) {
|
||||
const mq = window.matchMedia('(prefers-color-scheme: light)');
|
||||
const onChange = () => {
|
||||
if (getStoredPref() === 'auto') applyTheme('auto');
|
||||
};
|
||||
if (mq.addEventListener) mq.addEventListener('change', onChange);
|
||||
else if (mq.addListener) mq.addListener(onChange);
|
||||
}
|
||||
|
||||
document.querySelectorAll('[data-theme-toggle]').forEach((btn) => {
|
||||
btn.addEventListener('click', () => {
|
||||
const next = btn.dataset.nextTheme || (document.documentElement.classList.contains('light') ? 'dark' : 'light');
|
||||
setStoredTheme(next);
|
||||
const next = nextPref(getStoredPref());
|
||||
setStoredPref(next);
|
||||
applyTheme(next);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user