feat(ui): Inter font + design-system preview page #10
Generated
+10
@@ -8,6 +8,7 @@
|
||||
"name": "inboxintel-frontend",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@fontsource-variable/inter": "^5.2.8",
|
||||
"@radix-ui/react-avatar": "^1.2.1",
|
||||
"@radix-ui/react-checkbox": "^1.3.6",
|
||||
"@radix-ui/react-dialog": "^1.1.18",
|
||||
@@ -764,6 +765,15 @@
|
||||
"integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@fontsource-variable/inter": {
|
||||
"version": "5.2.8",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource-variable/inter/-/inter-5.2.8.tgz",
|
||||
"integrity": "sha512-kOfP2D+ykbcX/P3IFnokOhVRNoTozo5/JxhAIVYLpea/UBmCQ/YWPBfWIDuBImXX/15KH+eKh4xpEUyS2sQQGQ==",
|
||||
"license": "OFL-1.1",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ayuhito"
|
||||
}
|
||||
},
|
||||
"node_modules/@jridgewell/gen-mapping": {
|
||||
"version": "0.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
"preview": "vite preview --host"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fontsource-variable/inter": "^5.2.8",
|
||||
"@radix-ui/react-avatar": "^1.2.1",
|
||||
"@radix-ui/react-checkbox": "^1.3.6",
|
||||
"@radix-ui/react-dialog": "^1.1.18",
|
||||
|
||||
@@ -66,9 +66,9 @@
|
||||
|
||||
body {
|
||||
@apply bg-background text-foreground antialiased;
|
||||
/* Prefer Inter when available (bundled in a later slice); graceful system fallback. */
|
||||
font-family: 'Inter', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto,
|
||||
Helvetica, Arial, sans-serif;
|
||||
/* Inter (variable, self-hosted via @fontsource-variable/inter); system fallback. */
|
||||
font-family: 'Inter Variable', 'Inter', ui-sans-serif, system-ui, -apple-system,
|
||||
'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
/* Subtle, modern scrollbars that respect the theme. */
|
||||
|
||||
@@ -9,7 +9,9 @@ import Unsubscribe from './pages/Unsubscribe.jsx';
|
||||
import FolderView from './pages/FolderView.jsx';
|
||||
import SearchResults from './pages/SearchResults.jsx';
|
||||
import Layout from './components/Layout.jsx';
|
||||
import DesignSystem from './pages/DesignSystem.jsx';
|
||||
import { ToastProvider, TooltipProvider } from './components/ui';
|
||||
import '@fontsource-variable/inter';
|
||||
import './index.css';
|
||||
import './styles.css';
|
||||
|
||||
@@ -30,6 +32,7 @@ ReactDOM.createRoot(document.getElementById('root')).render(
|
||||
<Route path="unsubscribe" element={<Unsubscribe />} />
|
||||
<Route path="folder/:slug" element={<FolderView />} />
|
||||
<Route path="search" element={<SearchResults />} />
|
||||
<Route path="design" element={<DesignSystem />} />
|
||||
</Route>
|
||||
|
||||
<Route path="*" element={<Navigate to="/" replace />} />
|
||||
|
||||
@@ -0,0 +1,177 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Button, Badge, Input, Textarea, Switch, Skeleton, Separator,
|
||||
Card, CardHeader, CardTitle, CardDescription, CardContent,
|
||||
Tabs, TabsList, TabsTrigger, TabsContent, EmptyState, ThemeToggle,
|
||||
} from '../components/ui';
|
||||
|
||||
/*
|
||||
Design-system preview (v2). A living reference for the tokens + primitives so the
|
||||
redesign can be visually verified in both themes. Route: /app/design.
|
||||
Not linked in primary nav — it's a developer/design surface.
|
||||
*/
|
||||
|
||||
const TOKENS = [
|
||||
['background', 'App background'],
|
||||
['card', 'Card / surface'],
|
||||
['muted', 'Muted surface'],
|
||||
['border', 'Border'],
|
||||
['primary', 'Brand (green)'],
|
||||
['success', 'Success'],
|
||||
['warning', 'Warning'],
|
||||
['danger', 'Danger'],
|
||||
];
|
||||
// Full literal class names so Tailwind's JIT includes them.
|
||||
const GREEN = [
|
||||
'bg-green-50', 'bg-green-100', 'bg-green-200', 'bg-green-300', 'bg-green-400',
|
||||
'bg-green-500', 'bg-green-600', 'bg-green-700', 'bg-green-800', 'bg-green-900',
|
||||
];
|
||||
const TYPE = [
|
||||
['text-3xl', '30px — Display'],
|
||||
['text-2xl', '24px — Heading'],
|
||||
['text-xl', '20px — Subheading'],
|
||||
['text-lg', '18px — Large'],
|
||||
['text-base', '16px — Body'],
|
||||
['text-sm', '14px — UI base'],
|
||||
['text-xs', '12px — Caption'],
|
||||
];
|
||||
|
||||
function Swatch({ token, label }) {
|
||||
return (
|
||||
<div className="flex flex-col gap-1">
|
||||
<div
|
||||
className="h-14 w-full rounded-lg border"
|
||||
style={{ background: `hsl(var(--${token}))` }}
|
||||
/>
|
||||
<div className="text-xs font-medium">{label}</div>
|
||||
<div className="text-xs text-muted-foreground">--{token}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Section({ title, children }) {
|
||||
return (
|
||||
<section className="mb-10">
|
||||
<h2 className="mb-3 text-lg font-semibold">{title}</h2>
|
||||
{children}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default function DesignSystem() {
|
||||
return (
|
||||
<div className="mx-auto max-w-4xl p-2">
|
||||
<div className="mb-8 flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-semibold">Design system</h1>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
v2 tokens & primitives — green accent, warm neutrals, dark-first. Toggle the
|
||||
theme to verify both.
|
||||
</p>
|
||||
</div>
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
|
||||
<Section title="Semantic tokens">
|
||||
<div className="grid grid-cols-2 gap-4 sm:grid-cols-4">
|
||||
{TOKENS.map(([t, l]) => <Swatch key={t} token={t} label={l} />)}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section title="Brand ramp (green, from #3ba31f)">
|
||||
<div className="flex overflow-hidden rounded-lg border">
|
||||
{GREEN.map((cls) => (
|
||||
<div key={cls} className={`h-12 flex-1 ${cls}`} title={cls} />
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-1 flex justify-between text-xs text-muted-foreground">
|
||||
<span>50</span><span>500 (brand)</span><span>900</span>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section title="Typography (Inter)">
|
||||
<div className="space-y-2">
|
||||
{TYPE.map(([cls, label]) => (
|
||||
<div key={cls} className={`${cls} font-medium`}>
|
||||
{label}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section title="Buttons">
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<Button>Primary</Button>
|
||||
<Button variant="secondary">Secondary</Button>
|
||||
<Button variant="outline">Outline</Button>
|
||||
<Button variant="ghost">Ghost</Button>
|
||||
<Button variant="destructive">Danger</Button>
|
||||
<Button disabled>Disabled</Button>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section title="Badges">
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<Badge>Default</Badge>
|
||||
<Badge variant="secondary">Secondary</Badge>
|
||||
<Badge variant="outline">Outline</Badge>
|
||||
<Badge variant="destructive">Danger</Badge>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section title="Inputs">
|
||||
<div className="grid max-w-md gap-3">
|
||||
<Input placeholder="Search your inbox…" />
|
||||
<Textarea placeholder="Write a reply…" rows={3} />
|
||||
<label className="flex items-center gap-2 text-sm">
|
||||
<Switch defaultChecked /> Use AI features
|
||||
</label>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Section title="Card & tabs">
|
||||
<Card className="max-w-md">
|
||||
<CardHeader>
|
||||
<CardTitle>Thread summary</CardTitle>
|
||||
<CardDescription>AI-generated · local</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Tabs defaultValue="summary">
|
||||
<TabsList>
|
||||
<TabsTrigger value="summary">Summary</TabsTrigger>
|
||||
<TabsTrigger value="actions">Actions</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="summary" className="pt-3 text-sm text-muted-foreground">
|
||||
A concise overview of the conversation would appear here.
|
||||
</TabsContent>
|
||||
<TabsContent value="actions" className="pt-3 text-sm text-muted-foreground">
|
||||
• Reply to the client · • Follow up in 3 days
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Section>
|
||||
|
||||
<Section title="Loading & empty states">
|
||||
<div className="grid gap-6 sm:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-4 w-3/4" />
|
||||
<Skeleton className="h-4 w-1/2" />
|
||||
<Skeleton className="h-4 w-2/3" />
|
||||
</div>
|
||||
<div className="rounded-lg border p-4">
|
||||
<EmptyState
|
||||
title="No results"
|
||||
description="Try broader terms or clear a filter."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
<Separator className="my-8" />
|
||||
<p className="text-xs text-muted-foreground">
|
||||
InboxIntel design system · see docs/discovery/04-ux-redesign-and-design-system.md
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -48,6 +48,7 @@ export default {
|
||||
},
|
||||
fontFamily: {
|
||||
sans: [
|
||||
'Inter Variable',
|
||||
'Inter',
|
||||
'ui-sans-serif',
|
||||
'system-ui',
|
||||
|
||||
Reference in New Issue
Block a user