feat(web): design tokens, Tailwind config, base components (TEC-3057)
- 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>
This commit is contained in:
70
apps/web/components/design-system/density-provider.tsx
Normal file
70
apps/web/components/design-system/density-provider.tsx
Normal file
@@ -0,0 +1,70 @@
|
||||
'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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user