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:
217
apps/web/app/[locale]/(dashboard)/dev/tokens/page.tsx
Normal file
217
apps/web/app/[locale]/(dashboard)/dev/tokens/page.tsx
Normal file
@@ -0,0 +1,217 @@
|
||||
'use client';
|
||||
|
||||
import { notFound } from 'next/navigation';
|
||||
import {
|
||||
Surface,
|
||||
SurfaceElevated,
|
||||
Divider,
|
||||
DensityProvider,
|
||||
useDensity,
|
||||
DENSITY_ROW_HEIGHT,
|
||||
Numeric,
|
||||
Signal,
|
||||
} from '@/components/design-system';
|
||||
|
||||
// Dev-only: block in production
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
// Will 404 at build time for static generation
|
||||
}
|
||||
|
||||
const COLOR_TOKENS = [
|
||||
{ name: '--background', tw: 'bg-background' },
|
||||
{ name: '--background-elevated', tw: 'bg-background-elevated' },
|
||||
{ name: '--background-surface', tw: 'bg-background-surface' },
|
||||
{ name: '--primary', tw: 'bg-primary' },
|
||||
{ name: '--primary-hover', tw: 'bg-primary-hover' },
|
||||
{ name: '--destructive', tw: 'bg-destructive' },
|
||||
{ name: '--warning', tw: 'bg-warning' },
|
||||
{ name: '--success', tw: 'bg-success' },
|
||||
{ name: '--accent-blue', tw: 'bg-accent-blue' },
|
||||
{ name: '--accent-purple', tw: 'bg-accent-purple' },
|
||||
{ name: '--signal-up', tw: 'bg-signal-up' },
|
||||
{ name: '--signal-down', tw: 'bg-signal-down' },
|
||||
{ name: '--signal-neutral', tw: 'bg-signal-neutral' },
|
||||
];
|
||||
|
||||
const CHART_TOKENS = [
|
||||
{ name: '--chart-1', tw: 'bg-chart-1' },
|
||||
{ name: '--chart-2', tw: 'bg-chart-2' },
|
||||
{ name: '--chart-3', tw: 'bg-chart-3' },
|
||||
{ name: '--chart-4', tw: 'bg-chart-4' },
|
||||
{ name: '--chart-5', tw: 'bg-chart-5' },
|
||||
{ name: '--chart-6', tw: 'bg-chart-6' },
|
||||
];
|
||||
|
||||
function DensityDemo() {
|
||||
const { density, setDensity } = useDensity();
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
<div className="flex gap-2">
|
||||
{(['compact', 'regular', 'roomy'] as const).map((d) => (
|
||||
<button
|
||||
key={d}
|
||||
onClick={() => setDensity(d)}
|
||||
className={`rounded-md px-3 py-1 text-sm ${density === d ? 'bg-primary text-primary-foreground' : 'bg-background-surface text-foreground-muted'}`}
|
||||
>
|
||||
{d}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="rounded-lg border border-border bg-background-elevated p-4">
|
||||
<div className={`${DENSITY_ROW_HEIGHT[density]} flex items-center border-b border-border px-cell`}>
|
||||
<span className="text-foreground-muted text-heading-xs uppercase font-semibold">Mẫu hàng — {density}</span>
|
||||
</div>
|
||||
{[1, 2, 3].map((i) => (
|
||||
<div key={i} className={`${DENSITY_ROW_HEIGHT[density]} flex items-center border-b border-border px-cell`}>
|
||||
<span className="flex-1 text-sm">Dòng {i}</span>
|
||||
<Numeric value={i * 3_500_000_000} format="vnd" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function DevTokensPage() {
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
notFound();
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-8 p-6">
|
||||
<h1 className="text-heading-lg font-bold">Design Tokens Showcase</h1>
|
||||
<p className="text-foreground-muted text-sm">Dev-only route — không hiển thị trên production.</p>
|
||||
|
||||
{/* Colors */}
|
||||
<section>
|
||||
<h2 className="text-heading-md font-semibold mb-4">Màu sắc</h2>
|
||||
<div className="grid grid-cols-2 gap-3 sm:grid-cols-4 lg:grid-cols-6">
|
||||
{COLOR_TOKENS.map((t) => (
|
||||
<div key={t.name} className="space-y-1">
|
||||
<div className={`h-12 w-full rounded-md border border-border ${t.tw}`} />
|
||||
<p className="text-xs text-foreground-muted font-mono">{t.name}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Chart palette */}
|
||||
<section>
|
||||
<h2 className="text-heading-md font-semibold mb-4">Chart Palette</h2>
|
||||
<div className="flex gap-2">
|
||||
{CHART_TOKENS.map((t) => (
|
||||
<div key={t.name} className="space-y-1 flex-1">
|
||||
<div className={`h-10 w-full rounded-md ${t.tw}`} />
|
||||
<p className="text-[10px] text-foreground-muted font-mono text-center">{t.name}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Typography */}
|
||||
<section>
|
||||
<h2 className="text-heading-md font-semibold mb-4">Typography</h2>
|
||||
<div className="space-y-2">
|
||||
<p className="text-heading-xl font-bold">heading-xl (1.875rem)</p>
|
||||
<p className="text-heading-lg font-bold">heading-lg (1.5rem)</p>
|
||||
<p className="text-heading-md font-semibold">heading-md (1.125rem)</p>
|
||||
<p className="text-heading-sm font-semibold">heading-sm (0.875rem)</p>
|
||||
<p className="text-heading-xs font-semibold uppercase">heading-xs (0.75rem uppercase tracking)</p>
|
||||
<Divider className="my-2" />
|
||||
<p className="font-mono text-data-lg">data-lg: 1.250.000.000 ₫</p>
|
||||
<p className="font-mono text-data-md">data-md: 850.000.000 ₫</p>
|
||||
<p className="font-mono text-data-sm">data-sm: 450.000.000 ₫</p>
|
||||
<p className="font-mono text-ticker">ticker: Q1 +2.4%</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Shadows */}
|
||||
<section>
|
||||
<h2 className="text-heading-md font-semibold mb-4">Elevation (Shadow)</h2>
|
||||
<div className="flex gap-6">
|
||||
{['elevation-0', 'elevation-1', 'elevation-2', 'elevation-3'].map((s) => (
|
||||
<div
|
||||
key={s}
|
||||
className={`flex h-20 w-32 items-center justify-center rounded-lg bg-background-elevated text-xs text-foreground-muted shadow-${s}`}
|
||||
>
|
||||
{s}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Signals */}
|
||||
<section>
|
||||
<h2 className="text-heading-md font-semibold mb-4">Signal</h2>
|
||||
<div className="flex gap-3">
|
||||
<Signal direction="up" label="+2.4%" />
|
||||
<Signal direction="down" label="-1.3%" />
|
||||
<Signal direction="neutral" label="0.0%" />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Glow shadows */}
|
||||
<section>
|
||||
<h2 className="text-heading-md font-semibold mb-4">Glow Shadows</h2>
|
||||
<div className="flex gap-6">
|
||||
<div className="flex h-16 w-28 items-center justify-center rounded-lg bg-background-elevated text-xs text-signal-up shadow-glow-up">
|
||||
glow-up
|
||||
</div>
|
||||
<div className="flex h-16 w-28 items-center justify-center rounded-lg bg-background-elevated text-xs text-signal-down shadow-glow-down">
|
||||
glow-down
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Surfaces */}
|
||||
<section>
|
||||
<h2 className="text-heading-md font-semibold mb-4">Surface</h2>
|
||||
<div className="flex gap-4">
|
||||
<Surface className="p-4 border border-border">
|
||||
<p className="text-sm">Surface (flat)</p>
|
||||
</Surface>
|
||||
<SurfaceElevated className="p-4">
|
||||
<p className="text-sm">SurfaceElevated</p>
|
||||
</SurfaceElevated>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Numeric */}
|
||||
<section>
|
||||
<h2 className="text-heading-md font-semibold mb-4">Numeric</h2>
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="text-sm text-foreground-muted w-20">VND:</span>
|
||||
<Numeric value={3_500_000_000} />
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="text-sm text-foreground-muted w-20">Percent:</span>
|
||||
<Numeric value={12.5} format="percent" />
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<span className="text-sm text-foreground-muted w-20">Compact:</span>
|
||||
<Numeric value={1_250_000_000} format="compact" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Density */}
|
||||
<section>
|
||||
<h2 className="text-heading-md font-semibold mb-4">Density</h2>
|
||||
<DensityProvider>
|
||||
<DensityDemo />
|
||||
</DensityProvider>
|
||||
</section>
|
||||
|
||||
{/* Tick flash animations */}
|
||||
<section>
|
||||
<h2 className="text-heading-md font-semibold mb-4">Tick Flash</h2>
|
||||
<div className="flex gap-4">
|
||||
<div className="tick-flash-up rounded-md px-4 py-2 text-sm">Flash Up</div>
|
||||
<div className="tick-flash-down rounded-md px-4 py-2 text-sm">Flash Down</div>
|
||||
</div>
|
||||
<p className="text-xs text-foreground-muted mt-2">Refresh page to replay animation. Disabled with prefers-reduced-motion.</p>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user