diff --git a/src/App.tsx b/src/App.tsx index 4ac17ca..2107880 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -4,6 +4,7 @@ import { Metrics } from './pages/Metrics/Metrics' import { RouteDetail } from './pages/RouteDetail/RouteDetail' import { ExchangeDetail } from './pages/ExchangeDetail/ExchangeDetail' import { AgentHealth } from './pages/AgentHealth/AgentHealth' +import { Inventory } from './pages/Inventory/Inventory' export default function App() { return ( @@ -13,6 +14,7 @@ export default function App() { } /> } /> } /> + } /> ) } diff --git a/src/pages/Inventory/Inventory.module.css b/src/pages/Inventory/Inventory.module.css new file mode 100644 index 0000000..fc58819 --- /dev/null +++ b/src/pages/Inventory/Inventory.module.css @@ -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; +} diff --git a/src/pages/Inventory/Inventory.tsx b/src/pages/Inventory/Inventory.tsx new file mode 100644 index 0000000..8fa13e7 --- /dev/null +++ b/src/pages/Inventory/Inventory.tsx @@ -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 ( +
+
+

Component Inventory

+ ← Back to app +
+ +
+ + +
+ +
+
+
+ ) +} diff --git a/src/pages/Inventory/sections/PrimitivesSection.module.css b/src/pages/Inventory/sections/PrimitivesSection.module.css new file mode 100644 index 0000000..24d9c69 --- /dev/null +++ b/src/pages/Inventory/sections/PrimitivesSection.module.css @@ -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; +} diff --git a/src/pages/Inventory/sections/PrimitivesSection.tsx b/src/pages/Inventory/sections/PrimitivesSection.tsx new file mode 100644 index 0000000..148bb60 --- /dev/null +++ b/src/pages/Inventory/sections/PrimitivesSection.tsx @@ -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 ( +
+

{title}

+

{description}

+
{children}
+
+ ) +} + +// ── 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(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 ( +
+

Primitives

+ + {/* 1. Alert */} + +
+ This is an informational message. + Operation completed successfully. + This action may have side effects. + {!alertDismissed && ( + setAlertDismissed(true)} + > + Something went wrong. Dismiss to clear. + + )} + {alertDismissed && ( + + )} +
+
+ + {/* 2. Avatar */} + + + + + + + {/* 3. Badge */} + + + + + + + + + + + {/* 4. Button */} + +
+
+ + + + +
+
+ + + +
+
+
+ + {/* 5. Card */} + +
Plain card
+
Amber accent
+
Success accent
+
Error accent
+
+ + {/* 6. Checkbox */} + + setChecked1(e.target.checked)} + /> + setChecked2(e.target.checked)} + /> + + + + + {/* 7. CodeBlock */} + +
+ +
+
+ + {/* 8. Collapsible */} + +
+ +

Hidden content revealed on expand.

+
+ +

This content is visible from the start.

+
+
+
+ + {/* 9. DateTimePicker */} + + d && setDtValue(d)} + /> + + + {/* 10. DateRangePicker */} + +
+ +
+
+ + {/* 11. EmptyState */} + + 📭} + title="No results found" + description="Try adjusting your filters or search query." + action={} + /> + + + {/* 12. FilterPill */} + + + + + + + + {/* 13. FormField */} + +
+ + + + + + +
+
+ + {/* 14. InfoCallout */} + +
+ Review before publishing. + Deployment completed. + Rate limit approaching. + Build failed — check logs. +
+
+ + {/* 15. Input */} + + + + + + {/* 16. KeyboardHint */} + + + + + + + {/* 17. Label */} + + + + + + {/* 18. MonoText */} + + xs: route-id-001 + sm: route-id-001 + md: route-id-001 + + + {/* 19. Pagination */} + + + + + {/* 20. ProgressBar */} + +
+ + + + + + + +
+
+ + {/* 21. Radio */} + + + + + + + + + + + + + + {/* 22. SectionHeader */} + +
+ Without action + Add item}> + With action + +
+
+ + {/* 23. Select */} + +