refactor: extract theme logic into shared core module

This commit is contained in:
2026-04-15 17:06:37 -04:00
parent e1a7815b76
commit 0d940e3997
3 changed files with 75 additions and 67 deletions
+71
View File
@@ -0,0 +1,71 @@
'use client';
import { useState, useEffect } from 'react';
import { Sun01Icon, Moon02Icon, SunCloud02Icon, MoonCloudIcon } from '@zen/core/shared/icons';
// Script à injecter dans <head> pour appliquer le thème avant le premier rendu (anti-FOUC).
export const THEME_INIT_SCRIPT = `(function(){try{var t=localStorage.getItem('theme'),d=window.matchMedia('(prefers-color-scheme: dark)').matches;if(t==='dark'||(!t&&d))document.documentElement.classList.add('dark');}catch(e){}})();`;
const THEME_ICONS = {
light: Sun01Icon,
dark: Moon02Icon,
};
export function getStoredTheme() {
const stored = localStorage.getItem('theme');
if (stored === 'light' || stored === 'dark') return stored;
return 'auto';
}
export function applyTheme(theme) {
if (theme === 'dark') {
document.documentElement.classList.add('dark');
localStorage.setItem('theme', 'dark');
} else if (theme === 'light') {
document.documentElement.classList.remove('dark');
localStorage.setItem('theme', 'light');
} else {
localStorage.removeItem('theme');
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
document.documentElement.classList.toggle('dark', prefersDark);
}
}
export function getThemeIcon(theme, systemIsDark) {
if (theme === 'auto') return systemIsDark ? MoonCloudIcon : SunCloud02Icon;
return THEME_ICONS[theme];
}
function getNextTheme(current) {
const systemIsDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
if (current === 'auto') return systemIsDark ? 'light' : 'dark';
if (current === 'dark') return systemIsDark ? 'auto' : 'light';
return systemIsDark ? 'dark' : 'auto';
}
export function useTheme() {
const [theme, setTheme] = useState('auto');
const [systemIsDark, setSystemIsDark] = useState(false);
useEffect(() => {
setTheme(getStoredTheme());
setSystemIsDark(window.matchMedia('(prefers-color-scheme: dark)').matches);
const mq = window.matchMedia('(prefers-color-scheme: dark)');
function onSystemChange(e) {
setSystemIsDark(e.matches);
if (localStorage.getItem('theme')) return;
document.documentElement.classList.toggle('dark', e.matches);
}
mq.addEventListener('change', onSystemChange);
return () => mq.removeEventListener('change', onSystemChange);
}, []);
function toggle() {
const next = getNextTheme(theme);
setTheme(next);
applyTheme(next);
}
return { theme, toggle, systemIsDark };
}
+2 -66
View File
@@ -1,74 +1,10 @@
'use client';
import { useState, useEffect } from 'react';
import { Sun01Icon, Moon02Icon, SunCloud02Icon, MoonCloudIcon } from '@zen/core/shared/icons';
function getNextTheme(current) {
const systemIsDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
if (current === 'auto') return systemIsDark ? 'light' : 'dark';
if (current === 'dark') return systemIsDark ? 'auto' : 'light';
return systemIsDark ? 'dark' : 'auto';
}
function getAutoIcon(systemIsDark) {
return systemIsDark ? MoonCloudIcon : SunCloud02Icon;
}
const THEME_ICONS = {
light: Sun01Icon,
dark: Moon02Icon,
};
function getStoredTheme() {
const stored = localStorage.getItem('theme');
if (stored === 'light' || stored === 'dark') return stored;
return 'auto';
}
function applyTheme(theme) {
if (theme === 'dark') {
document.documentElement.classList.add('dark');
localStorage.setItem('theme', 'dark');
} else if (theme === 'light') {
document.documentElement.classList.remove('dark');
localStorage.setItem('theme', 'light');
} else {
localStorage.removeItem('theme');
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
document.documentElement.classList.toggle('dark', prefersDark);
}
}
function useTheme() {
const [theme, setTheme] = useState('auto');
const [systemIsDark, setSystemIsDark] = useState(false);
useEffect(() => {
setTheme(getStoredTheme());
setSystemIsDark(window.matchMedia('(prefers-color-scheme: dark)').matches);
const mq = window.matchMedia('(prefers-color-scheme: dark)');
function onSystemChange(e) {
setSystemIsDark(e.matches);
if (localStorage.getItem('theme')) return;
document.documentElement.classList.toggle('dark', e.matches);
}
mq.addEventListener('change', onSystemChange);
return () => mq.removeEventListener('change', onSystemChange);
}, []);
function toggle() {
const next = getNextTheme(theme);
setTheme(next);
applyTheme(next);
}
return { theme, toggle, systemIsDark };
}
import { useTheme, getThemeIcon } from '@zen/core/themes';
export default function ThemeToggle() {
const { theme, toggle, systemIsDark } = useTheme();
const Icon = theme === 'auto' ? getAutoIcon(systemIsDark) : THEME_ICONS[theme];
const Icon = getThemeIcon(theme, systemIsDark);
return (
<button