@schandlergarcia/sf-web-components 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/.a4drules/features/command-center-dashboard-rule.md +46 -0
  2. package/.a4drules/skills/command-center-builder/SKILL.md +637 -0
  3. package/.a4drules/skills/command-center-project/SKILL.md +103 -0
  4. package/.a4drules/skills/component-library/SKILL.md +1025 -0
  5. package/.a4drules/skills/outer-app/SKILL.md +64 -0
  6. package/README.md +7 -7
  7. package/package.json +1 -1
  8. package/.a4drules/skills/building-data-visualization/SKILL.md +0 -72
  9. package/.a4drules/skills/building-data-visualization/implementation/bar-line-chart.md +0 -316
  10. package/.a4drules/skills/building-data-visualization/implementation/dashboard-layout.md +0 -189
  11. package/.a4drules/skills/building-data-visualization/implementation/donut-chart.md +0 -181
  12. package/.a4drules/skills/building-data-visualization/implementation/stat-card.md +0 -150
  13. package/.a4drules/skills/building-react-components/SKILL.md +0 -96
  14. package/.a4drules/skills/building-react-components/implementation/component.md +0 -78
  15. package/.a4drules/skills/building-react-components/implementation/header-footer.md +0 -132
  16. package/.a4drules/skills/building-react-components/implementation/page.md +0 -93
  17. package/.a4drules/skills/configuring-csp-trusted-sites/SKILL.md +0 -90
  18. package/.a4drules/skills/configuring-csp-trusted-sites/implementation/metadata-format.md +0 -281
  19. package/.a4drules/skills/configuring-webapp-metadata/SKILL.md +0 -158
  20. package/.a4drules/skills/creating-webapp/SKILL.md +0 -140
  21. package/.a4drules/skills/deploying-to-salesforce/SKILL.md +0 -226
  22. package/.a4drules/skills/implementing-file-upload/SKILL.md +0 -396
  23. package/.a4drules/skills/installing-webapp-features/SKILL.md +0 -210
  24. package/.a4drules/skills/managing-agentforce-conversation-client/SKILL.md +0 -186
  25. package/.a4drules/skills/managing-agentforce-conversation-client/references/constraints.md +0 -134
  26. package/.a4drules/skills/managing-agentforce-conversation-client/references/examples.md +0 -132
  27. package/.a4drules/skills/managing-agentforce-conversation-client/references/style-tokens.md +0 -101
  28. package/.a4drules/skills/managing-agentforce-conversation-client/references/troubleshooting.md +0 -57
  29. package/.a4drules/skills/using-salesforce-data/SKILL.md +0 -363
  30. package/.a4drules/skills/using-salesforce-data/graphql-search.sh +0 -139
  31. package/.a4drules/webapp-data.md +0 -353
  32. package/.a4drules/webapp-ui.md +0 -16
