web: move the section jumps to their own strip and grow the Theme menu
The six section links leave the top bar for a thin shelf under it, each a bordered pill at the page's normal text size, so the bar itself stays a row of actions and the jumps still read as buttons. The Theme menu gains the workspace's colour groups — the landing palette now takes its hue from the accent tokens — an Auto mode that follows the OS scheme live, and a dropdown in place of the three font chips.
This commit is contained in:
@@ -21,6 +21,12 @@ export function ThemeProvider({ children }: { children: ReactNode }) {
|
||||
|
||||
useEffect(() => {
|
||||
applyTheme(mode, accent, fonts);
|
||||
// Auto has to follow the OS while the page is open, not just at mount.
|
||||
if (mode !== 'auto') return;
|
||||
const media = window.matchMedia('(prefers-color-scheme: light)');
|
||||
const onChange = () => applyTheme(mode, accent, fonts);
|
||||
media.addEventListener('change', onChange);
|
||||
return () => media.removeEventListener('change', onChange);
|
||||
}, [mode, accent, fonts]);
|
||||
|
||||
const setMode = useCallback((next: ThemeMode) => setModeState(next), []);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Theme data only — the CSS in styles/tokens.css owns the values. Adding an
|
||||
// accent group is one entry here plus one 1-line block in the stylesheet.
|
||||
|
||||
export type ThemeMode = 'light' | 'dark';
|
||||
export type ThemeMode = 'light' | 'dark' | 'auto';
|
||||
export type AccentId = 'amber' | 'blue' | 'emerald' | 'rose' | 'violet';
|
||||
export type FontSetId = 'studio' | 'editorial' | 'native';
|
||||
|
||||
@@ -23,7 +23,8 @@ export const FONT_SETS: { id: FontSetId; label: string; note: string }[] = [
|
||||
{ id: 'native', label: 'Thuần Việt', note: 'Be Vietnam Pro · Space Mono' },
|
||||
];
|
||||
|
||||
export const THEME_COLOR: Record<ThemeMode, string> = {
|
||||
// Keyed by the resolved mode only: auto never paints a browser chrome colour.
|
||||
export const THEME_COLOR: Record<'light' | 'dark', string> = {
|
||||
light: '#ffffff',
|
||||
dark: '#0a0a0a',
|
||||
};
|
||||
@@ -32,9 +33,17 @@ const KEY_THEME = 'rc.theme';
|
||||
const KEY_ACCENT = 'rc.accent';
|
||||
const KEY_FONTS = 'rc.fonts';
|
||||
|
||||
const MODES: ThemeMode[] = ['light', 'dark', 'auto'];
|
||||
|
||||
export function readTheme(): ThemeMode {
|
||||
const stored = localStorage.getItem(KEY_THEME);
|
||||
if (stored === 'light' || stored === 'dark') return stored;
|
||||
return MODES.includes(stored as ThemeMode) ? (stored as ThemeMode) : 'auto';
|
||||
}
|
||||
|
||||
// Auto follows the OS setting; everything downstream — the CSS, the persisted
|
||||
// value — only ever sees light or dark.
|
||||
export function resolveMode(mode: ThemeMode): 'light' | 'dark' {
|
||||
if (mode !== 'auto') return mode;
|
||||
return window.matchMedia?.('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
|
||||
}
|
||||
|
||||
@@ -50,10 +59,10 @@ export function readFonts(): FontSetId {
|
||||
|
||||
export function applyTheme(mode: ThemeMode, accent: AccentId, fonts: FontSetId): void {
|
||||
const root = document.documentElement;
|
||||
root.dataset.theme = mode;
|
||||
root.dataset.theme = resolveMode(mode);
|
||||
root.dataset.accent = accent;
|
||||
root.dataset.fonts = fonts;
|
||||
document.querySelector('meta[name="theme-color"]')?.setAttribute('content', THEME_COLOR[mode]);
|
||||
document.querySelector('meta[name="theme-color"]')?.setAttribute('content', THEME_COLOR[resolveMode(mode)]);
|
||||
localStorage.setItem(KEY_THEME, mode);
|
||||
localStorage.setItem(KEY_ACCENT, accent);
|
||||
localStorage.setItem(KEY_FONTS, fonts);
|
||||
|
||||
Reference in New Issue
Block a user