599 lines
22 KiB
TypeScript
599 lines
22 KiB
TypeScript
|
|
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 (
|
||
|
|
<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>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── Toast demo inner component (must be inside ToastProvider) ─────────────────
|
||
|
|
|
||
|
|
function ToastDemo() {
|
||
|
|
const { toast } = useToast()
|
||
|
|
return (
|
||
|
|
<div className={styles.demoAreaRow}>
|
||
|
|
<Button size="sm" variant="primary" onClick={() => toast({ title: 'Success!', description: 'Operation completed.', variant: 'success' })}>
|
||
|
|
Success toast
|
||
|
|
</Button>
|
||
|
|
<Button size="sm" variant="secondary" onClick={() => toast({ title: 'Info', description: 'Something to note.', variant: 'info' })}>
|
||
|
|
Info toast
|
||
|
|
</Button>
|
||
|
|
<Button size="sm" variant="danger" onClick={() => toast({ title: 'Error', description: 'Something went wrong.', variant: 'error' })}>
|
||
|
|
Error toast
|
||
|
|
</Button>
|
||
|
|
<Button size="sm" variant="ghost" onClick={() => toast({ title: 'Warning', description: 'Proceed with caution.', variant: 'warning' })}>
|
||
|
|
Warning toast
|
||
|
|
</Button>
|
||
|
|
</div>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
|
||
|
|
// ── 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<string | undefined>('proc1')
|
||
|
|
|
||
|
|
return (
|
||
|
|
<ToastProvider>
|
||
|
|
<section id="composites" className={styles.section}>
|
||
|
|
<h2 className={styles.sectionTitle}>Composites</h2>
|
||
|
|
|
||
|
|
{/* 1. Accordion */}
|
||
|
|
<DemoCard
|
||
|
|
id="accordion"
|
||
|
|
title="Accordion"
|
||
|
|
description="Collapsible panels with single or multiple open mode."
|
||
|
|
>
|
||
|
|
<div className={styles.demoAreaColumn} style={{ width: '100%' }}>
|
||
|
|
<span className={styles.demoLabel}>Single mode (default)</span>
|
||
|
|
<Accordion items={accordionItems} />
|
||
|
|
<span className={styles.demoLabel}>Multiple mode</span>
|
||
|
|
<Accordion
|
||
|
|
items={[
|
||
|
|
{ id: 'm1', title: 'Section A', content: 'Content for section A.' },
|
||
|
|
{ id: 'm2', title: 'Section B', content: 'Content for section B.', defaultOpen: true },
|
||
|
|
{ id: 'm3', title: 'Section C', content: 'Content for section C.', defaultOpen: true },
|
||
|
|
]}
|
||
|
|
multiple
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
</DemoCard>
|
||
|
|
|
||
|
|
{/* 2. AlertDialog */}
|
||
|
|
<DemoCard
|
||
|
|
id="alertdialog"
|
||
|
|
title="AlertDialog"
|
||
|
|
description="Modal confirmation dialog in danger, warning, and info variants."
|
||
|
|
>
|
||
|
|
<div className={styles.demoAreaRow}>
|
||
|
|
<Button
|
||
|
|
size="sm"
|
||
|
|
variant="danger"
|
||
|
|
onClick={() => { setAlertVariant('danger'); setAlertOpen(true) }}
|
||
|
|
>
|
||
|
|
Danger dialog
|
||
|
|
</Button>
|
||
|
|
<Button
|
||
|
|
size="sm"
|
||
|
|
variant="secondary"
|
||
|
|
onClick={() => { setAlertVariant('warning'); setAlertOpen(true) }}
|
||
|
|
>
|
||
|
|
Warning dialog
|
||
|
|
</Button>
|
||
|
|
<Button
|
||
|
|
size="sm"
|
||
|
|
variant="ghost"
|
||
|
|
onClick={() => { setAlertVariant('info'); setAlertOpen(true) }}
|
||
|
|
>
|
||
|
|
Info dialog
|
||
|
|
</Button>
|
||
|
|
</div>
|
||
|
|
<AlertDialog
|
||
|
|
open={alertOpen}
|
||
|
|
onClose={() => 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'}
|
||
|
|
/>
|
||
|
|
</DemoCard>
|
||
|
|
|
||
|
|
{/* 3. AreaChart */}
|
||
|
|
<DemoCard
|
||
|
|
id="areachart"
|
||
|
|
title="AreaChart"
|
||
|
|
description="SVG area chart with hover tooltip, threshold line, and legend."
|
||
|
|
>
|
||
|
|
<AreaChart series={CHART_SERIES} xLabel="Time (minutes)" yLabel="Count" width={420} height={180} />
|
||
|
|
</DemoCard>
|
||
|
|
|
||
|
|
{/* 4. AvatarGroup */}
|
||
|
|
<DemoCard
|
||
|
|
id="avatargroup"
|
||
|
|
title="AvatarGroup"
|
||
|
|
description="Stacked avatar group showing up to max avatars with an overflow count."
|
||
|
|
>
|
||
|
|
<div className={styles.demoAreaColumn}>
|
||
|
|
<span className={styles.demoLabel}>max=3, size=sm</span>
|
||
|
|
<AvatarGroup names={['Alice Johnson', 'Bob Smith', 'Carol White', 'David Lee', 'Eve Brown']} max={3} size="sm" />
|
||
|
|
<span className={styles.demoLabel}>max=4, size=md</span>
|
||
|
|
<AvatarGroup names={['Alice Johnson', 'Bob Smith', 'Carol White', 'David Lee', 'Eve Brown']} max={4} size="md" />
|
||
|
|
<span className={styles.demoLabel}>max=2, size=lg</span>
|
||
|
|
<AvatarGroup names={['Alice Johnson', 'Bob Smith', 'Carol White', 'David Lee', 'Eve Brown']} max={2} size="lg" />
|
||
|
|
</div>
|
||
|
|
</DemoCard>
|
||
|
|
|
||
|
|
{/* 5. BarChart */}
|
||
|
|
<DemoCard
|
||
|
|
id="barchart"
|
||
|
|
title="BarChart"
|
||
|
|
description="Bar chart with grouped and stacked modes, hover tooltips, and legend."
|
||
|
|
>
|
||
|
|
<div className={styles.demoAreaColumn}>
|
||
|
|
<span className={styles.demoLabel}>Grouped</span>
|
||
|
|
<BarChart series={BAR_SERIES} width={420} height={180} xLabel="Day" />
|
||
|
|
<span className={styles.demoLabel}>Stacked</span>
|
||
|
|
<BarChart series={BAR_SERIES} stacked width={420} height={180} xLabel="Day" />
|
||
|
|
</div>
|
||
|
|
</DemoCard>
|
||
|
|
|
||
|
|
{/* 6. Breadcrumb */}
|
||
|
|
<DemoCard
|
||
|
|
id="breadcrumb"
|
||
|
|
title="Breadcrumb"
|
||
|
|
description="Slash-separated navigation breadcrumb with linked and plain segments."
|
||
|
|
>
|
||
|
|
<Breadcrumb
|
||
|
|
items={[
|
||
|
|
{ label: 'Dashboard', href: '#' },
|
||
|
|
{ label: 'Applications', href: '#' },
|
||
|
|
{ label: 'order-ingest' },
|
||
|
|
]}
|
||
|
|
/>
|
||
|
|
</DemoCard>
|
||
|
|
|
||
|
|
{/* 7. CommandPalette */}
|
||
|
|
<DemoCard
|
||
|
|
id="commandpalette"
|
||
|
|
title="CommandPalette"
|
||
|
|
description="Full-screen search palette with category tabs, keyboard navigation, and highlight."
|
||
|
|
>
|
||
|
|
<Button size="sm" variant="secondary" onClick={() => setCmdOpen(true)}>
|
||
|
|
Open CommandPalette
|
||
|
|
</Button>
|
||
|
|
<CommandPalette
|
||
|
|
open={cmdOpen}
|
||
|
|
onClose={() => setCmdOpen(false)}
|
||
|
|
onSelect={() => setCmdOpen(false)}
|
||
|
|
data={COMMAND_PALETTE_DATA}
|
||
|
|
/>
|
||
|
|
</DemoCard>
|
||
|
|
|
||
|
|
{/* 8. DataTable */}
|
||
|
|
<DemoCard
|
||
|
|
id="datatable"
|
||
|
|
title="DataTable"
|
||
|
|
description="Sortable, paginated table with row click, accent rows, and page size selector."
|
||
|
|
>
|
||
|
|
<div style={{ width: '100%' }}>
|
||
|
|
<DataTable
|
||
|
|
columns={tableColumns}
|
||
|
|
data={TABLE_DATA}
|
||
|
|
sortable
|
||
|
|
pageSize={5}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
</DemoCard>
|
||
|
|
|
||
|
|
{/* 9. DetailPanel */}
|
||
|
|
<DemoCard
|
||
|
|
id="detailpanel"
|
||
|
|
title="DetailPanel"
|
||
|
|
description="Slide-in side panel with tabbed content and close button."
|
||
|
|
>
|
||
|
|
<Button size="sm" variant="secondary" onClick={() => setPanelOpen(true)}>
|
||
|
|
Open DetailPanel
|
||
|
|
</Button>
|
||
|
|
<DetailPanel
|
||
|
|
open={panelOpen}
|
||
|
|
onClose={() => setPanelOpen(false)}
|
||
|
|
title="Route: order-ingest"
|
||
|
|
tabs={[
|
||
|
|
{ label: 'Overview', value: 'overview', content: <div style={{ padding: '12px 0', fontSize: 13 }}>Route processes ~1,243 exchanges/day with avg latency 42ms.</div> },
|
||
|
|
{ label: 'Processors', value: 'processors', content: <div style={{ padding: '12px 0', fontSize: 13 }}>ValidateOrder → EnrichPayload → RouteToQueue</div> },
|
||
|
|
{ label: 'Errors', value: 'errors', content: <div style={{ padding: '12px 0', fontSize: 13 }}>3 errors in last 24h. Last: timeout at EnrichPayload.</div> },
|
||
|
|
]}
|
||
|
|
/>
|
||
|
|
</DemoCard>
|
||
|
|
|
||
|
|
{/* 10. Dropdown */}
|
||
|
|
<DemoCard
|
||
|
|
id="dropdown"
|
||
|
|
title="Dropdown"
|
||
|
|
description="Click-triggered dropdown menu with icons, dividers, and disabled items."
|
||
|
|
>
|
||
|
|
<Dropdown
|
||
|
|
trigger={<Button size="sm" variant="secondary">Actions ▾</Button>}
|
||
|
|
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 },
|
||
|
|
]}
|
||
|
|
/>
|
||
|
|
</DemoCard>
|
||
|
|
|
||
|
|
{/* 11. EventFeed */}
|
||
|
|
<DemoCard
|
||
|
|
id="eventfeed"
|
||
|
|
title="EventFeed"
|
||
|
|
description="Scrollable live event log with severity filters and auto-scroll."
|
||
|
|
>
|
||
|
|
<div style={{ width: '100%' }}>
|
||
|
|
<EventFeed events={FEED_EVENTS} maxItems={10} />
|
||
|
|
</div>
|
||
|
|
</DemoCard>
|
||
|
|
|
||
|
|
{/* 12. FilterBar */}
|
||
|
|
<DemoCard
|
||
|
|
id="filterbar"
|
||
|
|
title="FilterBar"
|
||
|
|
description="Search input combined with filter pills and active filter tag chips."
|
||
|
|
>
|
||
|
|
<div style={{ width: '100%' }}>
|
||
|
|
<FilterBar
|
||
|
|
filters={filterOptions}
|
||
|
|
activeFilters={activeFilters}
|
||
|
|
onFilterChange={setActiveFilters}
|
||
|
|
searchPlaceholder="Search routes..."
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
</DemoCard>
|
||
|
|
|
||
|
|
{/* 13. LineChart */}
|
||
|
|
<DemoCard
|
||
|
|
id="linechart"
|
||
|
|
title="LineChart"
|
||
|
|
description="Multi-series SVG line chart with hover crosshair, tooltip, and legend."
|
||
|
|
>
|
||
|
|
<LineChart series={CHART_SERIES} xLabel="Time (minutes)" yLabel="Count" width={420} height={180} />
|
||
|
|
</DemoCard>
|
||
|
|
|
||
|
|
{/* 14. MenuItem */}
|
||
|
|
<DemoCard
|
||
|
|
id="menuitem"
|
||
|
|
title="MenuItem"
|
||
|
|
description="Navigation menu row with health dot, meta text, count badge, and active state."
|
||
|
|
>
|
||
|
|
<div className={styles.demoAreaColumn} style={{ minWidth: 200 }}>
|
||
|
|
<MenuItem label="order-ingest" meta="POST route" count={1243} health="live" />
|
||
|
|
<MenuItem label="payment-validate" meta="POST route" count={987} health="live" active />
|
||
|
|
<MenuItem label="inventory-check" meta="GET route" count={432} health="stale" />
|
||
|
|
<MenuItem label="archive-order" meta="PUT route" count={54} health="dead" indent={1} />
|
||
|
|
</div>
|
||
|
|
</DemoCard>
|
||
|
|
|
||
|
|
{/* 15. Modal */}
|
||
|
|
<DemoCard
|
||
|
|
id="modal"
|
||
|
|
title="Modal"
|
||
|
|
description="Portal-rendered modal dialog in sm, md, and lg sizes with backdrop dismiss."
|
||
|
|
>
|
||
|
|
<Button size="sm" variant="secondary" onClick={() => setModalOpen(true)}>
|
||
|
|
Open Modal
|
||
|
|
</Button>
|
||
|
|
<Modal
|
||
|
|
open={modalOpen}
|
||
|
|
onClose={() => setModalOpen(false)}
|
||
|
|
title="Configure Route"
|
||
|
|
size="md"
|
||
|
|
>
|
||
|
|
<div style={{ fontSize: 13, lineHeight: 1.6 }}>
|
||
|
|
<p style={{ margin: '0 0 12px' }}>Adjust the route settings below. Changes will take effect immediately after saving.</p>
|
||
|
|
<p style={{ margin: 0, color: 'var(--text-muted)' }}>Route: order-ingest · Processor chain: 3 steps · Avg latency: 42ms</p>
|
||
|
|
</div>
|
||
|
|
</Modal>
|
||
|
|
</DemoCard>
|
||
|
|
|
||
|
|
{/* 16. Popover */}
|
||
|
|
<DemoCard
|
||
|
|
id="popover"
|
||
|
|
title="Popover"
|
||
|
|
description="Portal-positioned popover in all four positions with arrow indicator."
|
||
|
|
>
|
||
|
|
<div className={styles.demoAreaRow} style={{ paddingTop: 8, paddingBottom: 8 }}>
|
||
|
|
<Popover
|
||
|
|
position="top"
|
||
|
|
trigger={<Button size="sm" variant="secondary">Top</Button>}
|
||
|
|
content={<div style={{ padding: '8px 12px', fontSize: 12 }}>Popover on top</div>}
|
||
|
|
/>
|
||
|
|
<Popover
|
||
|
|
position="bottom"
|
||
|
|
trigger={<Button size="sm" variant="secondary">Bottom</Button>}
|
||
|
|
content={<div style={{ padding: '8px 12px', fontSize: 12 }}>Popover on bottom</div>}
|
||
|
|
/>
|
||
|
|
<Popover
|
||
|
|
position="left"
|
||
|
|
trigger={<Button size="sm" variant="secondary">Left</Button>}
|
||
|
|
content={<div style={{ padding: '8px 12px', fontSize: 12 }}>Popover on left</div>}
|
||
|
|
/>
|
||
|
|
<Popover
|
||
|
|
position="right"
|
||
|
|
trigger={<Button size="sm" variant="secondary">Right</Button>}
|
||
|
|
content={<div style={{ padding: '8px 12px', fontSize: 12 }}>Popover on right</div>}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
</DemoCard>
|
||
|
|
|
||
|
|
{/* 17. ProcessorTimeline */}
|
||
|
|
<DemoCard
|
||
|
|
id="processortimeline"
|
||
|
|
title="ProcessorTimeline"
|
||
|
|
description="Horizontal Gantt-style timeline showing processor execution order, duration, and status."
|
||
|
|
>
|
||
|
|
<div style={{ width: '100%' }}>
|
||
|
|
<ProcessorTimeline
|
||
|
|
totalMs={120}
|
||
|
|
processors={[
|
||
|
|
{ name: 'ValidateOrder', type: 'validator', durationMs: 12, status: 'ok', startMs: 0 },
|
||
|
|
{ name: 'EnrichPayload', type: 'enricher', durationMs: 35, status: 'slow', startMs: 12 },
|
||
|
|
{ name: 'RouteToQueue', type: 'router', durationMs: 8, status: 'ok', startMs: 47 },
|
||
|
|
{ name: 'AuditLog', type: 'logger', durationMs: 65, status: 'fail', startMs: 55 },
|
||
|
|
]}
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
</DemoCard>
|
||
|
|
|
||
|
|
{/* 18. ShortcutsBar */}
|
||
|
|
<DemoCard
|
||
|
|
id="shortcutsbar"
|
||
|
|
title="ShortcutsBar"
|
||
|
|
description="Row of keyboard shortcut hints, each pairing a key badge with a label."
|
||
|
|
>
|
||
|
|
<ShortcutsBar
|
||
|
|
shortcuts={[
|
||
|
|
{ keys: 'Ctrl+K', label: 'Search' },
|
||
|
|
{ keys: '↑↓', label: 'Navigate' },
|
||
|
|
{ keys: 'Enter', label: 'Open' },
|
||
|
|
{ keys: 'Esc', label: 'Close' },
|
||
|
|
{ keys: '?', label: 'Help' },
|
||
|
|
]}
|
||
|
|
/>
|
||
|
|
</DemoCard>
|
||
|
|
|
||
|
|
{/* 19. Tabs */}
|
||
|
|
<DemoCard
|
||
|
|
id="tabs"
|
||
|
|
title="Tabs"
|
||
|
|
description="Tab bar with optional count badges and active indicator."
|
||
|
|
>
|
||
|
|
<div className={styles.demoAreaColumn} style={{ width: '100%' }}>
|
||
|
|
<Tabs tabs={tabItems} active={activeTab} onChange={setActiveTab} />
|
||
|
|
<div style={{ fontSize: 13, color: 'var(--text-muted)' }}>
|
||
|
|
Active tab: <strong>{activeTab}</strong>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</DemoCard>
|
||
|
|
|
||
|
|
{/* 20. Toast */}
|
||
|
|
<DemoCard
|
||
|
|
id="toast"
|
||
|
|
title="Toast"
|
||
|
|
description="Stacked portal toast notifications in four variants with auto-dismiss and manual close."
|
||
|
|
>
|
||
|
|
<ToastDemo />
|
||
|
|
</DemoCard>
|
||
|
|
|
||
|
|
{/* 21. TreeView */}
|
||
|
|
<DemoCard
|
||
|
|
id="treeview"
|
||
|
|
title="TreeView"
|
||
|
|
description="Keyboard-navigable tree showing App → Routes → Processors hierarchy."
|
||
|
|
>
|
||
|
|
<TreeView
|
||
|
|
nodes={TREE_NODES}
|
||
|
|
selectedId={selectedNode}
|
||
|
|
onSelect={setSelectedNode}
|
||
|
|
/>
|
||
|
|
</DemoCard>
|
||
|
|
</section>
|
||
|
|
</ToastProvider>
|
||
|
|
)
|
||
|
|
}
|