diff --git a/src/pages/Inventory/Inventory.tsx b/src/pages/Inventory/Inventory.tsx index 8fa13e7..8aff9fe 100644 --- a/src/pages/Inventory/Inventory.tsx +++ b/src/pages/Inventory/Inventory.tsx @@ -1,6 +1,8 @@ import { Link } from 'react-router-dom' import styles from './Inventory.module.css' import { PrimitivesSection } from './sections/PrimitivesSection' +import { CompositesSection } from './sections/CompositesSection' +import { LayoutSection } from './sections/LayoutSection' const NAV_ITEMS = [ { label: 'Primitives', href: '#primitives' }, @@ -30,6 +32,8 @@ export function Inventory() {
+ +
diff --git a/src/pages/Inventory/sections/CompositesSection.module.css b/src/pages/Inventory/sections/CompositesSection.module.css new file mode 100644 index 0000000..24d9c69 --- /dev/null +++ b/src/pages/Inventory/sections/CompositesSection.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/CompositesSection.tsx b/src/pages/Inventory/sections/CompositesSection.tsx new file mode 100644 index 0000000..f5a0cd9 --- /dev/null +++ b/src/pages/Inventory/sections/CompositesSection.tsx @@ -0,0 +1,598 @@ +import { useState } from 'react' +import styles from './CompositesSection.module.css' +import { + Accordion, + AlertDialog, + AreaChart, + AvatarGroup, + BarChart, + Breadcrumb, + CommandPalette, + DataTable, + DetailPanel, + Dropdown, + EventFeed, + FilterBar, + LineChart, + MenuItem, + Modal, + Popover, + ProcessorTimeline, + ShortcutsBar, + Tabs, + ToastProvider, + useToast, + TreeView, +} from '../../../design-system/composites' +import type { SearchResult } from '../../../design-system/composites' +import { Button } from '../../../design-system/primitives' + +// ── DemoCard helper ────────────────────────────────────────────────────────── + +interface DemoCardProps { + id: string + title: string + description: string + children: React.ReactNode +} + +function DemoCard({ id, title, description, children }: DemoCardProps) { + return ( +
+

{title}

+

{description}

+
{children}
+
+ ) +} + +// ── Toast demo inner component (must be inside ToastProvider) ───────────────── + +function ToastDemo() { + const { toast } = useToast() + return ( +
+ + + + +
+ ) +} + +// ── Sample data ─────────────────────────────────────────────────────────────── + +const CHART_SERIES = [ + { + label: 'Requests', + data: [ + { x: 0, y: 120 }, { x: 1, y: 180 }, { x: 2, y: 150 }, + { x: 3, y: 210 }, { x: 4, y: 190 }, { x: 5, y: 240 }, + ], + }, + { + label: 'Errors', + data: [ + { x: 0, y: 5 }, { x: 1, y: 12 }, { x: 2, y: 8 }, + { x: 3, y: 15 }, { x: 4, y: 7 }, { x: 5, y: 10 }, + ], + color: 'var(--error)', + }, +] + +const BAR_SERIES = [ + { + label: 'GET', + data: [ + { x: 'Mon', y: 80 }, { x: 'Tue', y: 95 }, { x: 'Wed', y: 110 }, + { x: 'Thu', y: 72 }, { x: 'Fri', y: 130 }, + ], + }, + { + label: 'POST', + data: [ + { x: 'Mon', y: 40 }, { x: 'Tue', y: 55 }, { x: 'Wed', y: 60 }, + { x: 'Thu', y: 38 }, { x: 'Fri', y: 75 }, + ], + color: 'var(--success)', + }, +] + +const COMMAND_PALETTE_DATA: SearchResult[] = [ + { id: 'r1', category: 'route', title: 'order-ingest', meta: 'POST /orders/ingest' }, + { id: 'r2', category: 'route', title: 'payment-validate', meta: 'POST /payments/validate' }, + { id: 'e1', category: 'execution', title: 'exec-001', meta: 'Started 2m ago' }, + { id: 'e2', category: 'execution', title: 'exec-002', meta: 'Completed 5m ago' }, + { id: 'a1', category: 'agent', title: 'camel-agent-prod-1', meta: 'live · 42 tps' }, + { id: 'x1', category: 'exchange', title: 'exch-aabb1122', meta: 'route: order-ingest' }, +] + +interface TableRow { + id: string + name: string + method: string + status: string + exchanges: number +} + +const TABLE_DATA: TableRow[] = [ + { id: '1', name: 'order-ingest', method: 'POST', status: 'live', exchanges: 1243 }, + { id: '2', name: 'payment-validate', method: 'POST', status: 'live', exchanges: 987 }, + { id: '3', name: 'inventory-check', method: 'GET', status: 'stale', exchanges: 432 }, + { id: '4', name: 'notify-customer', method: 'POST', status: 'live', exchanges: 876 }, + { id: '5', name: 'archive-order', method: 'PUT', status: 'dead', exchanges: 54 }, +] + +const NOW = new Date() +const minsAgo = (n: number) => new Date(NOW.getTime() - n * 60 * 1000) + +const FEED_EVENTS = [ + { id: 'ev1', severity: 'success' as const, message: 'Route order-ingest started successfully', timestamp: minsAgo(1) }, + { id: 'ev2', severity: 'warning' as const, message: 'Agent camel-agent-prod-2 response time elevated', timestamp: minsAgo(3) }, + { id: 'ev3', severity: 'error' as const, message: 'Exchange exch-aabb1122 failed: timeout', timestamp: minsAgo(7) }, + { id: 'ev4', severity: 'running' as const, message: 'Processor payment-validate processing batch', timestamp: minsAgo(10) }, + { id: 'ev5', severity: 'success' as const, message: 'Deployment v3.2.1 completed', timestamp: minsAgo(15) }, +] + +const TREE_NODES = [ + { + id: 'app1', + label: 'cameleer-prod', + icon: '⬡', + children: [ + { + id: 'route1', + label: 'order-ingest', + icon: '→', + children: [ + { id: 'proc1', label: 'ValidateOrder', icon: '◈', meta: '12ms' }, + { id: 'proc2', label: 'EnrichPayload', icon: '◈', meta: '8ms' }, + { id: 'proc3', label: 'RouteToQueue', icon: '◈', meta: '3ms' }, + ], + }, + { + id: 'route2', + label: 'payment-validate', + icon: '→', + children: [ + { id: 'proc4', label: 'TokenizeCard', icon: '◈', meta: '22ms' }, + { id: 'proc5', label: 'AuthorizePayment', icon: '◈', meta: '45ms' }, + ], + }, + ], + }, +] + +// ── CompositesSection ───────────────────────────────────────────────────────── + +export function CompositesSection() { + // 1. Accordion + const accordionItems = [ + { id: 'a1', title: 'What is Apache Camel?', content: 'Apache Camel is an open-source integration framework based on enterprise integration patterns.' }, + { id: 'a2', title: 'How do routes work?', content: 'Routes define the path a message takes through the system, from consumer to producer.', defaultOpen: true }, + { id: 'a3', title: 'What are processors?', content: 'Processors transform, filter, enrich, or route messages as they flow through a route.' }, + ] + + // 2. AlertDialog + const [alertOpen, setAlertOpen] = useState(false) + const [alertVariant, setAlertVariant] = useState<'danger' | 'warning' | 'info'>('danger') + + // 7. CommandPalette + const [cmdOpen, setCmdOpen] = useState(false) + + // 8. DataTable + const tableColumns = [ + { key: 'name', header: 'Route', sortable: true }, + { key: 'method', header: 'Method', sortable: true }, + { key: 'status', header: 'Status', sortable: true }, + { key: 'exchanges', header: 'Exchanges', sortable: true }, + ] + + // 9. DetailPanel + const [panelOpen, setPanelOpen] = useState(false) + + // 12. FilterBar + const filterOptions = [ + { label: 'Live', value: 'live', color: 'success' as const, count: 12 }, + { label: 'Stale', value: 'stale', count: 3 }, + { label: 'Dead', value: 'dead', color: 'error' as const, count: 1 }, + ] + const [activeFilters, setActiveFilters] = useState([{ label: 'Live', value: 'live' }]) + + // 15. Modal + const [modalOpen, setModalOpen] = useState(false) + + // 19. Tabs + const tabItems = [ + { label: 'Overview', value: 'overview', count: undefined }, + { label: 'Routes', value: 'routes', count: 14 }, + { label: 'Agents', value: 'agents', count: 6 }, + ] + const [activeTab, setActiveTab] = useState('overview') + + // 21. TreeView + const [selectedNode, setSelectedNode] = useState('proc1') + + return ( + +
+

Composites

+ + {/* 1. Accordion */} + +
+ Single mode (default) + + Multiple mode + +
+
+ + {/* 2. AlertDialog */} + +
+ + + +
+ setAlertOpen(false)} + onConfirm={() => setAlertOpen(false)} + title={alertVariant === 'danger' ? 'Delete route?' : alertVariant === 'warning' ? 'Proceed with caution?' : 'Confirm action'} + description={ + alertVariant === 'danger' + ? 'This will permanently delete the route and all its exchange history. This action cannot be undone.' + : alertVariant === 'warning' + ? 'This operation will restart all active processors. Running exchanges may be interrupted.' + : 'This will update the route configuration and apply changes immediately.' + } + variant={alertVariant} + confirmLabel={alertVariant === 'danger' ? 'Delete' : 'Confirm'} + /> +
+ + {/* 3. AreaChart */} + + + + + {/* 4. AvatarGroup */} + +
+ max=3, size=sm + + max=4, size=md + + max=2, size=lg + +
+
+ + {/* 5. BarChart */} + +
+ Grouped + + Stacked + +
+
+ + {/* 6. Breadcrumb */} + + + + + {/* 7. CommandPalette */} + + + setCmdOpen(false)} + onSelect={() => setCmdOpen(false)} + data={COMMAND_PALETTE_DATA} + /> + + + {/* 8. DataTable */} + +
+ +
+
+ + {/* 9. DetailPanel */} + + + setPanelOpen(false)} + title="Route: order-ingest" + tabs={[ + { label: 'Overview', value: 'overview', content:
Route processes ~1,243 exchanges/day with avg latency 42ms.
}, + { label: 'Processors', value: 'processors', content:
ValidateOrder → EnrichPayload → RouteToQueue
}, + { label: 'Errors', value: 'errors', content:
3 errors in last 24h. Last: timeout at EnrichPayload.
}, + ]} + /> +
+ + {/* 10. Dropdown */} + + Actions ▾} + items={[ + { label: 'View details', icon: '👁', onClick: () => undefined }, + { label: 'Edit route', icon: '✏', onClick: () => undefined }, + { divider: true, label: '' }, + { label: 'Restart', icon: '↺', onClick: () => undefined }, + { label: 'Delete', icon: '✕', onClick: () => undefined, disabled: true }, + ]} + /> + + + {/* 11. EventFeed */} + +
+ +
+
+ + {/* 12. FilterBar */} + +
+ +
+
+ + {/* 13. LineChart */} + + + + + {/* 14. MenuItem */} + +
+ + + + +
+
+ + {/* 15. Modal */} + + + setModalOpen(false)} + title="Configure Route" + size="md" + > +
+

