Files
hapi/web/src/hooks/useFontScale.ts
T

117 lines
2.9 KiB
TypeScript

import { useCallback, useEffect, useLayoutEffect, useState } from 'react'
export type FontScale = 0.8 | 0.9 | 1 | 1.1 | 1.2
export function getFontScaleOptions(): ReadonlyArray<{ value: FontScale; label: string }> {
return [
{ value: 0.8, label: '80%' },
{ value: 0.9, label: '90%' },
{ value: 1, label: '100%' },
{ value: 1.1, label: '110%' },
{ value: 1.2, label: '120%' },
]
}
function getFontScaleStorageKey(): string {
return 'hapi-font-scale'
}
function isBrowser(): boolean {
return typeof window !== 'undefined' && typeof document !== 'undefined'
}
const useIsomorphicLayoutEffect = isBrowser() ? useLayoutEffect : useEffect
function safeGetItem(key: string): string | null {
if (!isBrowser()) {
return null
}
try {
return localStorage.getItem(key)
} catch {
return null
}
}
function safeSetItem(key: string, value: string): void {
if (!isBrowser()) {
return
}
try {
localStorage.setItem(key, value)
} catch {
// Ignore storage errors
}
}
function safeRemoveItem(key: string): void {
if (!isBrowser()) {
return
}
try {
localStorage.removeItem(key)
} catch {
// Ignore storage errors
}
}
function parseFontScale(raw: string | null): FontScale {
const value = Number(raw)
if (value === 0.8 || value === 0.9 || value === 1 || value === 1.1 || value === 1.2) {
return value
}
return 1
}
function applyFontScale(scale: FontScale): void {
if (!isBrowser()) {
return
}
document.documentElement.style.setProperty('--app-font-scale', String(scale))
}
function getInitialFontScale(): FontScale {
return parseFontScale(safeGetItem(getFontScaleStorageKey()))
}
export function initializeFontScale(): void {
applyFontScale(getInitialFontScale())
}
export function useFontScale(): { fontScale: FontScale; setFontScale: (scale: FontScale) => void } {
const [fontScale, setFontScaleState] = useState<FontScale>(getInitialFontScale)
useIsomorphicLayoutEffect(() => {
applyFontScale(fontScale)
}, [fontScale])
useEffect(() => {
if (!isBrowser()) {
return
}
const onStorage = (event: StorageEvent) => {
if (event.key !== getFontScaleStorageKey()) {
return
}
const nextScale = parseFontScale(event.newValue)
setFontScaleState(nextScale)
}
window.addEventListener('storage', onStorage)
return () => window.removeEventListener('storage', onStorage)
}, [])
const setFontScale = useCallback((scale: FontScale) => {
setFontScaleState(scale)
if (scale === 1) {
safeRemoveItem(getFontScaleStorageKey())
} else {
safeSetItem(getFontScaleStorageKey(), String(scale))
}
}, [])
return { fontScale, setFontScale }
}