Files
sencho/frontend/src/hooks/use-density.ts
T
Anso ad90bd9404 feat(settings): add comfortable/compact density toggle (#683)
Adds a per-device appearance preference that compresses row, cell, and
tile padding across dashboard, settings, audit log, and every shared
table without changing typography or layout structure.

Density is stored in localStorage and applied to the document body as a
class that swaps a set of CSS variables. Components opt in by consuming
the tokens, so the global table primitive scales all seven consumers at
once.

A new Appearance section in the Identity group lets users pick between
Comfortable and Compact via a Combobox, with a helper line that
reflects the current choice.
2026-04-18 18:48:58 -04:00

62 lines
1.9 KiB
TypeScript

import { useCallback, useEffect, useState } from 'react';
export type Density = 'comfortable' | 'compact';
const STORAGE_KEY = 'sencho.appearance.density';
const DEFAULT_DENSITY: Density = 'comfortable';
function isDensity(value: unknown): value is Density {
return value === 'comfortable' || value === 'compact';
}
function readStoredDensity(): Density {
if (typeof window === 'undefined') return DEFAULT_DENSITY;
try {
const raw = window.localStorage.getItem(STORAGE_KEY);
return isDensity(raw) ? raw : DEFAULT_DENSITY;
} catch {
return DEFAULT_DENSITY;
}
}
function applyDensityClass(density: Density) {
if (typeof document === 'undefined') return;
const body = document.body;
if (!body) return;
body.classList.toggle('density-compact', density === 'compact');
}
export function initializeDensity() {
applyDensityClass(readStoredDensity());
}
export function useDensity(): [Density, (next: Density) => void] {
const [density, setDensityState] = useState<Density>(readStoredDensity);
useEffect(() => {
applyDensityClass(density);
try {
if (window.localStorage.getItem(STORAGE_KEY) !== density) {
window.localStorage.setItem(STORAGE_KEY, density);
}
} catch {
// ignore; localStorage may be unavailable (private mode, quota)
}
}, [density]);
useEffect(() => {
function onStorage(event: StorageEvent) {
if (event.key !== STORAGE_KEY) return;
if (isDensity(event.newValue)) setDensityState(event.newValue);
}
window.addEventListener('storage', onStorage);
return () => window.removeEventListener('storage', onStorage);
}, []);
const setDensity = useCallback((next: Density) => {
setDensityState(next);
}, []);
return [density, setDensity];
}