- Add chart palette, motion, and z-index CSS vars to globals.css - Replace custom theme-provider with next-themes (dark default) - Extend tailwind.config.ts with heading fonts, spacing (row-compact, row-roomy, sidebar), chart colors, elevation shadows, glow shadows, transition timing, pill border-radius, z-index scale - Update tick-flash animations to match design token spec (480ms) - Add prefers-reduced-motion support for all animations - Create base design-system components: Surface, SurfaceElevated, Divider, DensityProvider/useDensity, Numeric (VND/percent/compact formatting), Signal (up/down/neutral pill) - Add dev-only /dev/tokens showcase route (404 in production) - Update theme-provider tests to match next-themes integration Co-Authored-By: Paperclip <noreply@paperclip.ing>
71 lines
1.8 KiB
TypeScript
71 lines
1.8 KiB
TypeScript
'use client';
|
|
|
|
import { createContext, useCallback, useContext, useEffect, useState } from 'react';
|
|
|
|
export type DensityMode = 'compact' | 'regular' | 'roomy';
|
|
|
|
interface DensityContextValue {
|
|
density: DensityMode;
|
|
setDensity: (mode: DensityMode) => void;
|
|
}
|
|
|
|
const STORAGE_KEY = 'goodgo.density';
|
|
|
|
const DensityContext = createContext<DensityContextValue>({
|
|
density: 'regular',
|
|
setDensity: () => {},
|
|
});
|
|
|
|
export function useDensity() {
|
|
return useContext(DensityContext);
|
|
}
|
|
|
|
/** Row height in Tailwind spacing tokens per density mode. */
|
|
export const DENSITY_ROW_HEIGHT: Record<DensityMode, string> = {
|
|
compact: 'h-row-compact', // 32px
|
|
regular: 'h-row', // 36px
|
|
roomy: 'h-row-roomy', // 44px
|
|
};
|
|
|
|
/** Cell padding classes per density mode. */
|
|
export const DENSITY_CELL_PADDING: Record<DensityMode, string> = {
|
|
compact: 'px-2 py-1', // 4px 8px
|
|
regular: 'px-2.5 py-1.5', // 6px 10px
|
|
roomy: 'px-3 py-2.5', // 10px 12px
|
|
};
|
|
|
|
/** Data font size per density mode. */
|
|
export const DENSITY_DATA_FONT: Record<DensityMode, string> = {
|
|
compact: 'text-data-sm',
|
|
regular: 'text-data-md',
|
|
roomy: 'text-data-md',
|
|
};
|
|
|
|
export function DensityProvider({
|
|
defaultDensity = 'regular',
|
|
children,
|
|
}: {
|
|
defaultDensity?: DensityMode;
|
|
children: React.ReactNode;
|
|
}) {
|
|
const [density, setDensityState] = useState<DensityMode>(defaultDensity);
|
|
|
|
useEffect(() => {
|
|
const stored = localStorage.getItem(STORAGE_KEY) as DensityMode | null;
|
|
if (stored === 'compact' || stored === 'regular' || stored === 'roomy') {
|
|
setDensityState(stored);
|
|
}
|
|
}, []);
|
|
|
|
const setDensity = useCallback((mode: DensityMode) => {
|
|
setDensityState(mode);
|
|
localStorage.setItem(STORAGE_KEY, mode);
|
|
}, []);
|
|
|
|
return (
|
|
<DensityContext.Provider value={{ density, setDensity }}>
|
|
{children}
|
|
</DensityContext.Provider>
|
|
);
|
|
}
|