feat: add Component Inventory page — primitives section
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import { Metrics } from './pages/Metrics/Metrics'
|
|||||||
import { RouteDetail } from './pages/RouteDetail/RouteDetail'
|
import { RouteDetail } from './pages/RouteDetail/RouteDetail'
|
||||||
import { ExchangeDetail } from './pages/ExchangeDetail/ExchangeDetail'
|
import { ExchangeDetail } from './pages/ExchangeDetail/ExchangeDetail'
|
||||||
import { AgentHealth } from './pages/AgentHealth/AgentHealth'
|
import { AgentHealth } from './pages/AgentHealth/AgentHealth'
|
||||||
|
import { Inventory } from './pages/Inventory/Inventory'
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
return (
|
return (
|
||||||
@@ -13,6 +14,7 @@ export default function App() {
|
|||||||
<Route path="/routes/:id" element={<RouteDetail />} />
|
<Route path="/routes/:id" element={<RouteDetail />} />
|
||||||
<Route path="/exchanges/:id" element={<ExchangeDetail />} />
|
<Route path="/exchanges/:id" element={<ExchangeDetail />} />
|
||||||
<Route path="/agents" element={<AgentHealth />} />
|
<Route path="/agents" element={<AgentHealth />} />
|
||||||
|
<Route path="/inventory" element={<Inventory />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
87
src/pages/Inventory/Inventory.module.css
Normal file
87
src/pages/Inventory/Inventory.module.css
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
.page {
|
||||||
|
min-height: 100vh;
|
||||||
|
background: var(--bg-body);
|
||||||
|
font-family: var(--font-body);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 10;
|
||||||
|
background: var(--bg-surface);
|
||||||
|
border-bottom: 1px solid var(--border);
|
||||||
|
padding: 12px 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.headerTitle {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.backLink {
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--amber);
|
||||||
|
text-decoration: none;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.backLink:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
display: flex;
|
||||||
|
gap: 0;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav {
|
||||||
|
width: 200px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
position: sticky;
|
||||||
|
top: 57px;
|
||||||
|
height: calc(100vh - 57px);
|
||||||
|
overflow-y: auto;
|
||||||
|
padding-right: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navSection {
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navLabel {
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
color: var(--text-muted);
|
||||||
|
padding: 8px 8px 4px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navLink {
|
||||||
|
display: block;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
text-decoration: none;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.navLink:hover {
|
||||||
|
background: var(--bg-hover);
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
37
src/pages/Inventory/Inventory.tsx
Normal file
37
src/pages/Inventory/Inventory.tsx
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { Link } from 'react-router-dom'
|
||||||
|
import styles from './Inventory.module.css'
|
||||||
|
import { PrimitivesSection } from './sections/PrimitivesSection'
|
||||||
|
|
||||||
|
const NAV_ITEMS = [
|
||||||
|
{ label: 'Primitives', href: '#primitives' },
|
||||||
|
{ label: 'Composites', href: '#composites' },
|
||||||
|
{ label: 'Layout', href: '#layout' },
|
||||||
|
]
|
||||||
|
|
||||||
|
export function Inventory() {
|
||||||
|
return (
|
||||||
|
<div className={styles.page}>
|
||||||
|
<header className={styles.header}>
|
||||||
|
<h1 className={styles.headerTitle}>Component Inventory</h1>
|
||||||
|
<Link to="/" className={styles.backLink}>← Back to app</Link>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div className={styles.body}>
|
||||||
|
<nav className={styles.nav} aria-label="Component categories">
|
||||||
|
<div className={styles.navSection}>
|
||||||
|
<span className={styles.navLabel}>Categories</span>
|
||||||
|
{NAV_ITEMS.map((item) => (
|
||||||
|
<a key={item.href} href={item.href} className={styles.navLink}>
|
||||||
|
{item.label}
|
||||||
|
</a>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<main className={styles.content}>
|
||||||
|
<PrimitivesSection />
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
74
src/pages/Inventory/sections/PrimitivesSection.module.css
Normal file
74
src/pages/Inventory/sections/PrimitivesSection.module.css
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
.section {
|
||||||
|
margin-bottom: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sectionTitle {
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin: 0 0 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.componentCard {
|
||||||
|
background: var(--bg-surface);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.componentTitle {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-primary);
|
||||||
|
margin: 0 0 4px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.componentDesc {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
margin: 0 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demoArea {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demoAreaColumn {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demoAreaRow {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demoLabel {
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demoGroup {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.demoGroupRow {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
591
src/pages/Inventory/sections/PrimitivesSection.tsx
Normal file
591
src/pages/Inventory/sections/PrimitivesSection.tsx
Normal file
@@ -0,0 +1,591 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
import styles from './PrimitivesSection.module.css'
|
||||||
|
import {
|
||||||
|
Alert,
|
||||||
|
Avatar,
|
||||||
|
Badge,
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Checkbox,
|
||||||
|
CodeBlock,
|
||||||
|
Collapsible,
|
||||||
|
DateRangePicker,
|
||||||
|
DateTimePicker,
|
||||||
|
EmptyState,
|
||||||
|
FilterPill,
|
||||||
|
FormField,
|
||||||
|
InfoCallout,
|
||||||
|
Input,
|
||||||
|
KeyboardHint,
|
||||||
|
Label,
|
||||||
|
MonoText,
|
||||||
|
Pagination,
|
||||||
|
ProgressBar,
|
||||||
|
RadioGroup,
|
||||||
|
RadioItem,
|
||||||
|
SectionHeader,
|
||||||
|
Select,
|
||||||
|
Skeleton,
|
||||||
|
Sparkline,
|
||||||
|
Spinner,
|
||||||
|
StatCard,
|
||||||
|
StatusDot,
|
||||||
|
Tag,
|
||||||
|
Textarea,
|
||||||
|
Toggle,
|
||||||
|
Tooltip,
|
||||||
|
} from '../../../design-system/primitives'
|
||||||
|
|
||||||
|
// ── helpers ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
interface DemoCardProps {
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
description: string
|
||||||
|
children: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
function DemoCard({ id, title, description, children }: DemoCardProps) {
|
||||||
|
return (
|
||||||
|
<div id={id} className={styles.componentCard}>
|
||||||
|
<h3 className={styles.componentTitle}>{title}</h3>
|
||||||
|
<p className={styles.componentDesc}>{description}</p>
|
||||||
|
<div className={styles.demoArea}>{children}</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Sample data ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
const SPARKLINE_DATA = [10, 25, 15, 30, 20, 35, 28]
|
||||||
|
|
||||||
|
const CODE_JSON = JSON.stringify(
|
||||||
|
{ status: 'ok', version: '2.4.1', routes: 42 },
|
||||||
|
null,
|
||||||
|
2,
|
||||||
|
)
|
||||||
|
|
||||||
|
// ── PrimitivesSection ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
export function PrimitivesSection() {
|
||||||
|
// Alert state
|
||||||
|
const [alertDismissed, setAlertDismissed] = useState(false)
|
||||||
|
|
||||||
|
// Checkbox state
|
||||||
|
const [checked1, setChecked1] = useState(false)
|
||||||
|
const [checked2, setChecked2] = useState(true)
|
||||||
|
|
||||||
|
// Toggle state
|
||||||
|
const [toggleOn, setToggleOn] = useState(true)
|
||||||
|
const [toggleOff, setToggleOff] = useState(false)
|
||||||
|
|
||||||
|
// Radio state
|
||||||
|
const [radioV, setRadioV] = useState('option-a')
|
||||||
|
const [radioH, setRadioH] = useState('beta')
|
||||||
|
|
||||||
|
// Pagination state
|
||||||
|
const [page, setPage] = useState(5)
|
||||||
|
|
||||||
|
// DateTimePicker state
|
||||||
|
const [dtValue, setDtValue] = useState<Date>(new Date('2026-03-18T09:00'))
|
||||||
|
|
||||||
|
// DateRangePicker state
|
||||||
|
const [dateRange, setDateRange] = useState({
|
||||||
|
start: new Date('2026-03-11T00:00'),
|
||||||
|
end: new Date('2026-03-18T23:59'),
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section id="primitives" className={styles.section}>
|
||||||
|
<h2 className={styles.sectionTitle}>Primitives</h2>
|
||||||
|
|
||||||
|
{/* 1. Alert */}
|
||||||
|
<DemoCard
|
||||||
|
id="alert"
|
||||||
|
title="Alert"
|
||||||
|
description="Contextual feedback messages in four variants, optionally dismissible."
|
||||||
|
>
|
||||||
|
<div className={styles.demoAreaColumn} style={{ width: '100%' }}>
|
||||||
|
<Alert variant="info" title="Info">This is an informational message.</Alert>
|
||||||
|
<Alert variant="success" title="Success">Operation completed successfully.</Alert>
|
||||||
|
<Alert variant="warning" title="Warning">This action may have side effects.</Alert>
|
||||||
|
{!alertDismissed && (
|
||||||
|
<Alert
|
||||||
|
variant="error"
|
||||||
|
title="Error"
|
||||||
|
dismissible
|
||||||
|
onDismiss={() => setAlertDismissed(true)}
|
||||||
|
>
|
||||||
|
Something went wrong. Dismiss to clear.
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
{alertDismissed && (
|
||||||
|
<Button size="sm" variant="ghost" onClick={() => setAlertDismissed(false)}>
|
||||||
|
Reset dismissed alert
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</DemoCard>
|
||||||
|
|
||||||
|
{/* 2. Avatar */}
|
||||||
|
<DemoCard
|
||||||
|
id="avatar"
|
||||||
|
title="Avatar"
|
||||||
|
description="Initials-based avatar with hash-derived colour, three sizes."
|
||||||
|
>
|
||||||
|
<Avatar name="Alice Johnson" size="sm" />
|
||||||
|
<Avatar name="Bob Smith" size="md" />
|
||||||
|
<Avatar name="Carol White" size="lg" />
|
||||||
|
</DemoCard>
|
||||||
|
|
||||||
|
{/* 3. Badge */}
|
||||||
|
<DemoCard
|
||||||
|
id="badge"
|
||||||
|
title="Badge"
|
||||||
|
description="Compact label with semantic colours and a dashed variant."
|
||||||
|
>
|
||||||
|
<Badge label="primary" color="primary" />
|
||||||
|
<Badge label="success" color="success" />
|
||||||
|
<Badge label="warning" color="warning" />
|
||||||
|
<Badge label="error" color="error" />
|
||||||
|
<Badge label="running" color="running" />
|
||||||
|
<Badge label="auto-hash" color="auto" />
|
||||||
|
<Badge label="dashed" color="primary" variant="dashed" />
|
||||||
|
</DemoCard>
|
||||||
|
|
||||||
|
{/* 4. Button */}
|
||||||
|
<DemoCard
|
||||||
|
id="button"
|
||||||
|
title="Button"
|
||||||
|
description="Primary, secondary, danger, and ghost variants across two sizes plus loading state."
|
||||||
|
>
|
||||||
|
<div className={styles.demoAreaColumn}>
|
||||||
|
<div className={styles.demoAreaRow}>
|
||||||
|
<Button variant="primary">Primary</Button>
|
||||||
|
<Button variant="secondary">Secondary</Button>
|
||||||
|
<Button variant="danger">Danger</Button>
|
||||||
|
<Button variant="ghost">Ghost</Button>
|
||||||
|
</div>
|
||||||
|
<div className={styles.demoAreaRow}>
|
||||||
|
<Button variant="primary" size="sm">Small</Button>
|
||||||
|
<Button variant="primary" size="md">Medium</Button>
|
||||||
|
<Button variant="primary" loading>Loading</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</DemoCard>
|
||||||
|
|
||||||
|
{/* 5. Card */}
|
||||||
|
<DemoCard
|
||||||
|
id="card"
|
||||||
|
title="Card"
|
||||||
|
description="Surface container with optional left-border accent colour."
|
||||||
|
>
|
||||||
|
<Card><div style={{ padding: '8px 12px', fontSize: 13 }}>Plain card</div></Card>
|
||||||
|
<Card accent="amber"><div style={{ padding: '8px 12px', fontSize: 13 }}>Amber accent</div></Card>
|
||||||
|
<Card accent="success"><div style={{ padding: '8px 12px', fontSize: 13 }}>Success accent</div></Card>
|
||||||
|
<Card accent="error"><div style={{ padding: '8px 12px', fontSize: 13 }}>Error accent</div></Card>
|
||||||
|
</DemoCard>
|
||||||
|
|
||||||
|
{/* 6. Checkbox */}
|
||||||
|
<DemoCard
|
||||||
|
id="checkbox"
|
||||||
|
title="Checkbox"
|
||||||
|
description="Accessible checkbox with optional label, controlled and disabled states."
|
||||||
|
>
|
||||||
|
<Checkbox
|
||||||
|
label="Unchecked"
|
||||||
|
checked={checked1}
|
||||||
|
onChange={(e) => setChecked1(e.target.checked)}
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
label="Checked"
|
||||||
|
checked={checked2}
|
||||||
|
onChange={(e) => setChecked2(e.target.checked)}
|
||||||
|
/>
|
||||||
|
<Checkbox label="Disabled" disabled />
|
||||||
|
<Checkbox label="Disabled checked" disabled defaultChecked />
|
||||||
|
</DemoCard>
|
||||||
|
|
||||||
|
{/* 7. CodeBlock */}
|
||||||
|
<DemoCard
|
||||||
|
id="codeblock"
|
||||||
|
title="CodeBlock"
|
||||||
|
description="Syntax-highlighted code block with line numbers and copy button."
|
||||||
|
>
|
||||||
|
<div style={{ width: '100%' }}>
|
||||||
|
<CodeBlock
|
||||||
|
content={CODE_JSON}
|
||||||
|
language="json"
|
||||||
|
lineNumbers
|
||||||
|
copyable
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</DemoCard>
|
||||||
|
|
||||||
|
{/* 8. Collapsible */}
|
||||||
|
<DemoCard
|
||||||
|
id="collapsible"
|
||||||
|
title="Collapsible"
|
||||||
|
description="Animated accordion-style disclosure, open and closed by default."
|
||||||
|
>
|
||||||
|
<div className={styles.demoAreaColumn} style={{ width: '100%' }}>
|
||||||
|
<Collapsible title="Collapsed by default">
|
||||||
|
<p style={{ margin: 0, fontSize: 13 }}>Hidden content revealed on expand.</p>
|
||||||
|
</Collapsible>
|
||||||
|
<Collapsible title="Open by default" defaultOpen>
|
||||||
|
<p style={{ margin: 0, fontSize: 13 }}>This content is visible from the start.</p>
|
||||||
|
</Collapsible>
|
||||||
|
</div>
|
||||||
|
</DemoCard>
|
||||||
|
|
||||||
|
{/* 9. DateTimePicker */}
|
||||||
|
<DemoCard
|
||||||
|
id="datetimepicker"
|
||||||
|
title="DateTimePicker"
|
||||||
|
description="Native datetime-local input wrapped with label and controlled value."
|
||||||
|
>
|
||||||
|
<DateTimePicker
|
||||||
|
label="Pick a date & time"
|
||||||
|
value={dtValue}
|
||||||
|
onChange={(d) => d && setDtValue(d)}
|
||||||
|
/>
|
||||||
|
</DemoCard>
|
||||||
|
|
||||||
|
{/* 10. DateRangePicker */}
|
||||||
|
<DemoCard
|
||||||
|
id="daterangepicker"
|
||||||
|
title="DateRangePicker"
|
||||||
|
description="Preset pills combined with two DateTimePicker inputs for a start/end range."
|
||||||
|
>
|
||||||
|
<div style={{ width: '100%' }}>
|
||||||
|
<DateRangePicker value={dateRange} onChange={setDateRange} />
|
||||||
|
</div>
|
||||||
|
</DemoCard>
|
||||||
|
|
||||||
|
{/* 11. EmptyState */}
|
||||||
|
<DemoCard
|
||||||
|
id="emptystate"
|
||||||
|
title="EmptyState"
|
||||||
|
description="Zero-data placeholder with icon, title, description, and action slot."
|
||||||
|
>
|
||||||
|
<EmptyState
|
||||||
|
icon={<span style={{ fontSize: 28 }}>📭</span>}
|
||||||
|
title="No results found"
|
||||||
|
description="Try adjusting your filters or search query."
|
||||||
|
action={<Button size="sm" variant="secondary">Clear filters</Button>}
|
||||||
|
/>
|
||||||
|
</DemoCard>
|
||||||
|
|
||||||
|
{/* 12. FilterPill */}
|
||||||
|
<DemoCard
|
||||||
|
id="filterpill"
|
||||||
|
title="FilterPill"
|
||||||
|
description="Toggle-style pill for filter UIs, with optional dot indicator and count."
|
||||||
|
>
|
||||||
|
<FilterPill label="Active" active />
|
||||||
|
<FilterPill label="Inactive" />
|
||||||
|
<FilterPill label="With dot" dot active />
|
||||||
|
<FilterPill label="Count" count={42} />
|
||||||
|
</DemoCard>
|
||||||
|
|
||||||
|
{/* 13. FormField */}
|
||||||
|
<DemoCard
|
||||||
|
id="formfield"
|
||||||
|
title="FormField"
|
||||||
|
description="Field wrapper providing a label, hint text, and inline error messaging."
|
||||||
|
>
|
||||||
|
<div className={styles.demoAreaColumn} style={{ minWidth: 240 }}>
|
||||||
|
<FormField label="Username" htmlFor="inv-username" hint="Letters and numbers only.">
|
||||||
|
<Input id="inv-username" placeholder="e.g. alice42" />
|
||||||
|
</FormField>
|
||||||
|
<FormField label="Email" htmlFor="inv-email" required error="Invalid email address.">
|
||||||
|
<Input id="inv-email" placeholder="you@example.com" />
|
||||||
|
</FormField>
|
||||||
|
</div>
|
||||||
|
</DemoCard>
|
||||||
|
|
||||||
|
{/* 14. InfoCallout */}
|
||||||
|
<DemoCard
|
||||||
|
id="infocallout"
|
||||||
|
title="InfoCallout"
|
||||||
|
description="Bordered callout block in amber, success, warning, and error flavours."
|
||||||
|
>
|
||||||
|
<div className={styles.demoAreaColumn} style={{ width: '100%' }}>
|
||||||
|
<InfoCallout variant="amber" title="Amber">Review before publishing.</InfoCallout>
|
||||||
|
<InfoCallout variant="success" title="Success">Deployment completed.</InfoCallout>
|
||||||
|
<InfoCallout variant="warning" title="Warning">Rate limit approaching.</InfoCallout>
|
||||||
|
<InfoCallout variant="error" title="Error">Build failed — check logs.</InfoCallout>
|
||||||
|
</div>
|
||||||
|
</DemoCard>
|
||||||
|
|
||||||
|
{/* 15. Input */}
|
||||||
|
<DemoCard
|
||||||
|
id="input"
|
||||||
|
title="Input"
|
||||||
|
description="Text input with optional leading icon and placeholder."
|
||||||
|
>
|
||||||
|
<Input placeholder="Plain input" />
|
||||||
|
<Input icon="🔍" placeholder="With icon" />
|
||||||
|
</DemoCard>
|
||||||
|
|
||||||
|
{/* 16. KeyboardHint */}
|
||||||
|
<DemoCard
|
||||||
|
id="keyboardhint"
|
||||||
|
title="KeyboardHint"
|
||||||
|
description="Styled keyboard shortcut badge."
|
||||||
|
>
|
||||||
|
<KeyboardHint keys="Ctrl+K" />
|
||||||
|
<KeyboardHint keys="⌘+P" />
|
||||||
|
<KeyboardHint keys="Esc" />
|
||||||
|
</DemoCard>
|
||||||
|
|
||||||
|
{/* 17. Label */}
|
||||||
|
<DemoCard
|
||||||
|
id="label"
|
||||||
|
title="Label"
|
||||||
|
description="Form label with optional required asterisk."
|
||||||
|
>
|
||||||
|
<Label>Plain label</Label>
|
||||||
|
<Label required>Required label</Label>
|
||||||
|
</DemoCard>
|
||||||
|
|
||||||
|
{/* 18. MonoText */}
|
||||||
|
<DemoCard
|
||||||
|
id="monotext"
|
||||||
|
title="MonoText"
|
||||||
|
description="Monospaced text in xs, sm, and md sizes."
|
||||||
|
>
|
||||||
|
<MonoText size="xs">xs: route-id-001</MonoText>
|
||||||
|
<MonoText size="sm">sm: route-id-001</MonoText>
|
||||||
|
<MonoText size="md">md: route-id-001</MonoText>
|
||||||
|
</DemoCard>
|
||||||
|
|
||||||
|
{/* 19. Pagination */}
|
||||||
|
<DemoCard
|
||||||
|
id="pagination"
|
||||||
|
title="Pagination"
|
||||||
|
description="Page navigation with ellipsis and sibling-window algorithm."
|
||||||
|
>
|
||||||
|
<Pagination
|
||||||
|
page={page}
|
||||||
|
totalPages={20}
|
||||||
|
onPageChange={setPage}
|
||||||
|
/>
|
||||||
|
</DemoCard>
|
||||||
|
|
||||||
|
{/* 20. ProgressBar */}
|
||||||
|
<DemoCard
|
||||||
|
id="progressbar"
|
||||||
|
title="ProgressBar"
|
||||||
|
description="Determinate and indeterminate bars across variants and sizes."
|
||||||
|
>
|
||||||
|
<div className={styles.demoAreaColumn} style={{ width: '100%' }}>
|
||||||
|
<ProgressBar value={60} label="60% complete" />
|
||||||
|
<ProgressBar value={60} variant="success" label="Success variant" />
|
||||||
|
<ProgressBar value={60} variant="warning" label="Warning variant" />
|
||||||
|
<ProgressBar value={60} variant="error" label="Error variant" />
|
||||||
|
<ProgressBar value={60} variant="running" label="Running variant" />
|
||||||
|
<ProgressBar indeterminate label="Indeterminate" />
|
||||||
|
<ProgressBar value={60} size="sm" label="Small size" />
|
||||||
|
</div>
|
||||||
|
</DemoCard>
|
||||||
|
|
||||||
|
{/* 21. Radio */}
|
||||||
|
<DemoCard
|
||||||
|
id="radio"
|
||||||
|
title="Radio"
|
||||||
|
description="RadioGroup with vertical and horizontal orientations."
|
||||||
|
>
|
||||||
|
<RadioGroup name="inv-radio-v" value={radioV} onChange={setRadioV} orientation="vertical">
|
||||||
|
<RadioItem value="option-a" label="Option A" />
|
||||||
|
<RadioItem value="option-b" label="Option B" />
|
||||||
|
<RadioItem value="option-c" label="Option C (disabled)" disabled />
|
||||||
|
</RadioGroup>
|
||||||
|
<RadioGroup name="inv-radio-h" value={radioH} onChange={setRadioH} orientation="horizontal">
|
||||||
|
<RadioItem value="alpha" label="Alpha" />
|
||||||
|
<RadioItem value="beta" label="Beta" />
|
||||||
|
<RadioItem value="gamma" label="Gamma" />
|
||||||
|
</RadioGroup>
|
||||||
|
</DemoCard>
|
||||||
|
|
||||||
|
{/* 22. SectionHeader */}
|
||||||
|
<DemoCard
|
||||||
|
id="sectionheader"
|
||||||
|
title="SectionHeader"
|
||||||
|
description="Labelled horizontal divider with an optional right-side action slot."
|
||||||
|
>
|
||||||
|
<div className={styles.demoAreaColumn} style={{ width: '100%' }}>
|
||||||
|
<SectionHeader>Without action</SectionHeader>
|
||||||
|
<SectionHeader action={<Button size="sm" variant="ghost">Add item</Button>}>
|
||||||
|
With action
|
||||||
|
</SectionHeader>
|
||||||
|
</div>
|
||||||
|
</DemoCard>
|
||||||
|
|
||||||
|
{/* 23. Select */}
|
||||||
|
<DemoCard
|
||||||
|
id="select"
|
||||||
|
title="Select"
|
||||||
|
description="Styled native select with custom chevron."
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
options={[
|
||||||
|
{ value: 'opt1', label: 'Option 1' },
|
||||||
|
{ value: 'opt2', label: 'Option 2' },
|
||||||
|
{ value: 'opt3', label: 'Option 3' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</DemoCard>
|
||||||
|
|
||||||
|
{/* 24. Skeleton */}
|
||||||
|
<DemoCard
|
||||||
|
id="skeleton"
|
||||||
|
title="Skeleton"
|
||||||
|
description="Loading placeholder in text, circular, and rectangular variants."
|
||||||
|
>
|
||||||
|
<div className={styles.demoAreaColumn}>
|
||||||
|
<Skeleton variant="text" lines={3} width={200} />
|
||||||
|
<Skeleton variant="circular" />
|
||||||
|
<Skeleton variant="rectangular" width={200} height={60} />
|
||||||
|
</div>
|
||||||
|
</DemoCard>
|
||||||
|
|
||||||
|
{/* 25. Sparkline */}
|
||||||
|
<DemoCard
|
||||||
|
id="sparkline"
|
||||||
|
title="Sparkline"
|
||||||
|
description="Lightweight SVG line chart for inline trend visualisation."
|
||||||
|
>
|
||||||
|
<Sparkline data={SPARKLINE_DATA} width={120} height={30} />
|
||||||
|
<Sparkline data={SPARKLINE_DATA} color="var(--success)" width={120} height={30} />
|
||||||
|
<Sparkline data={SPARKLINE_DATA} color="var(--error)" width={120} height={30} />
|
||||||
|
</DemoCard>
|
||||||
|
|
||||||
|
{/* 26. Spinner */}
|
||||||
|
<DemoCard
|
||||||
|
id="spinner"
|
||||||
|
title="Spinner"
|
||||||
|
description="Animated loading indicator in sm, md, and lg sizes."
|
||||||
|
>
|
||||||
|
<Spinner size="sm" />
|
||||||
|
<Spinner size="md" />
|
||||||
|
<Spinner size="lg" />
|
||||||
|
</DemoCard>
|
||||||
|
|
||||||
|
{/* 27. StatCard */}
|
||||||
|
<DemoCard
|
||||||
|
id="statcard"
|
||||||
|
title="StatCard"
|
||||||
|
description="Metric tile with label, value, trend indicator, and inline sparkline."
|
||||||
|
>
|
||||||
|
<StatCard
|
||||||
|
label="Throughput"
|
||||||
|
value="1,433"
|
||||||
|
detail="msg/s"
|
||||||
|
trend="up"
|
||||||
|
trendValue="+12%"
|
||||||
|
accent="amber"
|
||||||
|
sparkline={SPARKLINE_DATA}
|
||||||
|
/>
|
||||||
|
<StatCard
|
||||||
|
label="Error rate"
|
||||||
|
value="0.4%"
|
||||||
|
trend="down"
|
||||||
|
trendValue="-0.1pp"
|
||||||
|
accent="error"
|
||||||
|
/>
|
||||||
|
<StatCard
|
||||||
|
label="Agents"
|
||||||
|
value="6"
|
||||||
|
detail="all healthy"
|
||||||
|
trend="neutral"
|
||||||
|
trendValue="±0"
|
||||||
|
accent="success"
|
||||||
|
/>
|
||||||
|
</DemoCard>
|
||||||
|
|
||||||
|
{/* 28. StatusDot */}
|
||||||
|
<DemoCard
|
||||||
|
id="statusdot"
|
||||||
|
title="StatusDot"
|
||||||
|
description="Small coloured dot for all status variants, with optional pulse animation."
|
||||||
|
>
|
||||||
|
{(['live', 'stale', 'dead', 'success', 'warning', 'error', 'running'] as const).map(
|
||||||
|
(v) => (
|
||||||
|
<div key={v} className={styles.demoGroupRow}>
|
||||||
|
<StatusDot variant={v} />
|
||||||
|
<span style={{ fontSize: 12, color: 'var(--text-secondary)' }}>{v}</span>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
<div className={styles.demoGroupRow}>
|
||||||
|
<StatusDot variant="live" pulse />
|
||||||
|
<span style={{ fontSize: 12, color: 'var(--text-secondary)' }}>live + pulse</span>
|
||||||
|
</div>
|
||||||
|
</DemoCard>
|
||||||
|
|
||||||
|
{/* 29. Tag */}
|
||||||
|
<DemoCard
|
||||||
|
id="tag"
|
||||||
|
title="Tag"
|
||||||
|
description="Keyword label with semantic or auto hash colour, and removable variant."
|
||||||
|
>
|
||||||
|
<Tag label="primary" color="primary" />
|
||||||
|
<Tag label="success" color="success" />
|
||||||
|
<Tag label="warning" color="warning" />
|
||||||
|
<Tag label="error" color="error" />
|
||||||
|
<Tag label="auto-hash" color="auto" />
|
||||||
|
<Tag label="removable" color="primary" onRemove={() => undefined} />
|
||||||
|
</DemoCard>
|
||||||
|
|
||||||
|
{/* 30. Textarea */}
|
||||||
|
<DemoCard
|
||||||
|
id="textarea"
|
||||||
|
title="Textarea"
|
||||||
|
description="Multi-line text input with vertical resize by default."
|
||||||
|
>
|
||||||
|
<Textarea placeholder="Enter a description…" style={{ width: 280 }} />
|
||||||
|
</DemoCard>
|
||||||
|
|
||||||
|
{/* 31. Toggle */}
|
||||||
|
<DemoCard
|
||||||
|
id="toggle"
|
||||||
|
title="Toggle"
|
||||||
|
description="On/off switch with optional label."
|
||||||
|
>
|
||||||
|
<Toggle
|
||||||
|
label="Enabled"
|
||||||
|
checked={toggleOn}
|
||||||
|
onChange={(e) => setToggleOn(e.target.checked)}
|
||||||
|
/>
|
||||||
|
<Toggle
|
||||||
|
label="Disabled off"
|
||||||
|
checked={toggleOff}
|
||||||
|
onChange={(e) => setToggleOff(e.target.checked)}
|
||||||
|
/>
|
||||||
|
<Toggle label="Locked on" checked disabled />
|
||||||
|
<Toggle label="Locked off" disabled />
|
||||||
|
</DemoCard>
|
||||||
|
|
||||||
|
{/* 32. Tooltip */}
|
||||||
|
<DemoCard
|
||||||
|
id="tooltip"
|
||||||
|
title="Tooltip"
|
||||||
|
description="CSS-only hover tooltip in all four positions."
|
||||||
|
>
|
||||||
|
<Tooltip content="Top tooltip" position="top">
|
||||||
|
<Button size="sm" variant="secondary">Top</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip content="Bottom tooltip" position="bottom">
|
||||||
|
<Button size="sm" variant="secondary">Bottom</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip content="Left tooltip" position="left">
|
||||||
|
<Button size="sm" variant="secondary">Left</Button>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip content="Right tooltip" position="right">
|
||||||
|
<Button size="sm" variant="secondary">Right</Button>
|
||||||
|
</Tooltip>
|
||||||
|
</DemoCard>
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user