Adjust the route settings below. Changes will take effect immediately after saving.

+

Route: order-ingest · Processor chain: 3 steps · Avg latency: 42ms

+
+
+
+ + {/* 16. Popover */} + +
+ Top} + content={
Popover on top
} + /> + Bottom} + content={
Popover on bottom
} + /> + Left} + content={
Popover on left
} + /> + Right} + content={
Popover on right
} + /> +
+
+ + {/* 17. ProcessorTimeline */} + +
+ +
+
+ + {/* 18. ShortcutsBar */} + + + + + {/* 19. Tabs */} + +
+ +
+ Active tab: {activeTab} +
+
+
+ + {/* 20. Toast */} + + + + + {/* 21. TreeView */} + + + +
+
+ ) +} diff --git a/src/pages/Inventory/sections/LayoutSection.module.css b/src/pages/Inventory/sections/LayoutSection.module.css new file mode 100644 index 0000000..5f15c00 --- /dev/null +++ b/src/pages/Inventory/sections/LayoutSection.module.css @@ -0,0 +1,139 @@ +.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; +} + +/* AppShell diagram */ +.shellDiagram { + width: 100%; + background: var(--bg-canvas); + border: 1px solid var(--border); + border-radius: var(--radius-sm); + overflow: hidden; + font-size: 11px; + color: var(--text-muted); +} + +.shellDiagramTop { + border-bottom: 1px solid var(--border); + padding: 8px 12px; + background: var(--bg-overlay); + font-weight: 600; + color: var(--text-secondary); +} + +.shellDiagramBody { + display: flex; + height: 140px; +} + +.shellDiagramSide { + width: 140px; + border-right: 1px solid var(--border); + background: var(--bg-overlay); + padding: 8px 12px; + display: flex; + flex-direction: column; + gap: 4px; + font-weight: 600; + color: var(--text-secondary); + flex-shrink: 0; +} + +.shellDiagramMain { + flex: 1; + padding: 8px 12px; + display: flex; + align-items: center; + justify-content: center; + color: var(--text-muted); + font-style: italic; +} + +/* Sidebar preview container */ +.sidebarPreview { + width: 220px; + height: 400px; + border: 1px solid var(--border); + border-radius: var(--radius-md); + overflow: hidden; + flex-shrink: 0; +} + +/* TopBar preview container */ +.topbarPreview { + width: 100%; + border: 1px solid var(--border); + border-radius: var(--radius-md); + overflow: hidden; +} diff --git a/src/pages/Inventory/sections/LayoutSection.tsx b/src/pages/Inventory/sections/LayoutSection.tsx new file mode 100644 index 0000000..411a52a --- /dev/null +++ b/src/pages/Inventory/sections/LayoutSection.tsx @@ -0,0 +1,140 @@ +import styles from './LayoutSection.module.css' +import { Sidebar } from '../../../design-system/layout/Sidebar/Sidebar' +import { TopBar } from '../../../design-system/layout/TopBar/TopBar' + +// ── DemoCard helper ────────────────────────────────────────────────────────── + +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 SAMPLE_APPS = [ + { id: 'app1', name: 'cameleer-prod', agentCount: 3, health: 'live' as const, execCount: 14320 }, + { id: 'app2', name: 'cameleer-staging', agentCount: 2, health: 'stale' as const, execCount: 871 }, + { id: 'app3', name: 'cameleer-dev', agentCount: 1, health: 'dead' as const, execCount: 42 }, +] + +const SAMPLE_ROUTES = [ + { id: 'r1', name: 'order-ingest', execCount: 5421 }, + { id: 'r2', name: 'payment-validate', execCount: 3102 }, + { id: 'r3', name: 'notify-customer', execCount: 2201 }, +] + +const SAMPLE_AGENTS = [ + { + id: 'ag1', + name: 'agent-prod-1', + service: 'camel-core', + version: 'v3.2.1', + tps: '42 tps', + lastSeen: '1m ago', + status: 'live' as const, + }, + { + id: 'ag2', + name: 'agent-prod-2', + service: 'camel-core', + version: 'v3.2.1', + tps: '38 tps', + lastSeen: '2m ago', + status: 'live' as const, + errorRate: '0.4%', + }, + { + id: 'ag3', + name: 'agent-staging-1', + service: 'camel-core', + version: 'v3.1.9', + tps: '5 tps', + lastSeen: '8m ago', + status: 'stale' as const, + }, +] + +// ── LayoutSection ───────────────────────────────────────────────────────────── + +export function LayoutSection() { + return ( +
+

Layout

+ + {/* 1. AppShell */} + +
+
+ TopBar — breadcrumb · search · env badge · shift · user avatar +
+
+
+ Sidebar + Logo + Search + Navigation + Applications + Routes + Agents +
+
+ <children> — page content rendered here +
+
+
+
+ + {/* 2. Sidebar */} + +
+ +
+
+ + {/* 3. TopBar */} + +
+ undefined} + /> +
+
+
+ ) +}