feat(design-system): page /design-system dev-only — palette live + composants (Sprint 0.5 étape 7)
This commit is contained in:
parent
9a4e22b533
commit
7dfd0df6b3
2 changed files with 263 additions and 0 deletions
|
|
@ -1,9 +1,24 @@
|
|||
import React, { Suspense } from 'react'
|
||||
import { Routes, Route } from 'react-router-dom'
|
||||
|
||||
const DesignSystemPage = import.meta.env.DEV
|
||||
? React.lazy(() => import('@/features/design-system/DesignSystemPage'))
|
||||
: () => null
|
||||
|
||||
export function AppRouter() {
|
||||
return (
|
||||
<Routes>
|
||||
<Route path="/" element={<ScaffoldPlaceholder />} />
|
||||
{import.meta.env.DEV && (
|
||||
<Route
|
||||
path="/design-system"
|
||||
element={
|
||||
<Suspense fallback={<div className="p-6 text-ink-4">Loading…</div>}>
|
||||
<DesignSystemPage />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</Routes>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
248
src/features/design-system/DesignSystemPage.tsx
Normal file
248
src/features/design-system/DesignSystemPage.tsx
Normal file
|
|
@ -0,0 +1,248 @@
|
|||
import React, { useState } from 'react'
|
||||
import { useTheme } from '@/shared/hooks/useTheme'
|
||||
import { Button } from '@/shared/components/ui/button'
|
||||
import { Badge } from '@/shared/components/ui/badge'
|
||||
import { Input } from '@/shared/components/ui/input'
|
||||
import { Label } from '@/shared/components/ui/label'
|
||||
import { Separator } from '@/shared/components/ui/separator'
|
||||
import { Progress } from '@/shared/components/ui/progress'
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
AvatarGroup,
|
||||
AvatarGroupCount,
|
||||
} from '@/shared/components/ui/avatar'
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from '@/shared/components/ui/dialog'
|
||||
|
||||
// ─── palette data ────────────────────────────────────────────────────────────
|
||||
|
||||
const PALETTE: { token: string; var: string; light: string; dark: string }[] = [
|
||||
{ token: 'canvas', var: '--color-canvas', light: '#EEF2F8', dark: '#0D1220' },
|
||||
{ token: 'canvas-2', var: '--color-canvas-2', light: '#E6EBF4', dark: '#121A2D' },
|
||||
{ token: 'surface', var: '--color-surface', light: '#FFFFFF', dark: '#182238' },
|
||||
{ token: 'surface-hover',var: '--color-surface-hover',light: '#F8FAFD', dark: '#1E2A42' },
|
||||
{ token: 'line', var: '--color-line', light: '#DDE3ED', dark: '#27324B' },
|
||||
{ token: 'line-strong', var: '--color-line-strong', light: '#C7D0E0', dark: '#364363' },
|
||||
{ token: 'ink-1', var: '--color-ink-1', light: '#0F172A', dark: '#F1F4FA' },
|
||||
{ token: 'ink-2', var: '--color-ink-2', light: '#1E293B', dark: '#DDE3EF' },
|
||||
{ token: 'ink-3', var: '--color-ink-3', light: '#475569', dark: '#A8B2C7' },
|
||||
{ token: 'ink-4', var: '--color-ink-4', light: '#64748B', dark: '#7A8499' },
|
||||
{ token: 'ink-5', var: '--color-ink-5', light: '#94A3B8', dark: '#525C73' },
|
||||
{ token: 'expria', var: '--color-expria', light: '#1B4FD8', dark: '#5B7FFF' },
|
||||
{ token: 'expria-hover', var: '--color-expria-hover', light: '#1741B8', dark: '#6F8EFF' },
|
||||
{ token: 'expria-50', var: '--color-expria-50', light: '#EEF3FF', dark: 'rgba(91,127,255,.12)' },
|
||||
{ token: 'expria-100', var: '--color-expria-100', light: '#DCE6FF', dark: '—' },
|
||||
{ token: 'expria-200', var: '--color-expria-200', light: '#B8CDFF', dark: '—' },
|
||||
{ token: 'deep', var: '--color-deep', light: '#0B1F5C', dark: '#060B1A' },
|
||||
{ token: 'success', var: '--color-success', light: '#0E9F6E', dark: '#3DD68C' },
|
||||
{ token: 'success-bg', var: '--color-success-bg', light: '#E6F6F0', dark: 'rgba(61,214,140,.12)' },
|
||||
{ token: 'warning', var: '--color-warning', light: '#C77A00', dark: '#F5B849' },
|
||||
{ token: 'warning-bg', var: '--color-warning-bg', light: '#FEF3E2', dark: 'rgba(245,184,73,.12)' },
|
||||
{ token: 'danger', var: '--color-danger', light: '#C53030', dark: '#F06B6B' },
|
||||
{ token: 'danger-bg', var: '--color-danger-bg', light: '#FDECEC', dark: 'rgba(240,107,107,.12)' },
|
||||
]
|
||||
|
||||
// ─── section wrapper ─────────────────────────────────────────────────────────
|
||||
|
||||
function Section({ title, children }: { title: string; children: React.ReactNode }) {
|
||||
return (
|
||||
<section className="space-y-4">
|
||||
<h2 className="text-base font-semibold text-ink-3 uppercase tracking-wider">{title}</h2>
|
||||
<Separator />
|
||||
{children}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
// ─── main page ───────────────────────────────────────────────────────────────
|
||||
|
||||
export default function DesignSystemPage() {
|
||||
const { theme, setTheme } = useTheme()
|
||||
const [dialogOpen, setDialogOpen] = useState(false)
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-canvas px-6 py-10 space-y-14">
|
||||
|
||||
{/* ── header ── */}
|
||||
<header className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-ink-1">Design System</h1>
|
||||
<p className="text-sm text-ink-4 mt-0.5">Expria — Direction H palette · Sprint 0.5</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
|
||||
>
|
||||
{theme === 'dark' ? 'Light mode' : 'Dark mode'}
|
||||
</Button>
|
||||
</header>
|
||||
|
||||
{/* ── palette ── */}
|
||||
<Section title="Palette">
|
||||
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-3">
|
||||
{PALETTE.map(({ token, var: cssVar, light, dark }) => (
|
||||
<div key={token} className="flex flex-col gap-1.5">
|
||||
<div
|
||||
className="h-12 w-full rounded-md border border-line shadow-sm"
|
||||
style={{ background: `var(${cssVar})` }}
|
||||
/>
|
||||
<div className="space-y-0.5">
|
||||
<p className="text-xs font-mono font-medium text-ink-2">{token}</p>
|
||||
<p className="text-xs font-mono text-ink-4 leading-tight">
|
||||
☀ {light}
|
||||
</p>
|
||||
<p className="text-xs font-mono text-ink-4 leading-tight">
|
||||
☾ {dark}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
{/* ── typography ── */}
|
||||
<Section title="Typography">
|
||||
<div className="space-y-3 bg-surface rounded-lg p-6 border border-line">
|
||||
<p className="text-4xl font-bold text-ink-1">Display / 36px Bold</p>
|
||||
<p className="text-2xl font-semibold text-ink-1">Heading 1 / 24px Semibold</p>
|
||||
<p className="text-xl font-semibold text-ink-1">Heading 2 / 20px Semibold</p>
|
||||
<p className="text-lg font-medium text-ink-2">Heading 3 / 18px Medium</p>
|
||||
<p className="text-base text-ink-2">Body / 16px Regular — Plus Jakarta Sans</p>
|
||||
<p className="text-sm text-ink-3">Small / 14px Regular — secondary copy</p>
|
||||
<p className="text-xs text-ink-4">Caption / 12px Regular — labels, metadata</p>
|
||||
<p className="text-xs font-mono text-ink-3">Mono / 12px — token names, code</p>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
{/* ── buttons ── */}
|
||||
<Section title="Button">
|
||||
<div className="space-y-4 bg-surface rounded-lg p-6 border border-line">
|
||||
<div className="flex flex-wrap gap-2 items-center">
|
||||
<Button>Default</Button>
|
||||
<Button variant="secondary">Secondary</Button>
|
||||
<Button variant="outline">Outline</Button>
|
||||
<Button variant="ghost">Ghost</Button>
|
||||
<Button variant="destructive">Destructive</Button>
|
||||
<Button variant="link">Link</Button>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2 items-center">
|
||||
<Button size="lg">Large</Button>
|
||||
<Button>Default</Button>
|
||||
<Button size="sm">Small</Button>
|
||||
<Button size="icon">+</Button>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2 items-center">
|
||||
<Button disabled>Disabled</Button>
|
||||
<Button variant="outline" disabled>Outline disabled</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
{/* ── badges ── */}
|
||||
<Section title="Badge">
|
||||
<div className="flex flex-wrap gap-2 bg-surface rounded-lg p-6 border border-line">
|
||||
<Badge>Default</Badge>
|
||||
<Badge variant="secondary">Secondary</Badge>
|
||||
<Badge variant="outline">Outline</Badge>
|
||||
<Badge variant="destructive">Destructive</Badge>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
{/* ── inputs / forms ── */}
|
||||
<Section title="Input · Label · Progress · Separator">
|
||||
<div className="space-y-5 bg-surface rounded-lg p-6 border border-line max-w-md">
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="ds-email">Email</Label>
|
||||
<Input id="ds-email" type="email" placeholder="you@expria.io" />
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="ds-pass">Password</Label>
|
||||
<Input id="ds-pass" type="password" placeholder="••••••••" />
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<Label htmlFor="ds-invalid">Invalid field</Label>
|
||||
<Input id="ds-invalid" aria-invalid="true" placeholder="Error state" />
|
||||
</div>
|
||||
<div className="space-y-1.5">
|
||||
<Label>Progress — 65%</Label>
|
||||
<Progress value={65} />
|
||||
</div>
|
||||
<Separator />
|
||||
<p className="text-sm text-ink-4">Content below separator</p>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
{/* ── avatar ── */}
|
||||
<Section title="Avatar">
|
||||
<div className="flex flex-wrap items-end gap-6 bg-surface rounded-lg p-6 border border-line">
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<Avatar size="sm">
|
||||
<AvatarImage src="" />
|
||||
<AvatarFallback>HK</AvatarFallback>
|
||||
</Avatar>
|
||||
<span className="text-xs text-ink-4">sm</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<Avatar>
|
||||
<AvatarImage src="" />
|
||||
<AvatarFallback>HK</AvatarFallback>
|
||||
</Avatar>
|
||||
<span className="text-xs text-ink-4">default</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<Avatar size="lg">
|
||||
<AvatarImage src="" />
|
||||
<AvatarFallback>HK</AvatarFallback>
|
||||
</Avatar>
|
||||
<span className="text-xs text-ink-4">lg</span>
|
||||
</div>
|
||||
<div className="flex flex-col items-center gap-2">
|
||||
<AvatarGroup>
|
||||
{['AB', 'CD', 'EF'].map(initials => (
|
||||
<Avatar key={initials}>
|
||||
<AvatarFallback>{initials}</AvatarFallback>
|
||||
</Avatar>
|
||||
))}
|
||||
<AvatarGroupCount>+5</AvatarGroupCount>
|
||||
</AvatarGroup>
|
||||
<span className="text-xs text-ink-4">group</span>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
{/* ── dialog ── */}
|
||||
<Section title="Dialog">
|
||||
<div className="bg-surface rounded-lg p-6 border border-line">
|
||||
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="outline">Open dialog</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Example dialog</DialogTitle>
|
||||
<DialogDescription>
|
||||
This dialog uses Direction H tokens — bg-surface, border-line, text-ink-4.
|
||||
Toggle the theme to see it adapt.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter showCloseButton>
|
||||
<Button onClick={() => setDialogOpen(false)}>Confirm</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue