Offer the three proposed font groups in Themes
The theme menu now carries a fourth choice beside light/dark and the accent: the font pairing. All three pairings are free for commercial use (SIL OFL), have a Vietnamese subset, and are self-hosted — the landing still makes no CDN request. - styles/fonts.css: 22 @font-face blocks for Plus Jakarta Sans, Inter, JetBrains Mono, Fraunces, Be Vietnam Pro, Courier Prime and Space Mono, vietnamese/latin-ext/latin subsets only, under public/assets/fonts. - styles/tokens.css: [data-fonts="studio|editorial|native"] sets --font, --font-heading and --mono. Studio (Plus Jakarta Sans + Inter + JetBrains Mono) is the default. - theme/: FontSetId + FONT_SETS, persisted as rc.fonts, applied as <html data-fonts> next to data-theme and data-accent. - TopBar and the landing nav both get the picker; on the landing the theme tool now opens a small popover (light/dark + font group) instead of toggling on click. - landing.css: --lp-display/--lp-mono now resolve to the chosen group, so the picker retypes the whole page. Syne.woff2 goes with its @font-face.
This commit is contained in:
@@ -1,29 +1,36 @@
|
||||
import { createContext, useCallback, useContext, useEffect, useMemo, useState, type ReactNode } from 'react';
|
||||
import { ACCENTS, applyTheme, readAccent, readTheme, type AccentId, type ThemeMode } from './themes';
|
||||
import { ACCENTS, applyTheme, readAccent, readFonts, readTheme, type AccentId, type FontSetId, type ThemeMode } from './themes';
|
||||
|
||||
interface ThemeValue {
|
||||
mode: ThemeMode;
|
||||
accent: AccentId;
|
||||
fonts: FontSetId;
|
||||
setMode: (mode: ThemeMode) => void;
|
||||
setAccent: (accent: AccentId) => void;
|
||||
setFonts: (fonts: FontSetId) => void;
|
||||
}
|
||||
|
||||
const Ctx = createContext<ThemeValue | null>(null);
|
||||
|
||||
// The tokens live in CSS; this provider only owns the two attributes on <html>
|
||||
// and the persisted choice. No component ever reads a colour from JS.
|
||||
// The tokens live in CSS; this provider only owns the three attributes on
|
||||
// <html> and the persisted choice. No component ever reads a colour from JS.
|
||||
export function ThemeProvider({ children }: { children: ReactNode }) {
|
||||
const [mode, setModeState] = useState<ThemeMode>(readTheme);
|
||||
const [accent, setAccentState] = useState<AccentId>(readAccent);
|
||||
const [fonts, setFontsState] = useState<FontSetId>(readFonts);
|
||||
|
||||
useEffect(() => {
|
||||
applyTheme(mode, accent);
|
||||
}, [mode, accent]);
|
||||
applyTheme(mode, accent, fonts);
|
||||
}, [mode, accent, fonts]);
|
||||
|
||||
const setMode = useCallback((next: ThemeMode) => setModeState(next), []);
|
||||
const setAccent = useCallback((next: AccentId) => setAccentState(next), []);
|
||||
const setFonts = useCallback((next: FontSetId) => setFontsState(next), []);
|
||||
|
||||
const value = useMemo(() => ({ mode, accent, setMode, setAccent }), [mode, accent, setMode, setAccent]);
|
||||
const value = useMemo(
|
||||
() => ({ mode, accent, fonts, setMode, setAccent, setFonts }),
|
||||
[mode, accent, fonts, setMode, setAccent, setFonts]
|
||||
);
|
||||
return <Ctx.Provider value={value}>{children}</Ctx.Provider>;
|
||||
}
|
||||
|
||||
@@ -33,5 +40,5 @@ export function useTheme(): ThemeValue {
|
||||
return value;
|
||||
}
|
||||
|
||||
export { ACCENTS };
|
||||
export type { AccentId, ThemeMode };
|
||||
export { ACCENTS, FONT_SETS } from './themes';
|
||||
export type { AccentId, FontSetId, ThemeMode } from './themes';
|
||||
|
||||
Reference in New Issue
Block a user