diff --git a/frontend/AGENTS.md b/frontend/AGENTS.md index 8e23a94..2843206 100644 --- a/frontend/AGENTS.md +++ b/frontend/AGENTS.md @@ -81,6 +81,7 @@ frontend/src/ │ ├── contactMerge.ts # Merge WS contact updates into list │ ├── localLabel.ts # Local label (text + color) in localStorage │ ├── radioPresets.ts # LoRa radio preset configurations +│ ├── fontScale.ts # Browser-local relative font scale persistence/application │ └── theme.ts # Theme switching helpers ├── components/ │ ├── StatusBar.tsx @@ -110,7 +111,7 @@ frontend/src/ │ ├── settings/ │ │ ├── settingsConstants.ts # Settings section type, ordering, labels │ │ ├── SettingsRadioSection.tsx # Name, keys, advert interval, max contacts, radio preset, freq/bw/sf/cr, txPower, lat/lon, reboot, mesh discovery -│ │ ├── SettingsLocalSection.tsx # Browser-local settings: theme, local label, reopen last conversation +│ │ ├── SettingsLocalSection.tsx # Browser-local settings: theme, relative font scale, local label, reopen last conversation │ │ ├── SettingsFanoutSection.tsx # Fanout integrations: MQTT, bots, config CRUD │ │ ├── SettingsDatabaseSection.tsx # DB size, cleanup, auto-decrypt, local label │ │ ├── SettingsStatisticsSection.tsx # Read-only mesh network stats diff --git a/frontend/src/components/settings/SettingsLocalSection.tsx b/frontend/src/components/settings/SettingsLocalSection.tsx index 5b08952..2a7d6d8 100644 --- a/frontend/src/components/settings/SettingsLocalSection.tsx +++ b/frontend/src/components/settings/SettingsLocalSection.tsx @@ -17,6 +17,14 @@ import { setSavedDistanceUnit, } from '../../utils/distanceUnits'; import { useDistanceUnit } from '../../contexts/DistanceUnitContext'; +import { + DEFAULT_FONT_SCALE, + FONT_SCALE_SLIDER_STEP, + MAX_FONT_SCALE, + MIN_FONT_SCALE, + getSavedFontScale, + setSavedFontScale, +} from '../../utils/fontScale'; export function SettingsLocalSection({ onLocalLabelChange, @@ -31,6 +39,29 @@ export function SettingsLocalSection({ ); const [localLabelText, setLocalLabelText] = useState(() => getLocalLabel().text); const [localLabelColor, setLocalLabelColor] = useState(() => getLocalLabel().color); + const [fontScale, setFontScale] = useState(getSavedFontScale); + const [fontScaleSlider, setFontScaleSlider] = useState(getSavedFontScale); + const [fontScaleInput, setFontScaleInput] = useState(() => String(getSavedFontScale())); + + const commitFontScale = (nextScale: number) => { + const normalized = setSavedFontScale(nextScale); + setFontScale(normalized); + setFontScaleSlider(normalized); + setFontScaleInput(String(normalized)); + }; + + const restoreFontScaleInput = () => { + setFontScaleInput(String(fontScale)); + }; + + const handleSliderChange = (nextScale: number) => { + setFontScaleSlider(nextScale); + setFontScaleInput(String(nextScale)); + }; + + const handleSliderCommit = (nextScale: number) => { + commitFontScale(nextScale); + }; const handleToggleReopenLastConversation = (enabled: boolean) => { setReopenLastConversation(enabled); @@ -89,6 +120,85 @@ export function SettingsLocalSection({ +
+ +
+ handleSliderChange(Number(event.target.value))} + onMouseUp={(event) => handleSliderCommit(Number(event.currentTarget.value))} + onTouchEnd={(event) => handleSliderCommit(Number(event.currentTarget.value))} + onKeyUp={(event) => handleSliderCommit(Number(event.currentTarget.value))} + onBlur={(event) => handleSliderCommit(Number(event.currentTarget.value))} + aria-label="Relative font size slider" + className="w-full accent-primary sm:flex-1" + /> +
+ { + const nextValue = event.target.value; + setFontScaleInput(nextValue); + + if (nextValue === '') { + return; + } + + if (event.target.validity.valid && Number.isFinite(event.target.valueAsNumber)) { + commitFontScale(event.target.valueAsNumber); + } + }} + onBlur={() => { + const parsed = Number.parseFloat(fontScaleInput); + if (!Number.isFinite(parsed)) { + restoreFontScaleInput(); + return; + } + commitFontScale(parsed); + }} + onKeyDown={(event) => { + if (event.key !== 'Enter') { + return; + } + event.preventDefault(); + const parsed = Number.parseFloat(fontScaleInput); + if (!Number.isFinite(parsed)) { + restoreFontScaleInput(); + return; + } + commitFontScale(parsed); + }} + aria-label="Relative font size percentage" + /> + % +
+ +
+

+ Scales the app's typography for this browser only. The slider moves in 5% steps; + the number field accepts any value from 25% to 400%. +

+
+ + +