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 789ddc98e1.

* Fix
This commit is contained in:
Martin Braquet
2026-02-16 12:24:45 +01:00
committed by GitHub
parent 54e1106237
commit e3b743f87b
7 changed files with 101 additions and 4 deletions

View File

@@ -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 (
<select
id="font-picker"
value={font}
onChange={(e) => setFont(e.target.value as FontOption)}
className={clsx('rounded-md border border-gray-300 px-2 py-1 text-sm bg-canvas-50', className)}
>
{FONT_OPTIONS.map((option) => (
<option key={option} value={option}>
{t(`font.${option}`, EN_TRANSLATIONS[option] ?? option)}
</option>
))}
</select>
)
}

View File

@@ -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<FontOption, string> = {
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<FontOption>('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'
}

View File

@@ -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"
}
"vote.with_priority": "mit Priorität",
"settings.general.font": "Schriftart",
"font.atkinson": "Atkinson Hyperlegible",
"font.system-sans": "System Sans",
"font.classic-serif": "Klassische Serifenschrift"
}

View File

@@ -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é"
}
"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"
}

View File

@@ -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<PageProps>) {
const {Component, pageProps} = props
useEffect(printBuildInfo, [])
useHasLoaded()
useFontPreferenceManager()
const router = useRouter()
const [locale, setLocaleState] = useState(getLocale())

View File

@@ -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: {
<h3>{t('settings.general.language', 'Language')}</h3>
<LanguagePicker className={'w-fit min-w-[120px]'}/>
<h3>{t('settings.general.font', 'Font')}</h3>
<FontPicker className={'w-fit min-w-[180px]'}/>
<h3>{t('settings.data_privacy.title', 'Data & Privacy')}</h3>
<DataPrivacySettings/>

View File

@@ -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)
}