refactor: extract theme logic into shared core module
This commit is contained in:
@@ -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 };
|
||||||
|
}
|
||||||
@@ -1,74 +1,10 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useState, useEffect } from 'react';
|
import { useTheme, getThemeIcon } from '@zen/core/themes';
|
||||||
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 };
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function ThemeToggle() {
|
export default function ThemeToggle() {
|
||||||
const { theme, toggle, systemIsDark } = useTheme();
|
const { theme, toggle, systemIsDark } = useTheme();
|
||||||
const Icon = theme === 'auto' ? getAutoIcon(systemIsDark) : THEME_ICONS[theme];
|
const Icon = getThemeIcon(theme, systemIsDark);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
|
|||||||
+2
-1
@@ -22,6 +22,7 @@ export default defineConfig([
|
|||||||
'src/core/email/templates/index.js',
|
'src/core/email/templates/index.js',
|
||||||
'src/core/storage/index.js',
|
'src/core/storage/index.js',
|
||||||
'src/core/toast/index.js',
|
'src/core/toast/index.js',
|
||||||
|
'src/core/themes/index.js',
|
||||||
'src/features/provider/index.js',
|
'src/features/provider/index.js',
|
||||||
'src/shared/components/index.js',
|
'src/shared/components/index.js',
|
||||||
'src/shared/Icons.js',
|
'src/shared/Icons.js',
|
||||||
@@ -35,7 +36,7 @@ export default defineConfig([
|
|||||||
splitting: false,
|
splitting: false,
|
||||||
sourcemap: false,
|
sourcemap: false,
|
||||||
clean: true,
|
clean: true,
|
||||||
external: ['react', 'react-dom', 'next', 'pg', 'dotenv', 'dotenv/config', 'resend', '@react-email/components', 'node-cron', 'readline', 'crypto', 'url', 'fs', 'path', 'net', 'dns', 'tls', '@zen/core/api', '@zen/core/cron', '@zen/core/database', '@zen/core/email', '@zen/core/email/templates', '@zen/core/storage', '@zen/core/toast', '@zen/core/features/auth', '@zen/core/features/auth/actions', '@zen/core/features/auth/components', '@zen/core/shared/components', '@zen/core/shared/icons', '@zen/core/shared/logger', '@zen/core/shared/config', '@zen/core/shared/rate-limit', '@aws-sdk/client-s3', '@aws-sdk/s3-request-presigner'],
|
external: ['react', 'react-dom', 'next', 'pg', 'dotenv', 'dotenv/config', 'resend', '@react-email/components', 'node-cron', 'readline', 'crypto', 'url', 'fs', 'path', 'net', 'dns', 'tls', '@zen/core/api', '@zen/core/cron', '@zen/core/database', '@zen/core/email', '@zen/core/email/templates', '@zen/core/storage', '@zen/core/toast', '@zen/core/features/auth', '@zen/core/features/auth/actions', '@zen/core/features/auth/components', '@zen/core/shared/components', '@zen/core/shared/icons', '@zen/core/shared/logger', '@zen/core/shared/config', '@zen/core/shared/rate-limit', '@zen/core/themes', '@aws-sdk/client-s3', '@aws-sdk/s3-request-presigner'],
|
||||||
noExternal: [],
|
noExternal: [],
|
||||||
bundle: true,
|
bundle: true,
|
||||||
banner: {
|
banner: {
|
||||||
|
|||||||
Reference in New Issue
Block a user