feat(ui): Inter font + design-system preview page #10
Generated
+10
@@ -8,6 +8,7 @@
|
|||||||
"name": "inboxintel-frontend",
|
"name": "inboxintel-frontend",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fontsource-variable/inter": "^5.2.8",
|
||||||
"@radix-ui/react-avatar": "^1.2.1",
|
"@radix-ui/react-avatar": "^1.2.1",
|
||||||
"@radix-ui/react-checkbox": "^1.3.6",
|
"@radix-ui/react-checkbox": "^1.3.6",
|
||||||
"@radix-ui/react-dialog": "^1.1.18",
|
"@radix-ui/react-dialog": "^1.1.18",
|
||||||
@@ -764,6 +765,15 @@
|
|||||||
"integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==",
|
"integrity": "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==",
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/@jridgewell/gen-mapping": {
|
||||||
"version": "0.3.13",
|
"version": "0.3.13",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
"preview": "vite preview --host"
|
"preview": "vite preview --host"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@fontsource-variable/inter": "^5.2.8",
|
||||||
"@radix-ui/react-avatar": "^1.2.1",
|
"@radix-ui/react-avatar": "^1.2.1",
|
||||||
"@radix-ui/react-checkbox": "^1.3.6",
|
"@radix-ui/react-checkbox": "^1.3.6",
|
||||||
"@radix-ui/react-dialog": "^1.1.18",
|
"@radix-ui/react-dialog": "^1.1.18",
|
||||||
|
|||||||
@@ -66,9 +66,9 @@
|
|||||||
|
|
||||||
body {
|
body {
|
||||||
@apply bg-background text-foreground antialiased;
|
@apply bg-background text-foreground antialiased;
|
||||||
/* Prefer Inter when available (bundled in a later slice); graceful system fallback. */
|
/* Inter (variable, self-hosted via @fontsource-variable/inter); system fallback. */
|
||||||
font-family: 'Inter', ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto,
|
font-family: 'Inter Variable', 'Inter', ui-sans-serif, system-ui, -apple-system,
|
||||||
Helvetica, Arial, sans-serif;
|
'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Subtle, modern scrollbars that respect the theme. */
|
/* Subtle, modern scrollbars that respect the theme. */
|
||||||
|
|||||||
@@ -9,7 +9,9 @@ import Unsubscribe from './pages/Unsubscribe.jsx';
|
|||||||
import FolderView from './pages/FolderView.jsx';
|
import FolderView from './pages/FolderView.jsx';
|
||||||
import SearchResults from './pages/SearchResults.jsx';
|
import SearchResults from './pages/SearchResults.jsx';
|
||||||
import Layout from './components/Layout.jsx';
|
import Layout from './components/Layout.jsx';
|
||||||
|
import DesignSystem from './pages/DesignSystem.jsx';
|
||||||
import { ToastProvider, TooltipProvider } from './components/ui';
|
import { ToastProvider, TooltipProvider } from './components/ui';
|
||||||
|
import '@fontsource-variable/inter';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
import './styles.css';
|
import './styles.css';
|
||||||
|
|
||||||
@@ -30,6 +32,7 @@ ReactDOM.createRoot(document.getElementById('root')).render(
|
|||||||
<Route path="unsubscribe" element={<Unsubscribe />} />
|
<Route path="unsubscribe" element={<Unsubscribe />} />
|
||||||
<Route path="folder/:slug" element={<FolderView />} />
|
<Route path="folder/:slug" element={<FolderView />} />
|
||||||
<Route path="search" element={<SearchResults />} />
|
<Route path="search" element={<SearchResults />} />
|
||||||
|
<Route path="design" element={<DesignSystem />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route path="*" element={<Navigate to="/" replace />} />
|
<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: {
|
fontFamily: {
|
||||||
sans: [
|
sans: [
|
||||||
|
'Inter Variable',
|
||||||
'Inter',
|
'Inter',
|
||||||
'ui-sans-serif',
|
'ui-sans-serif',
|
||||||
'system-ui',
|
'system-ui',
|
||||||
|
|||||||
Reference in New Issue
Block a user