From e3b743f87bc326ca42eec4f2c6047f7f4bae27be Mon Sep 17 00:00:00 2001 From: Martin Braquet Date: Mon, 16 Feb 2026 12:24:45 +0100 Subject: [PATCH] Add font preference selector in settings (#28) * Add font preference selector in settings * Consolidate font family config into shared global constant * Revert "Consolidate font family config into shared global constant" This reverts commit 789ddc98e1666c7c0c81f81df54471e6c9b6eafc. * Fix --- web/components/font-picker.tsx | 34 +++++++++++++++++++++++++++++ web/hooks/use-font-preference.ts | 37 ++++++++++++++++++++++++++++++++ web/messages/de.json | 8 +++++-- web/messages/fr.json | 8 +++++-- web/pages/_app.tsx | 2 ++ web/pages/settings.tsx | 4 ++++ web/public/init-theme.js | 12 +++++++++++ 7 files changed, 101 insertions(+), 4 deletions(-) create mode 100644 web/components/font-picker.tsx create mode 100644 web/hooks/use-font-preference.ts diff --git a/web/components/font-picker.tsx b/web/components/font-picker.tsx new file mode 100644 index 0000000..774d2b4 --- /dev/null +++ b/web/components/font-picker.tsx @@ -0,0 +1,34 @@ +'use client' + +import clsx from 'clsx' +import {FontOption, useFontPreference} from 'web/hooks/use-font-preference' +import {useT} from 'web/lib/locale' + +const FONT_OPTIONS: FontOption[] = ['atkinson', 'system-sans', 'classic-serif'] + +const EN_TRANSLATIONS = { + "atkinson": "Atkinson Hyperlegible", + "system-sans": "Sytem Sans-serif", + "classic-serif": "Classic Serif" +} + +export function FontPicker(props: { className?: string } = {}) { + const {className} = props + const {font, setFont} = useFontPreference() + const t = useT() + + return ( + + ) +} diff --git a/web/hooks/use-font-preference.ts b/web/hooks/use-font-preference.ts new file mode 100644 index 0000000..0922b9e --- /dev/null +++ b/web/hooks/use-font-preference.ts @@ -0,0 +1,37 @@ +import {useEffect} from 'react' +import {usePersistentLocalState} from 'web/hooks/use-persistent-local-state' + +export type FontOption = 'atkinson' | 'system-sans' | 'classic-serif' + +const FONT_VARIABLES: Record = { + atkinson: '"Atkinson Hyperlegible Next", Georgia, "Times New Roman", Times, serif', + 'system-sans': '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif', + 'classic-serif': 'Georgia, "Times New Roman", Times, serif', +} + +export const useFontPreference = () => { + const [font, setFontState] = usePersistentLocalState('atkinson', 'font-preference') + + const setFont = (newFont: FontOption) => { + setFontState(newFont) + applyFontPreference(newFont) + } + + return {font, setFont} +} + +export const useFontPreferenceManager = () => { + useEffect(() => { + applyFontPreference(getStoredFontPreference()) + }, []) +} + +export const applyFontPreference = (font: FontOption) => { + const fontFamily = FONT_VARIABLES[font] ?? FONT_VARIABLES.atkinson + document.documentElement.style.setProperty('--font-main', fontFamily) +} + +const getStoredFontPreference = (): FontOption => { + if (typeof window === 'undefined') return 'atkinson' + return JSON.parse(localStorage.getItem('font-preference') ?? 'null') ?? 'atkinson' +} diff --git a/web/messages/de.json b/web/messages/de.json index 9b52f9e..bc3ffee 100644 --- a/web/messages/de.json +++ b/web/messages/de.json @@ -1042,5 +1042,9 @@ "more_options_user.edit_profile": "Profil bearbeiten", "more_options_user.profile_options": "Profiloptionen", "more_options_user.edit_bio": "Bio bearbeiten", - "vote.with_priority": "mit Priorität" -} \ No newline at end of file + "vote.with_priority": "mit Priorität", + "settings.general.font": "Schriftart", + "font.atkinson": "Atkinson Hyperlegible", + "font.system-sans": "System Sans", + "font.classic-serif": "Klassische Serifenschrift" +} diff --git a/web/messages/fr.json b/web/messages/fr.json index 22c8c05..c168ebd 100644 --- a/web/messages/fr.json +++ b/web/messages/fr.json @@ -1042,5 +1042,9 @@ "more_options_user.edit_profile": "Modifier le profil", "more_options_user.profile_options": "Options du profil", "more_options_user.edit_bio": "Options de biographie", - "vote.with_priority": "avec priorité" -} \ No newline at end of file + "vote.with_priority": "avec priorité", + "settings.general.font": "Police", + "font.atkinson": "Atkinson Hyperlegible", + "font.system-sans": "Sans-serif système", + "font.classic-serif": "Serif classique" +} diff --git a/web/pages/_app.tsx b/web/pages/_app.tsx index b72951e..033476c 100644 --- a/web/pages/_app.tsx +++ b/web/pages/_app.tsx @@ -23,6 +23,7 @@ import {getLocale, resetCachedLocale} from "web/lib/locale-cookie" import {I18nContext} from "web/lib/locale" import {HiddenProfilesProvider} from 'web/hooks/use-hidden-profiles' import {updateStatusBar} from "web/hooks/use-theme" +import {useFontPreferenceManager} from "web/hooks/use-font-preference" import {DAYJS_LOCALE_IMPORTS} from "web/lib/dayjs"; import 'web/lib/dayjs' @@ -94,6 +95,7 @@ function MyApp(props: AppProps) { const {Component, pageProps} = props useEffect(printBuildInfo, []) useHasLoaded() + useFontPreferenceManager() const router = useRouter() const [locale, setLocaleState] = useState(getLocale()) diff --git a/web/pages/settings.tsx b/web/pages/settings.tsx index 0aec2da..8906622 100644 --- a/web/pages/settings.tsx +++ b/web/pages/settings.tsx @@ -19,6 +19,7 @@ import {WithPrivateUser} from "web/components/user/with-user"; import {sendPasswordReset} from "web/lib/firebase/password"; import {AboutSettings} from "web/components/about-settings"; import {LanguagePicker} from "web/components/language/language-picker"; +import {FontPicker} from 'web/components/font-picker' import {useT} from "web/lib/locale"; import HiddenProfilesModal from 'web/components/settings/hidden-profiles-modal' import {EmailVerificationButton} from "web/components/email-verification-button"; @@ -119,6 +120,9 @@ const LoadedGeneralSettings = (props: {

{t('settings.general.language', 'Language')}

+

{t('settings.general.font', 'Font')}

+ +

{t('settings.data_privacy.title', 'Data & Privacy')}

diff --git a/web/public/init-theme.js b/web/public/init-theme.js index 50b667d..45dec46 100644 --- a/web/public/init-theme.js +++ b/web/public/init-theme.js @@ -6,4 +6,16 @@ if (theme === 'dark' || (theme === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches)) { document.documentElement.classList.add('dark') } + + const localFontPreference = localStorage.getItem('font-preference') + const fontPreference = localFontPreference ? JSON.parse(localFontPreference) : 'atkinson' + + const fontFamilies = { + atkinson: '"Atkinson Hyperlegible Next", Georgia, "Times New Roman", Times, serif', + 'system-sans': '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif', + 'classic-serif': 'Georgia, "Times New Roman", Times, serif', + } + + document.documentElement.style.setProperty('--font-main', fontFamilies[fontPreference] ?? fontFamilies.atkinson) + }