@@ -0,0 +1,1025 @@
1
+ ---
2
+ name: component-library
3
+ description: >-
4
+ Complete reference for the Command Center component library. Every component,
5
+ its exact props, data shapes, and when to use it. This is the authoritative
6
+ source — do not guess prop names or data formats.
7
+ ---
8
+
9
+ # Component Library
10
+
11
+ Location: `src/components/library/`. Barrel export: `@/components/library`.
12
+
13
+ Vendored from command-center-starter. Do not modify library files.
14
+
15
+ ## Import Pattern
16
+
17
+ ```jsx
18
+ // Named imports from the barrel (preferred)
19
+ import { MetricCard, ChartCard, TableCard, ListCard } from "@/components/library";
20
+
21
+ // Default exports that need direct import
22
+ import useDataSource from "@/components/library/data/useDataSource";
23
+ import { useThemeMode } from "@/components/library/theme/AppThemeProvider";
24
+ ```
25
+
26
+ ---
27
+
28
+ ## When to Use Each Component
29
+
30
+ | I need to show… | Use this | NOT this |
31
+ |---|---|---|
32
+ | A single KPI number | `MetricCard` | Custom div with big number |
33
+ | A row of 2–4 KPIs | Grid of `MetricCard` | `MetricsStrip` (for compact inline) |
34
+ | A data table | `TableCard` | `<table>` HTML |
35
+ | A scrollable item list | `ListCard` | Custom divs with `.map()` |
36
+ | An activity log with status icons | `ActivityCard` | ListCard (no status icons) |
37
+ | A chart (line, bar) | `ChartCard` + `D3Chart` | Raw `<svg>` |
38
+ | A donut/pie chart | `ChartCard` + `D3Chart` + custom renderChart | Any chart library |
39
+ | A world map with markers | `GeoMap` (direct, no ChartCard wrapper) | react-simple-maps |
40
+ | A callout/alert banner | `CalloutCard` | Custom colored div |
41
+ | A section heading | `SectionCard` | `<h2>` with custom styling |
42
+ | A multi-section panel | `WidgetCard` | Nested divs |
43
+ | Custom interactive content (expand/collapse, inline details) | `WidgetCard` (single section with arbitrary JSX) | Hand-rolled `<div className="bg-white border rounded...">` |
44
+ | System health status | `StatusCard` | Custom status badges |
45
+ | A sidebar feed | `FeedPanel` | Custom scrollable div |
46
+ | A command-palette AI chat | `ChatBar` | ChatPanel (that's for full-page) |
47
+ | A full-page AI chat | `ChatPanel` | ChatBar |
48
+ | Action buttons | `ActionList` or `UIButton` | Raw `<button>` |
49
+ | Loading placeholder | `CardSkeleton` | Custom skeleton divs |
50
+ | Empty state | `EmptyState` | Custom centered text |
51
+ | A form dialog | `FormModal` | Raw `Modal` + custom form |
52
+ | Inline form | `FormRenderer` + `useFormState` | Manual state + raw inputs |
53
+ | Filter bar with search/select/toggle | `FilterBar` | Custom filter UI |
54
+ | Progress indicator (linear) | `ProgressBar` | Custom div with width% |
55
+ | Progress indicator (circular) | `ProgressCircle` | Custom SVG circle |
56
+ | Collapsible sections | `Accordion` | Custom show/hide divs |
57
+ | Side panel / drawer | `Drawer` | Custom fixed-position div |
58
+ | Dropdown menu | `Dropdown` | Custom popover |
59
+ | Tooltip | `Tooltip` | Custom hover div |
60
+ | Toast notification | `toast()` function | Custom notification div |
61
+ | Keyboard shortcut display | `Kbd` | Custom `<kbd>` element |
62
+ | Scrollable container with fade | `ScrollShadow` | Custom overflow div |
63
+ | Tab switching inside a card | `Tabs` | Custom tab implementation |
64
+
65
+ ---
66
+
67
+ ## Card Components
68
+
69
+ ### MetricCard
70
+ **When:** Display a single KPI value (revenue, count, percentage).
71
+
72
+ ```jsx
73
+ <MetricCard
74
+ title="Active Travelers" // label
75
+ value={6} // main display value (any)
76
+ subtitle="Currently on trips" // secondary text
77
+ change="+5.2%" // change amount string
78
+ changeType="positive" // "positive" | "negative" | "neutral"
79
+ icon={<UsersIcon className="h-5 w-5 text-engine-teal" />}
80
+ color="default" // "default" | "primary" | "success" | "warning" | "danger"
81
+ trend="vs last month" // trend label text
82
+ layout="default" // "default" | "compact"
83
+ footer={<span>...</span>} // bottom content
84
+ actions={<button>...</button>} // top-right actions
85
+ loading={false}
86
+ />
87
+ ```
88
+ **Mistakes:** ❌ `changeValue` → use `change`. ❌ `trend="positive"` → `trend` is a label, use `changeType`.
89
+
90
+ ### ChartCard
91
+ **When:** Wrap a D3Chart visualization in a titled card.
92
+
93
+ ```jsx
94
+ <ChartCard
95
+ title="Spend Trend"
96
+ subtitle="Last 30 days"
97
+ height={280} // container height px (default 280)
98
+ chart={<D3Chart ... />} // ReactNode — the chart (REQUIRED, via prop not children)
99
+ legend={<div>...</div>} // below chart
100
+ chartType="Line Chart" // chip label
101
+ filters={<SelectFilter ... />} // header filters
102
+ timeRange={{ current: "30d", options: ["7d","30d","90d"], onChange: fn }}
103
+ />
104
+ ```
105
+ **Critical:** Pass chart via `chart={}` prop. Height sets CSS on container — match with D3Chart `height`.
106
+ **Do NOT use for GeoMap** — GeoMap renders directly without a card wrapper.
107
+ **Do NOT pass Recharts/Chart.js components** — only `D3Chart` is allowed.
108
+
109
+ ### TableCard
110
+ **When:** Display tabular data with sorting, search, pagination.
111
+
112
+ ```jsx
113
+ <TableCard
114
+ title="Travelers"
115
+ subtitle="6 active"
116
+ data={[{ id: "1", name: "Maya", cost: 2840, status: "Active" }]}
117
+ columns={[
118
+ { key: "name", label: "Name" },
119
+ { key: "cost", label: "Cost", type: "currency" },
120
+ { key: "status", label: "Status", render: (value, row) => <span>{value}</span> },
121
+ ]}
122
+ searchable // shows search input
123
+ sortable // clickable column headers
124
+ paginated // pagination controls
125
+ pageSize={10}
126
+ rowActions={(row) => <button>View</button>}
127
+ onRowSelect={(row) => console.log(row)}
128
+ emptyMessage="No travelers found."
129
+ />
130
+ ```
131
+ **Column types:** `"currency"` | `"percentage"` | `"number"` — auto-formatted, use before custom `render`.
132
+ **`render` receives `(value, row)`** — NOT `(row)`. `value` = `row[column.key]`.
133
+
134
+ ### ListCard
135
+ **When:** Display a scrollable list of items with avatars, status badges, timestamps.
136
+
137
+ ```jsx
138
+ <ListCard
139
+ title="Active Trips"
140
+ items={[{
141
+ id: "1",
142
+ title: "Hotel Zephyr", // or `name`
143
+ description: "San Francisco · Mar 23–26",
144
+ status: "Checked In", // shown as badge
145
+ timestamp: "2 min ago", // string or Date
146
+ avatar: undefined, // omit to auto-generate initials from title/name
147
+ // avatar: "https://example.com/photo.jpg", // URL string → renders <img>
148
+ // avatar: <Avatar initials="MR" />, // ReactNode → renders inside circle
149
+ value: "$2,840", // right-aligned
150
+ unit: "USD", // after value
151
+ }]}
152
+ maxBodyHeight={300} // enables scroll (string or number)
153
+ showAvatars={true}
154
+ showStatus={true}
155
+ showTimestamp={true}
156
+ dense={false}
157
+ divided={true}
158
+ onItemClick={(item, index) => {}}
159
+ itemActions={(item, index) => <button>View</button>}
160
+ emptyMessage="No items."
161
+ />
162
+ ```
163
+
164
+ ### ActivityCard
165
+ **When:** Show an activity log with animated status icons (working, pending, complete, error).
166
+
167
+ ```jsx
168
+ <ActivityCard
169
+ title="Today's Activity"
170
+ actions={[ // ⚠️ prop is "actions" NOT "items"
171
+ {
172
+ id: "1", // REQUIRED
173
+ status: "complete", // REQUIRED: "working" | "pending" | "complete" | "error"
174
+ title: "Maya checked in", // or `action` as fallback
175
+ subtitle: "Hotel Zephyr · SF",
176
+ timestamp: "2 min ago", // or `startedAt`
177
+ },
178
+ ]}
179
+ />
180
+ ```
181
+ **Returns `null`** if actions is empty. **No `maxBodyHeight`** — for scrollable feeds use `ListCard` instead.
182
+ Status icons: `"complete"` → green check, `"working"` → spinning (indigo), `"pending"` → clock, `"error"` → red exclamation.
183
+
184
+ ### CalloutCard
185
+ **When:** Highlight an alert, warning, or important notice.
186
+
187
+ ```jsx
188
+ <CalloutCard
189
+ title="Late booking penalty"
190
+ tone="warning" // "neutral" | "success" | "warning" | "danger" | "info"
191
+ icon={<ExclamationTriangleIcon className="h-5 w-5" />}
192
+ message={ // ⚠️ use `message` NOT children
193
+ <div>
194
+ <p>Priya Nair — Booking 2 days before departure</p>
195
+ <button onClick={handleApprove}>Approve</button>
196
+ </div>
197
+ }
198
+ />
199
+ ```
200
+ **Mistakes:** ❌ `variant` → use `tone`. ❌ Children → use `message` prop.
201
+
202
+ ### SectionCard
203
+ **When:** Display a prominent section heading or divider.
204
+
205
+ ```jsx
206
+ <SectionCard
207
+ title="Eva · Agentforce Agent"
208
+ description="Powered by Agentforce"
209
+ label="LIVE" // small pill badge
210
+ variant="default" // "default" | "primary" | "secondary" | "accent"
211
+ size="md" // "sm" | "md" | "lg" | "xl"
212
+ isDark={false}
213
+ />
214
+ ```
215
+
216
+ ### WidgetCard
217
+ **When:** Display multiple content sections in one card, OR wrap arbitrary custom interactive content (expand/collapse lists, inline detail panels, custom layouts) that doesn't fit other card types.
218
+
219
+ **This is the go-to card when you need custom JSX inside a card container.** Each section's `content` accepts arbitrary React nodes — use this instead of hand-rolling `<div className="bg-white border rounded ...">`.
220
+
221
+ **CRITICAL: WidgetCard does NOT accept children.** All content MUST go through the `sections` prop. If you write `<WidgetCard><div>...</div></WidgetCard>`, the children are silently ignored and the card renders "No sections." instead.
222
+
223
+ **Padding:** BaseCard already applies `p-4` around all content (header + body). Do NOT add extra `p-5`, `p-6`, etc. to your `header` or section `content` — it will cause double padding. If you need edge-to-edge content inside a section (like a divided list), use negative margins or set `padding="none"` on the card.
224
+
225
+ ```jsx
226
+ // ❌ WRONG — children are ignored, shows "No sections."
227
+ <WidgetCard>
228
+ <div>This content will NOT render</div>
229
+ </WidgetCard>
230
+
231
+ // ✅ CORRECT — content goes in sections prop
232
+ <WidgetCard
233
+ sections={[{ id: "main", content: <div>This content WILL render</div> }]}
234
+ />
235
+
236
+ // Multi-section panel
237
+ <WidgetCard
238
+ header={<div>Custom header</div>}
239
+ sections={[
240
+ { id: "1", title: "Section A", content: <div>...</div>, actions: <button>...</button> },
241
+ { id: "2", title: "Section B", content: <div>...</div> },
242
+ ]}
243
+ footer={<div>Footer</div>}
244
+ divided={true} // border between sections
245
+ collapsible={false}
246
+ />
247
+
248
+ // Single-section wrapper for custom interactive content (e.g., expandable list)
249
+ // NOTE: BaseCard already applies p-4 padding — do NOT add extra p-* to header/section content
250
+ <WidgetCard
251
+ header={<h2 className="text-lg font-semibold text-engine-text">Active Travelers</h2>}
252
+ sections={[{
253
+ id: "travelers",
254
+ content: (
255
+ <div className="divide-y divide-engine-border">
256
+ {travelers.map(t => (
257
+ <div key={t.id} onClick={() => toggle(t.id)}>
258
+ {/* collapsed row + expanded details — any JSX works here */}
259
+ </div>
260
+ ))}
261
+ </div>
262
+ ),
263
+ }]}
264
+ />
265
+ ```
266
+
267
+ ### StatusCard
268
+ **When:** Show system health or service status with colored indicators.
269
+
270
+ ```jsx
271
+ <StatusCard
272
+ title="System Health"
273
+ status="operational" // "operational" | "degraded" | "outage" | "maintenance"
274
+ items={[
275
+ { id: "1", title: "API", status: "operational" },
276
+ { id: "2", title: "Database", status: "degraded", description: "High latency" },
277
+ ]}
278
+ layout="list" // "list" | "grid" | "timeline"
279
+ showProgress={true} // shows % operational bar
280
+ />
281
+ ```
282
+ Status aliases: `"ok"/"healthy"/"up"` → `"operational"`, `"warn"` → `"degraded"`, `"down"/"critical"` → `"outage"`.
283
+
284
+ ### FeedPanel
285
+ **When:** Sidebar-style scrollable feed with fixed header.
286
+
287
+ ```jsx
288
+ <FeedPanel title="Notifications" subtitle="3 new" width={320}>
289
+ {children}
290
+ </FeedPanel>
291
+ ```
292
+
293
+ ### MetricsStrip
294
+ **When:** Compact horizontal row of metrics (inline, not cards).
295
+
296
+ ```jsx
297
+ <MetricsStrip
298
+ title="Overview"
299
+ metrics={[
300
+ { label: "Users", value: "1,234", trend: "+5%" },
301
+ { label: "Revenue", value: "$45K", trend: "-2%" },
302
+ ]}
303
+ collapsible={false}
304
+ />
305
+ ```
306
+
307
+ ### ActionList
308
+ **When:** Row of action buttons.
309
+
310
+ ```jsx
311
+ <ActionList
312
+ title="Quick Actions"
313
+ actions={[{ label: "Approve" }, { label: "Deny" }, { label: "Defer" }]}
314
+ onAction={(action) => console.log(action.label)}
315
+ />
316
+ ```
317
+
318
+ ---
319
+
320
+ ## Chart Components
321
+
322
+ ### D3Chart
323
+ **When:** Render any D3 visualization inside a ChartCard.
324
+
325
+ ```jsx
326
+ <D3Chart
327
+ data={dataArray}
328
+ renderChart={D3ChartTemplates.lineChart} // function — NOT template prop
329
+ options={{ xKey: "x", yKey: "y", stroke: "#5BC8C8" }}
330
+ responsive={true}
331
+ height={280}
332
+ />
333
+ ```
334
+ **`renderChart` signature:** `(svgEl, data, { width, height }, options) => void`
335
+
336
+ ### D3ChartTemplates
337
+
338
+ Only **two** built-in templates:
339
+
340
+ **`lineChart`** — `{ xKey, yKey, stroke, strokeWidth, margin, showAxes, showGrid }`
341
+ - Data: `[{ x: 1, y: 100 }]` — **numeric x required**
342
+
343
+ **`groupedBarChart`** — `{ xKey, groups, colors, barRadius, margin, yFormat, showGrid }`
344
+ - Data: `[{ x: "Jan", Hotels: 12000, Flights: 8000 }]` — **categorical x**
345
+
346
+ For donut, horizontal bar, area chart, etc. → write a custom `renderChart(svgEl, data, dims)` function using d3.
347
+
348
+ ### GeoMap
349
+ **When:** Show a world map with markers, flight arcs, and overlays.
350
+
351
+ ```jsx
352
+ <GeoMap
353
+ markers={[{ id: "sf", lon: -122.4, lat: 37.8, active: true, label: "SF" }]}
354
+ arcs={[{ id: "a1", from: [-122.4, 37.8], to: [-74.0, 40.7], progress: 0.65 }]}
355
+ overlays={[{ id: "wx1", center: [-97.7, 30.3], radius: 3 }]}
356
+ initialBounds={{ sw: [-130, 24], ne: [-65, 50], padding: 30 }}
357
+ theme="dark"
358
+ width={960} height={480}
359
+ zoomable
360
+ className="h-full w-full"
361
+ />
362
+ ```
363
+ **Do NOT wrap in ChartCard.** Render directly in `<div className="relative overflow-hidden rounded-xl h-[300px]">`.
364
+ Arc `danger: true` renders in red. Arc `progress` (0–1) shows animated dot along route.
365
+
366
+ ---
367
+
368
+ ## UI Primitives
369
+
370
+ ### UIButton
371
+ ```jsx
372
+ <UIButton variant="primary" size="md" onClick={fn} disabled={false} fullWidth={false}>Label</UIButton>
373
+ ```
374
+ Variants: `"primary"` | `"secondary"` | `"outline"` | `"ghost"`. Sizes: `"sm"` | `"md"` | `"lg"`.
375
+
376
+ ### UIText
377
+ ```jsx
378
+ <UIText as="p" size="md" weight="medium" muted={false}>Text content</UIText>
379
+ ```
380
+ Sizes: `"xs"` | `"sm"` | `"md"` | `"lg"` | `"xl"` | `"xxl"`. Weights: `"regular"` | `"medium"` | `"bold"`.
381
+
382
+ ### UIChip
383
+ ```jsx
384
+ <UIChip tone="success" size="xs">Active</UIChip>
385
+ ```
386
+ Tones: `"neutral"` | `"primary"` | `"success"` | `"warning"` | `"danger"`. Sizes: `"xs"` | `"sm"`.
387
+
388
+ ### Avatar
389
+ ```jsx
390
+ <Avatar src="url" name="Maya R" size="md" tone="slate" />
391
+ <Avatar initials="MR" size="sm" />
392
+ <Avatar icon={<UsersIcon />} size="lg" tone="brand" />
393
+ ```
394
+ Sizes: `"xs"` | `"sm"` | `"md"` | `"lg"`. Auto-generates initials from `name` if no `src`/`initials`/`icon`.
395
+
396
+ ### Spinner
397
+ ```jsx
398
+ <Spinner size="md" tone="brand" label="Loading" />
399
+ ```
400
+ Tones: `"brand"` | `"white"` | `"muted"` | `"current"`.
401
+
402
+ ### EmptyState
403
+ ```jsx
404
+ <EmptyState icon={<RocketIcon />} heading="Nothing here" body="Add some data" action={<UIButton>Add</UIButton>} size="md" />
405
+ ```
406
+
407
+ ### UICard
408
+ ```jsx
409
+ <UICard padding="p-5">{children}</UICard>
410
+ ```
411
+
412
+ ### UIContainer
413
+ ```jsx
414
+ <UIContainer title="Section" subtitle="Description" actions={<button>Add</button>} empty={false} emptyText="Nothing yet">{children}</UIContainer>
415
+ ```
416
+
417
+ ### UIInput
418
+ ```jsx
419
+ <UIInput placeholder="Enter text..." value={val} onChange={fn} type="text" />
420
+ ```
421
+
422
+ ### UIToggle
423
+ ```jsx
424
+ <UIToggle label="Dark mode" />
425
+ ```
426
+ Integrates with AppThemeProvider automatically.
427
+
428
+ ---
429
+
430
+ ## Filters
431
+
432
+ ### FilterBar
433
+ ```jsx
434
+ <FilterBar
435
+ filters={[
436
+ { id: "search", type: "search", placeholder: "Search..." },
437
+ { id: "status", type: "select", label: "Status", options: ["All", "Active"] },
438
+ { id: "flagged", type: "toggle", label: "Flagged only" },
439
+ ]}
440
+ values={{ search: "", status: "All", flagged: false }}
441
+ onChange={(filterId, value) => {}} // ⚠️ receives (filterId, value) NOT full state
442
+ onReset={() => {}}
443
+ layout="inline" // "inline" | "stacked"
444
+ />
445
+ ```
446
+
447
+ ---
448
+
449
+ ## Chat
450
+
451
+ ### ChatBar (command palette — use on dashboards)
452
+ ```jsx
453
+ <ChatBar
454
+ title="Eva · Travel Assistant"
455
+ placeholder="Ask Eva..."
456
+ suggestions={["Show spend", "Who has flags?", "Active trips"]}
457
+ onSend={async (userMsg, allMessages, helpers) => {
458
+ // Return message object to add to chat:
459
+ return { role: "assistant", content: "Here's what I found..." };
460
+ // Or stream: helpers.addMessage, helpers.appendChunk, helpers.setStreaming
461
+ }}
462
+ />
463
+ ```
464
+
465
+ ### ChatPanel (full-page chat)
466
+ ```jsx
467
+ <ChatPanel
468
+ title="AI Assistant"
469
+ onSend={async (userMsg, allMessages, helpers) => {
470
+ return { role: "assistant", content: "Reply" };
471
+ }}
472
+ suggestions={["Help me with...", "Show me..."]}
473
+ welcomeTitle="How can I help?"
474
+ welcomeSubtitle="Ask anything about your data."
475
+ showHeader={true}
476
+ />
477
+ ```
478
+
479
+ ### useChatState (standalone hook)
480
+ ```jsx
481
+ import { useChatState } from "@/components/library";
482
+ const chat = useChatState({ onSend: async (msg, history, helpers) => ({ role: "assistant", content: "Reply" }) });
483
+ // chat.messages, chat.sendMessage(text), chat.isLoading, chat.clearMessages, chat.retryLast
484
+ ```
485
+
486
+ ---
487
+
488
+ ## Data
489
+
490
+ ### useDataSource (DEFAULT export)
491
+ ```jsx
492
+ import useDataSource from "@/components/library/data/useDataSource";
493
+ const data = useDataSource({ sample: SAMPLE_DATA, live: fetchLiveFn });
494
+ ```
495
+ ⚠️ **Default export** — `import { useDataSource }` will fail silently.
496
+
497
+ ### DataModeProvider
498
+ Wraps app. Provides `useDataMode()` → `{ mode, isSample, isLive, toggle, setMode }`.
499
+
500
+ ---
501
+
502
+ ## Layout & Skeletons
503
+
504
+ ### PageContainer
505
+ ```jsx
506
+ <PageContainer>{children}</PageContainer>
507
+ ```
508
+ Centered `max-w-6xl` wrapper with padding.
509
+
510
+ ### CardSkeleton
511
+ ```jsx
512
+ <CardSkeleton lines={3} />
513
+ ```
514
+ Animated placeholder card for loading states.
515
+
516
+ ---
517
+
518
+ ## Forms
519
+
520
+ ### FormRenderer
521
+ ```jsx
522
+ <FormRenderer
523
+ sections={[{ id: "s1", title: "Details", fields: [
524
+ { id: "name", type: "text", label: "Name", required: true },
525
+ { id: "dept", type: "select", label: "Department", options: ["Sales", "Eng"] },
526
+ ]}]}
527
+ values={{ name: "", dept: "" }}
528
+ errors={{}}
529
+ touched={{}}
530
+ onFieldChange={(fieldId, value) => {}}
531
+ onFieldBlur={(fieldId) => {}}
532
+ />
533
+ ```
534
+ Field types: `text`, `email`, `url`, `number`, `date`, `textarea`, `select`, `radio`, `checkbox`, `checkboxGroup`, `toggle`.
535
+
536
+ ### FormModal
537
+ **When:** Open a modal dialog with a schema-driven form (create or edit records).
538
+
539
+ ```jsx
540
+ <FormModal
541
+ isOpen={showModal}
542
+ onClose={() => setShowModal(false)}
543
+ title="New Traveler"
544
+ subtitle="Add a traveler to the system"
545
+ sections={formSections} // same schema as FormRenderer
546
+ initialValues={{ name: "Maya" }} // prefill for edit mode; omit for create
547
+ onSubmit={async (values) => {}} // async — modal auto-closes on success
548
+ submitLabel="Save" // default "Save"
549
+ cancelLabel="Cancel" // default "Cancel"
550
+ size="lg" // "sm" | "md" | "lg" | "xl"
551
+ destructive={false} // red submit button for deletions
552
+ minSubmitMs={4000} // min spinner duration ms
553
+ />
554
+ ```
555
+ Renders via `createPortal` over a backdrop. Escape key dismisses. Wraps FormRenderer + useFormState internally.
556
+
557
+ ### FormSection & FormField
558
+ Lower-level building blocks used by FormRenderer. Rarely needed directly.
559
+
560
+ ```jsx
561
+ // FormSection — renders a titled group of fields in 2-col grid
562
+ <FormSection section={sectionSchema} values={vals} errors={errs} touched={touched}
563
+ onFieldChange={(id, val) => {}} onFieldBlur={(id) => {}} />
564
+
565
+ // FormField — renders a single labeled input
566
+ <FormField field={fieldDef} value={val} error={errMsg} touched={true}
567
+ onChange={(val) => {}} onBlur={() => {}} />
568
+ ```
569
+
570
+ ### useFormState
571
+ **When:** Manage form state without FormModal (inline forms, custom layouts).
572
+
573
+ ```jsx
574
+ const form = useFormState({
575
+ initialValues: { name: "", dept: "" },
576
+ sections: formSections, // schema — used for defaults + validation
577
+ onSubmit: async (values) => {},
578
+ minSubmitMs: 4000, // default 4000
579
+ });
580
+
581
+ // form.values, form.errors, form.touched
582
+ // form.isDirty, form.isValid, form.isSubmitting
583
+ // form.setValue(id, value), form.setValues(obj)
584
+ // form.setTouched(id), form.validate(), form.reset()
585
+ // form.handleSubmit(e) — async, validates first, enforces minSubmitMs
586
+ ```
587
+ Builds defaults from schema: `checkboxGroup` → `[]`, `toggle`/`checkbox` → `false`, others → `""`. Supports custom validators via `field.validate(value, allValues)`.
588
+
589
+ ---
590
+
591
+ ## Individual Filter Components
592
+
593
+ FilterBar composes these internally, but they can also be used standalone:
594
+
595
+ ### SearchFilter
596
+ ```jsx
597
+ <SearchFilter
598
+ value={searchText}
599
+ onChange={(text) => setSearchText(text)}
600
+ placeholder="Search travelers..."
601
+ className="w-64"
602
+ />
603
+ ```
604
+ Text input with magnifying glass icon and clear button.
605
+
606
+ ### SelectFilter
607
+ ```jsx
608
+ <SelectFilter
609
+ value={status}
610
+ onChange={(val) => setStatus(val)}
611
+ options={["All", "Active", "Inactive"]} // or [{ value: "active", label: "Active" }]
612
+ label="Status"
613
+ placeholder="Select..."
614
+ />
615
+ ```
616
+ Dropdown select. Value `"all"` skips filtering.
617
+
618
+ ### ToggleFilter
619
+ ```jsx
620
+ <ToggleFilter
621
+ value={flaggedOnly}
622
+ onChange={(isActive) => setFlaggedOnly(isActive)}
623
+ label="Flagged only"
624
+ />
625
+ ```
626
+
627
+ ---
628
+
629
+ ## Data Utilities
630
+
631
+ ### DataModeToggle
632
+ **When:** Place in the app header to let users switch between sample and live data.
633
+
634
+ ```jsx
635
+ <DataModeToggle className="ml-4" />
636
+ ```
637
+ Pill button switching between "Sample" (beaker icon, amber) and "Live" (signal icon, emerald). Reads/writes to `DataModeProvider` context.
638
+
639
+ ### usePageFilters
640
+ **When:** Complete filtering + sorting state for a data page.
641
+
642
+ ```jsx
643
+ const {
644
+ values, // current filter values
645
+ setFilter, // (filterId, value) => void
646
+ resetFilters, // () => void
647
+ sort, // { key, direction } | null
648
+ setSort, // (key, direction) => void
649
+ toggleSort, // (key) => void — cycles asc → desc → null
650
+ filteredData, // data after filters
651
+ sortedData, // data after filters + sort (pass this to components)
652
+ activeFilterCount, // number of non-default filters
653
+ } = usePageFilters({
654
+ data: rawData,
655
+ filters: [
656
+ { id: "search", type: "search", keys: ["name", "city"] },
657
+ { id: "status", type: "select", key: "status", defaultValue: "all" },
658
+ { id: "flagged", type: "toggle", key: "isFlagged", matchValue: true },
659
+ { id: "dates", type: "dateRange", key: "createdAt" },
660
+ ],
661
+ defaultSort: { key: "name", direction: "asc" },
662
+ });
663
+ ```
664
+ Pass `sortedData` (not `filteredData`) to components. Disable `TableCard.searchable` when FilterBar provides search.
665
+
666
+ ### filterUtils (pure functions)
667
+ ```jsx
668
+ import { filterBySearch, filterByValue, filterByToggle, filterByDateRange, sortByKey, applyFilters } from "@/components/library";
669
+
670
+ filterBySearch(data, "maya", ["name", "city"]) // case-insensitive multi-key
671
+ filterByValue(data, "status", "Active") // exact match; skips "all"/""/ null
672
+ filterByToggle(data, "isFlagged", true, true) // when active, match key to value
673
+ filterByDateRange(data, "createdAt", { start, end }) // date range inclusion
674
+ sortByKey(data, "name", "asc") // "asc" | "desc"; handles null, numbers, strings
675
+ applyFilters(data, filterDefs, filterValues) // apply all filters declaratively
676
+ ```
677
+
678
+ ---
679
+
680
+ ## Chat Sub-Components
681
+
682
+ ChatBar and ChatPanel compose these internally, but they can be used standalone for custom chat layouts:
683
+
684
+ ### ChatMessageList
685
+ ```jsx
686
+ <ChatMessageList
687
+ messages={messages}
688
+ isLoading={true} // shows "Thinking" indicator
689
+ isStreaming={false} // shows "Generating" indicator
690
+ suggestions={["Show spend", "Top travelers"]}
691
+ onSuggestion={(text) => sendMessage(text)}
692
+ renderAvatar={(msg) => <Avatar ... />} // optional per-message avatar
693
+ />
694
+ ```
695
+ Scrollable message area with auto-scroll to latest message.
696
+
697
+ ### ChatMessage
698
+ ```jsx
699
+ <ChatMessage
700
+ message={{ id: "1", role: "assistant", content: "Here's what I found...",
701
+ components: [<MetricCard ... />], // inline rendered components
702
+ toolCalls: [{ name: "search", status: "complete", result: "..." }],
703
+ isStreaming: false, timestamp: "2:30 PM" }}
704
+ avatar={<Avatar icon={<CpuChipIcon />} />}
705
+ />
706
+ ```
707
+ Renders a single message bubble. Assistant messages format markdown (code blocks, bold, italic, inline code). System messages render as centered alerts.
708
+
709
+ ### ChatInput
710
+ ```jsx
711
+ <ChatInput
712
+ onSend={(content) => handleSend(content)}
713
+ disabled={false}
714
+ isLoading={isProcessing} // shows stop button instead of send
715
+ onStop={() => cancelRequest()}
716
+ placeholder="Type a message..."
717
+ maxRows={6} // max visible rows before scroll
718
+ />
719
+ ```
720
+ Auto-resizing textarea. Enter sends, Shift+Enter = newline.
721
+
722
+ ### ChatTypingIndicator
723
+ ```jsx
724
+ <ChatTypingIndicator label="Thinking" />
725
+ ```
726
+ Animated three-dot bouncing indicator with AI icon.
727
+
728
+ ### ChatSuggestions
729
+ ```jsx
730
+ <ChatSuggestions
731
+ suggestions={["Show spend", "Active trips"]}
732
+ onSelect={(text) => sendMessage(text)}
733
+ />
734
+ ```
735
+ Row of sparkle-icon pill buttons for quick prompts. Returns null if empty.
736
+
737
+ ### ChatToolCall
738
+ ```jsx
739
+ <ChatToolCall
740
+ toolCall={{ id: "tc1", name: "queryRecords", args: { object: "Trip__c" },
741
+ status: "complete", result: "Found 6 records" }}
742
+ />
743
+ ```
744
+ Collapsible tool execution step. Status icons: spinner (running), check (complete), X (error). Color-coded by status.
745
+
746
+ ### ChatWelcome
747
+ ```jsx
748
+ <ChatWelcome
749
+ title="How can I help?"
750
+ subtitle="Ask me anything about your travel data."
751
+ suggestions={["Show spend", "Active trips"]}
752
+ onSuggestion={(text) => sendMessage(text)}
753
+ icon={<CpuChipIcon className="h-8 w-8" />}
754
+ />
755
+ ```
756
+ Full-screen welcome state shown before first message.
757
+
758
+ ---
759
+
760
+ ## BaseCard
761
+
762
+ Foundation primitive that all card components extend. Rarely used directly — prefer MetricCard, ListCard, etc.
763
+
764
+ ```jsx
765
+ <BaseCard
766
+ header={<div>Title area</div>}
767
+ body={<div>Content</div>} // or use children
768
+ footer={<div>Footer</div>}
769
+ variant="default" // "default" | "metric" | "chart" | "table" | "widget" | "status"
770
+ size="md" // min-height: "xs" | "sm" | "md" | "lg" | "xl" | "full"
771
+ padding="default" // "none" | "xs" | "sm" | "default" | "lg" | "xl"
772
+ shadow={true}
773
+ radius="2xl"
774
+ border={true}
775
+ isHoverable={false} // lift on hover
776
+ isPressable={false} // interactive button (requires onPress)
777
+ isLoading={false} // animated skeleton pulse
778
+ isDisabled={false}
779
+ isSelected={false} // ring-2 brand highlight
780
+ onPress={() => {}}
781
+ className=""
782
+ headerClassName=""
783
+ bodyClassName=""
784
+ footerClassName=""
785
+ />
786
+ ```
787
+
788
+ ---
789
+
790
+ ## HeroUI Wrapper Components
791
+
792
+ Thin wrappers around `@heroui/react` components, pre-scoped for the command center's theme. Import from `@/components/library`. Use compound sub-component pattern with dot notation.
793
+
794
+ ### Navigation & Layout
795
+
796
+ #### Tabs
797
+ ```jsx
798
+ import { Tabs } from "@/components/library";
799
+
800
+ <Tabs aria-label="Options" variant="underlined" color="primary">
801
+ <Tabs.List>
802
+ <Tabs.Tab id="overview">Overview</Tabs.Tab>
803
+ <Tabs.Tab id="details">Details</Tabs.Tab>
804
+ </Tabs.List>
805
+ <Tabs.Panel id="overview">
806
+ <div>Overview content</div>
807
+ </Tabs.Panel>
808
+ <Tabs.Panel id="details">
809
+ <div>Details content</div>
810
+ </Tabs.Panel>
811
+ </Tabs>
812
+ ```
813
+ **Critical:** `Tabs.Tab` MUST be inside `Tabs.List` — rendering it directly inside `Tabs` causes a "cannot be rendered outside a collection" error. Content goes in `Tabs.Panel`, not as children of `Tabs.Tab`.
814
+
815
+ Use **inside cards** for sub-section switching — never as page-level navigation.
816
+
817
+ #### Accordion
818
+ ```jsx
819
+ import { Accordion } from "@/components/library";
820
+
821
+ <Accordion selectionMode="multiple" variant="bordered">
822
+ <Accordion.Item key="1" title="Section 1" subtitle="Optional subtitle">
823
+ <div>Expanded content</div>
824
+ </Accordion.Item>
825
+ </Accordion>
826
+ ```
827
+
828
+ #### Breadcrumbs
829
+ ```jsx
830
+ import { Breadcrumbs } from "@/components/library";
831
+
832
+ <Breadcrumbs>
833
+ <Breadcrumbs.Item>Home</Breadcrumbs.Item>
834
+ <Breadcrumbs.Item>Dashboard</Breadcrumbs.Item>
835
+ <Breadcrumbs.Item>Current</Breadcrumbs.Item>
836
+ </Breadcrumbs>
837
+ ```
838
+
839
+ #### Separator
840
+ ```jsx
841
+ import { Separator } from "@/components/library";
842
+
843
+ <Separator orientation="horizontal" className="my-4" />
844
+ ```
845
+
846
+ #### Pagination
847
+ ```jsx
848
+ import { Pagination } from "@/components/library";
849
+
850
+ <Pagination total={10} page={currentPage} onChange={setCurrentPage}
851
+ showControls boundaries={1} siblings={1} />
852
+ ```
853
+
854
+ ### Overlays
855
+
856
+ #### Drawer
857
+ ```jsx
858
+ import { Drawer } from "@/components/library";
859
+
860
+ <Drawer isOpen={open} onOpenChange={setOpen} placement="right" size="md">
861
+ <Drawer.Content>
862
+ <Drawer.Header>Title</Drawer.Header>
863
+ <Drawer.Body>Content</Drawer.Body>
864
+ <Drawer.Footer>
865
+ <UIButton onClick={() => setOpen(false)}>Close</UIButton>
866
+ </Drawer.Footer>
867
+ </Drawer.Content>
868
+ </Drawer>
869
+ ```
870
+
871
+ #### Modal
872
+ ```jsx
873
+ import { Modal } from "@/components/library";
874
+
875
+ <Modal isOpen={open} onOpenChange={setOpen} size="lg">
876
+ <Modal.Container>
877
+ <Modal.Header>Title</Modal.Header>
878
+ <Modal.Body>Content</Modal.Body>
879
+ <Modal.Footer>Actions</Modal.Footer>
880
+ </Modal.Container>
881
+ </Modal>
882
+ ```
883
+ For schema-driven forms, prefer `FormModal` over raw `Modal`.
884
+
885
+ #### Dropdown
886
+ ```jsx
887
+ import { Dropdown } from "@/components/library";
888
+
889
+ <Dropdown>
890
+ <Dropdown.Trigger>
891
+ <UIButton>Options</UIButton>
892
+ </Dropdown.Trigger>
893
+ <Dropdown.Menu onAction={(key) => console.log(key)}>
894
+ <Dropdown.Item key="edit">Edit</Dropdown.Item>
895
+ <Dropdown.Item key="delete" className="text-danger">Delete</Dropdown.Item>
896
+ </Dropdown.Menu>
897
+ </Dropdown>
898
+ ```
899
+
900
+ #### Tooltip
901
+ ```jsx
902
+ import { Tooltip } from "@/components/library";
903
+
904
+ <Tooltip content="More info" placement="top">
905
+ <span>Hover me</span>
906
+ </Tooltip>
907
+ ```
908
+
909
+ #### Toast
910
+ ```jsx
911
+ import { toast } from "@/components/library";
912
+
913
+ // Trigger from anywhere (Toast.Provider must be in CommandCenter.tsx — already set up)
914
+ toast.success("Record saved");
915
+ toast.error("Something went wrong");
916
+ toast("Neutral message");
917
+ ```
918
+
919
+ ### Feedback
920
+
921
+ #### Alert
922
+ ```jsx
923
+ import { Alert } from "@/components/library";
924
+
925
+ <Alert color="warning" title="Heads up" description="Check this out" />
926
+ ```
927
+
928
+ #### Badge
929
+ ```jsx
930
+ import { Badge } from "@/components/library";
931
+
932
+ <Badge content="3" color="danger">
933
+ <BellIcon className="h-5 w-5" />
934
+ </Badge>
935
+ ```
936
+
937
+ #### ProgressBar
938
+ ```jsx
939
+ import { ProgressBar } from "@/components/library";
940
+
941
+ <ProgressBar label="Upload" value={75} maxValue={100} color="primary" showValueLabel />
942
+ ```
943
+
944
+ #### ProgressCircle
945
+ ```jsx
946
+ import { ProgressCircle } from "@/components/library";
947
+
948
+ <ProgressCircle value={60} maxValue={100} color="success" showValueLabel />
949
+ ```
950
+
951
+ #### Meter
952
+ ```jsx
953
+ import { Meter } from "@/components/library";
954
+
955
+ <Meter label="CPU Usage" value={82} maxValue={100} color="warning" />
956
+ ```
957
+
958
+ #### Skeleton
959
+ ```jsx
960
+ import { Skeleton } from "@/components/library";
961
+
962
+ <Skeleton className="h-4 w-3/4 rounded-lg" />
963
+ ```
964
+ For card-shaped loading states, prefer `CardSkeleton` over raw `Skeleton`.
965
+
966
+ ### Pickers & Forms
967
+
968
+ #### Select
969
+ ```jsx
970
+ import { Select } from "@/components/library";
971
+
972
+ <Select label="Department" placeholder="Choose..." selectedKeys={[dept]}
973
+ onSelectionChange={(keys) => setDept([...keys][0])}>
974
+ <Select.Item key="sales">Sales</Select.Item>
975
+ <Select.Item key="eng">Engineering</Select.Item>
976
+ </Select>
977
+ ```
978
+ For filter dropdowns, prefer `SelectFilter` or `FilterBar` over raw `Select`.
979
+
980
+ ### Utilities
981
+
982
+ #### Kbd
983
+ ```jsx
984
+ import { Kbd } from "@/components/library";
985
+
986
+ <Kbd keys={["command"]}>K</Kbd> // renders ⌘K
987
+ ```
988
+
989
+ #### ScrollShadow
990
+ ```jsx
991
+ import { ScrollShadow } from "@/components/library";
992
+
993
+ <ScrollShadow className="h-[300px]">
994
+ {longContent}
995
+ </ScrollShadow>
996
+ ```
997
+ Adds fade shadows at scroll edges to indicate more content.
998
+
999
+ ---
1000
+
1001
+ ## Do Not
1002
+
1003
+ - Import from individual library files when the barrel exports it
1004
+ - Modify vendored library files
1005
+ - Import shadcn (`@/components/ui/`) in command center code — shadcn components (`Button`, `Card`, `Input`, `Select`, `Alert`, `Skeleton`, etc.) exist for the outer app only. In command center pages, use the equivalent library component (`UIButton`, `BaseCard`/`WidgetCard`, `UIInput`, `Select`, `Alert`, `Skeleton`, etc. from `@/components/library`)
1006
+ - Use Lucide icons (`lucide-react`) in command center code — use Heroicons (`@heroicons/react`) instead
1007
+ - `import { useDataSource }` — it's a **default** export
1008
+ - Pass `items` to ActivityCard — use `actions`
1009
+ - Pass children to CalloutCard — use `message` prop
1010
+ - Pass `(row)` to TableCard render — it receives `(value, row)`
1011
+ - Use `template` prop on D3Chart — use `renderChart` function reference
1012
+ - Wrap GeoMap in ChartCard — render directly in a container div
1013
+ - Use ActivityCard for scrollable feeds — use ListCard with `maxBodyHeight`
1014
+ - Use raw `Modal` for form dialogs — use `FormModal` instead
1015
+ - Use `Tabs` as page-level navigation — only use inside cards for sub-sections
1016
+ - Pass `filteredData` to components — use `sortedData` from usePageFilters
1017
+ - Use `CardSkeleton` and `Skeleton` interchangeably — `CardSkeleton` for card placeholders, `Skeleton` for inline shapes
1018
+ - Recreate form state manually — use `useFormState` hook
1019
+ - Use `ChatPanel` on dashboards — use `ChatBar` (command palette style)
1020
+ - Pass children to `WidgetCard` — it ignores children and shows "No sections." Use the `sections` prop instead: `<WidgetCard sections={[{ id: "main", content: <div>...</div> }]} />`
1021
+ - Add `p-5`, `p-6`, etc. to WidgetCard `header` or section `content` — BaseCard already applies `p-4` padding, so extra padding causes double spacing
1022
+ - Pass initials strings as `avatar` in ListCard items — `avatar: "MR"` is treated as an image URL and breaks. Instead, omit `avatar` to auto-generate initials from `title`/`name`, or pass a ReactNode like `<Avatar initials="MR" />`
1023
+ - Hand-roll a card container (`<div className="bg-white border rounded ...">`) when `WidgetCard` with a single section can wrap your custom content
1024
+ - Use Recharts, Chart.js, or any third-party chart library — only `D3Chart` and `GeoMap` are allowed
1025
+ - Dismiss `validate:dashboard` errors as "justified" — every error has a library component fix