@schandlergarcia/sf-web-components 2.3.17 → 2.5.0

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 (94) hide show
  1. package/.a4drules/skills/command-center-builder/SKILL.md +3 -2
  2. package/.a4drules/skills/component-library/SKILL.md +50 -4
  3. package/.a4drules/skills/component-library/card-components.md +88 -0
  4. package/.a4drules/skills/component-library/when-to-use.md +1 -0
  5. package/CHANGELOG.md +40 -0
  6. package/CLAUDE.md +12 -13
  7. package/README.md +0 -15
  8. package/dist/components/library/cards/KanbanBoard.js +313 -0
  9. package/dist/components/library/cards/KanbanBoard.js.map +1 -0
  10. package/dist/components/library/index.js +60 -57
  11. package/dist/components/library/index.js.map +1 -1
  12. package/dist/components/workspace/ComponentRegistry.js +5 -2
  13. package/dist/components/workspace/ComponentRegistry.js.map +1 -1
  14. package/dist/index.js +84 -82
  15. package/dist/index.js.map +1 -1
  16. package/dist/styles/global.css +44 -57
  17. package/package.json +7 -2
  18. package/scripts/apply-brand.mjs +47 -30
  19. package/scripts/postinstall.mjs +1 -11
  20. package/src/components/library/cards/KanbanBoard.jsx +507 -0
  21. package/src/components/library/index.jsx +1 -0
  22. package/src/styles/global.css +44 -57
  23. package/brands/engine/PARTNER_HUB_PRD.md +0 -584
  24. package/brands/engine/agentApiConfig.ts +0 -36
  25. package/brands/engine/app/api/graphql-operations-types.ts +0 -11260
  26. package/brands/engine/app/api/graphqlClient.ts +0 -25
  27. package/brands/engine/app/api/partnerQueries.ts +0 -212
  28. package/brands/engine/app/appLayout.tsx +0 -5
  29. package/brands/engine/app/components/AgentPanel.tsx +0 -541
  30. package/brands/engine/app/components/AgentforceConversationClient.tsx +0 -201
  31. package/brands/engine/app/components/Data360Widget.tsx +0 -301
  32. package/brands/engine/app/components/__inherit_AgentforceConversationClient.tsx +0 -3
  33. package/brands/engine/app/components/alerts/status-alert.tsx +0 -49
  34. package/brands/engine/app/components/layouts/card-layout.tsx +0 -29
  35. package/brands/engine/app/components/workspace/CommandCenter.tsx +0 -16
  36. package/brands/engine/app/config/agentApi.ts +0 -36
  37. package/brands/engine/app/data/partner-hub-sample-data.js +0 -297
  38. package/brands/engine/app/features/object-search/__examples__/api/accountSearchService.ts +0 -46
  39. package/brands/engine/app/features/object-search/__examples__/api/query/distinctAccountIndustries.graphql +0 -19
  40. package/brands/engine/app/features/object-search/__examples__/api/query/distinctAccountTypes.graphql +0 -19
  41. package/brands/engine/app/features/object-search/__examples__/api/query/getAccountDetail.graphql +0 -121
  42. package/brands/engine/app/features/object-search/__examples__/api/query/searchAccounts.graphql +0 -51
  43. package/brands/engine/app/features/object-search/__examples__/pages/AccountObjectDetailPage.tsx +0 -357
  44. package/brands/engine/app/features/object-search/__examples__/pages/AccountSearch.tsx +0 -312
  45. package/brands/engine/app/features/object-search/__examples__/pages/Home.tsx +0 -34
  46. package/brands/engine/app/features/object-search/api/objectSearchService.ts +0 -84
  47. package/brands/engine/app/features/object-search/components/ActiveFilters.tsx +0 -89
  48. package/brands/engine/app/features/object-search/components/FilterContext.tsx +0 -83
  49. package/brands/engine/app/features/object-search/components/ObjectBreadcrumb.tsx +0 -66
  50. package/brands/engine/app/features/object-search/components/PaginationControls.tsx +0 -109
  51. package/brands/engine/app/features/object-search/components/SearchBar.tsx +0 -41
  52. package/brands/engine/app/features/object-search/components/SortControl.tsx +0 -143
  53. package/brands/engine/app/features/object-search/components/filters/BooleanFilter.tsx +0 -78
  54. package/brands/engine/app/features/object-search/components/filters/DateFilter.tsx +0 -128
  55. package/brands/engine/app/features/object-search/components/filters/DateRangeFilter.tsx +0 -70
  56. package/brands/engine/app/features/object-search/components/filters/FilterFieldWrapper.tsx +0 -33
  57. package/brands/engine/app/features/object-search/components/filters/MultiSelectFilter.tsx +0 -97
  58. package/brands/engine/app/features/object-search/components/filters/NumericRangeFilter.tsx +0 -163
  59. package/brands/engine/app/features/object-search/components/filters/SearchFilter.tsx +0 -50
  60. package/brands/engine/app/features/object-search/components/filters/SelectFilter.tsx +0 -97
  61. package/brands/engine/app/features/object-search/components/filters/TextFilter.tsx +0 -91
  62. package/brands/engine/app/features/object-search/hooks/useAsyncData.ts +0 -54
  63. package/brands/engine/app/features/object-search/hooks/useCachedAsyncData.ts +0 -184
  64. package/brands/engine/app/features/object-search/hooks/useDebouncedCallback.ts +0 -34
  65. package/brands/engine/app/features/object-search/hooks/useObjectSearchParams.ts +0 -252
  66. package/brands/engine/app/features/object-search/utils/debounce.ts +0 -25
  67. package/brands/engine/app/features/object-search/utils/fieldUtils.ts +0 -29
  68. package/brands/engine/app/features/object-search/utils/filterUtils.ts +0 -404
  69. package/brands/engine/app/features/object-search/utils/sortUtils.ts +0 -38
  70. package/brands/engine/app/hooks/useEngineLiveData.ts +0 -49
  71. package/brands/engine/app/hooks/useEvaAgent.ts +0 -288
  72. package/brands/engine/app/hooks/usePartnerDashboardData.ts +0 -141
  73. package/brands/engine/app/navigationMenu.tsx +0 -80
  74. package/brands/engine/app/pages/AccountObjectDetailPage.tsx +0 -361
  75. package/brands/engine/app/pages/AccountSearch.tsx +0 -305
  76. package/brands/engine/app/pages/BlankDashboard.tsx +0 -15
  77. package/brands/engine/app/pages/DataTest.tsx +0 -78
  78. package/brands/engine/app/pages/Home.tsx +0 -5
  79. package/brands/engine/app/pages/NotFound.tsx +0 -19
  80. package/brands/engine/app/pages/PartnerHubDashboard.tsx +0 -2760
  81. package/brands/engine/app/pages/Search.tsx +0 -13
  82. package/brands/engine/app/router-utils.tsx +0 -35
  83. package/brands/engine/app/routes.tsx +0 -39
  84. package/brands/engine/app/styles/global.css +0 -269
  85. package/brands/engine/brand.css +0 -40
  86. package/brands/engine/engine-command-center-prd.md +0 -575
  87. package/brands/engine/engine-live-data.js +0 -135
  88. package/brands/engine/engine-sample-data.js +0 -378
  89. package/brands/engine/engine_logo.png +0 -0
  90. package/brands/engine/global.css +0 -269
  91. package/brands/engine/partner-hub-sample-data.js +0 -281
  92. package/brands/engine/schema.graphql +0 -292
  93. package/brands/engine/useEngineLiveData.ts +0 -49
  94. package/brands/engine/useEvaAgent.ts +0 -288
@@ -3,7 +3,7 @@ name: command-center-builder
3
3
  description: >-
4
4
  Conventions and patterns for building Command Center dashboards.
5
5
  Covers layout patterns (visualization-hero, grid), component selection
6
- (MetricCard, ChartCard, GeoMap, ListCard, ActivityCard), dark mode,
6
+ (MetricCard, ChartCard, GeoMap, ListCard, KanbanBoard, ActivityCard), dark mode,
7
7
  brand colors, styling, and wiring dashboards into the app. Use when
8
8
  scaffolding, building, or editing any dashboard page in this project.
9
9
  ---
@@ -92,7 +92,7 @@ Use these exact imports. Do NOT read component source files to discover imports
92
92
  ```tsx
93
93
  // Library components — named imports from barrel
94
94
  import {
95
- BaseCard, MetricCard, ChartCard, TableCard, ListCard, ActivityCard,
95
+ BaseCard, MetricCard, ChartCard, TableCard, ListCard, KanbanBoard, ActivityCard,
96
96
  StatusCard, WidgetCard, SectionCard, FeedPanel, CalloutCard, ActionList,
97
97
  MetricsStrip, D3Chart, D3ChartTemplates, GeoMap, Avatar, UIButton,
98
98
  UIChip, UIText, EmptyState, Spinner, ChatBar, FilterBar,
@@ -164,6 +164,7 @@ The component library (`@/components/library`) provides pre-built, themed, dark-
164
164
  | Activity feed (display only) | `ActivityCard` or `FeedPanel` | `<div>` with hand-rolled interaction rows |
165
165
  | **Activity feed (with per-item actions like "Assign to Agent")** | **Custom markup inside `BaseCard`** | **`ActivityCard` (doesn't support per-item buttons)** |
166
166
  | Data table | `TableCard` | `<table>` or custom grid of rows |
167
+ | Board / swim-lanes (cards across columns, optional DnD) | `KanbanBoard` | Stacked `ListCard`s or hand-rolled grid of columns |
167
168
  | Stats panel | `WidgetCard` or `SectionCard` | `<div className="bg-white ... p-6">` with label/value pairs |
168
169
  | Custom interactive content (expand/collapse, inline details) | `WidgetCard` (sections accept arbitrary JSX) or `ListCard` (with `onItemClick` + `itemActions`) | Hand-rolled `<div>` card with custom expand/collapse logic |
169
170
  | Status items | `StatusCard` | `<div>` with custom status badges |
@@ -3,10 +3,11 @@ name: component-library
3
3
  description: >-
4
4
  API reference for the command center component library at
5
5
  @/components/library. Documents every component (MetricCard, ChartCard,
6
- D3Chart, D3ChartTemplates, GeoMap, ListCard, ActivityCard, TableCard,
7
- StatusCard, WidgetCard, BaseCard, ChatBar, Avatar, UIChip, FilterBar),
8
- their props, data shapes, and import patterns. Use when looking up how
9
- to use a specific component or choosing which component fits a need.
6
+ D3Chart, D3ChartTemplates, GeoMap, ListCard, KanbanBoard, ActivityCard,
7
+ TableCard, StatusCard, WidgetCard, BaseCard, ChatBar, Avatar, UIChip,
8
+ FilterBar), their props, data shapes, and import patterns. Use when
9
+ looking up how to use a specific component or choosing which component
10
+ fits a need.
10
11
  ---
11
12
 
12
13
  # Component Library
@@ -64,6 +65,9 @@ import type { MetricCardProps } from "@/components/library";
64
65
  - 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" />`
65
66
  - Hand-roll a card container (`<div className="bg-white border rounded ...">`) when `WidgetCard` with a single section can wrap your custom content
66
67
  - Use Recharts, Chart.js, or any third-party chart library — only `D3Chart` and `GeoMap` are allowed
68
+ - Use `react-beautiful-dnd`, `react-dnd`, or any other DnD library — `KanbanBoard` is built on `@dnd-kit/*` (peer deps); do not introduce a second DnD system
69
+ - Forget `columnId` on a `KanbanBoard` card — every card MUST have `columnId` matching one of `columns[].id`, otherwise it won't render
70
+ - Wrap `KanbanBoard` in another card (`WidgetCard`, `BaseCard`) — it already renders inside its own `BaseCard`
67
71
  - Dismiss `validate:dashboard` errors as "justified" — every error has a library component fix
68
72
 
69
73
  ---
@@ -76,6 +80,7 @@ import type { MetricCardProps } from "@/components/library";
76
80
  | A row of 2–4 KPIs | Grid of `MetricCard` | `MetricsStrip` (for compact inline) |
77
81
  | A data table | `TableCard` | `<table>` HTML |
78
82
  | A scrollable item list | `ListCard` | Custom divs with `.map()` |
83
+ | A board / swim-lanes (cards in columns, optional drag-and-drop) | `KanbanBoard` | Stacked `ListCard`s or hand-rolled grid |
79
84
  | An activity log with status icons | `ActivityCard` | ListCard (no status icons) |
80
85
  | A chart (line, bar) | `ChartCard` + `D3Chart` | Raw `<svg>` |
81
86
  | A donut/pie chart | `ChartCard` + `D3Chart` + custom renderChart | Any chart library |
@@ -358,6 +363,47 @@ Status aliases: `"ok"/"healthy"/"up"` → `"operational"`, `"warn"` → `"degrad
358
363
  />
359
364
  ```
360
365
 
366
+ ### KanbanBoard
367
+ **When:** Display items as cards across labeled swim-lanes (statuses, pipeline stages, sprint board). Includes drag-and-drop powered by `@dnd-kit/*`.
368
+
369
+ **Peer deps required:** `@dnd-kit/core`, `@dnd-kit/sortable`, `@dnd-kit/utilities`. Install in your project if missing:
370
+ ```bash
371
+ npm install @dnd-kit/core @dnd-kit/sortable @dnd-kit/utilities
372
+ ```
373
+
374
+ ```jsx
375
+ <KanbanBoard
376
+ title="Sprint Board"
377
+ subtitle="Q2 · Sprint 4"
378
+ columns={[
379
+ { id: "todo", title: "To Do", limit: 5, accent: "default" },
380
+ { id: "doing", title: "In Progress", limit: 3, accent: "warning" },
381
+ { id: "done", title: "Done", accent: "success" },
382
+ ]}
383
+ cards={[
384
+ { id: "C-1", columnId: "todo", title: "Onboard Acme", subtitle: "Owner: Aria", badges: ["Priority"], status: "warning" },
385
+ { id: "C-2", columnId: "doing", title: "Renew SLA", status: "danger", badges: [{ label: "Risk", color: "danger" }] },
386
+ { id: "C-3", columnId: "done", title: "Q2 review prep", status: "success" },
387
+ ]}
388
+ isDraggable // default true
389
+ showColumnCounts // default true
390
+ showWipLimits // default true; renders "count / limit"
391
+ dense={false}
392
+ accent="default" // fallback accent
393
+ onCardClick={(card) => {/* row click */}}
394
+ onCardMove={({ cardId, fromColumnId, toColumnId, fromIndex, toIndex }) => {
395
+ // Optional. If provided, you own the state. If omitted, the board manages reorder/move internally.
396
+ }}
397
+ emptyMessage="No items"
398
+ loading={false}
399
+ error={null}
400
+ />
401
+ ```
402
+
403
+ **Card shape:** `{ id, columnId, title, subtitle?, description?, status?, accent?, avatar?, badges?, meta?, actions? }`
404
+ **Column shape:** `{ id, title, accent?, limit?, emptyMessage?, footer?, actions? }`
405
+ **Mistakes:** ❌ Missing `columnId` on a card → it won't render. ❌ Wrapping `KanbanBoard` in another card. ❌ Using a non-`@dnd-kit` DnD library alongside it.
406
+
361
407
  ---
362
408
 
363
409
  ## Chart Components
@@ -249,5 +249,93 @@ Status aliases: `"ok"/"healthy"/"up"` → `"operational"`, `"warn"` → `"degrad
249
249
  />
250
250
  ```
251
251
 
252
+ ### KanbanBoard
253
+ **When:** Display items as cards across labeled swim-lanes (To Do → In Progress → Done, pipeline stages, status boards). Supports drag-and-drop between columns out of the box.
254
+
255
+ **Peer deps:** `@dnd-kit/core`, `@dnd-kit/sortable`, `@dnd-kit/utilities` are required peer dependencies. Install in your project if they're not already there:
256
+ ```bash
257
+ npm install @dnd-kit/core @dnd-kit/sortable @dnd-kit/utilities
258
+ ```
259
+
260
+ ```jsx
261
+ import { KanbanBoard } from "@/components/library";
262
+ import useDataSource from "@/components/library/data/useDataSource";
263
+
264
+ const SAMPLE_CARDS = [
265
+ { id: "C-1", columnId: "todo", title: "Onboard Acme", subtitle: "Owner: Aria", badges: ["Priority"], status: "warning" },
266
+ { id: "C-2", columnId: "doing", title: "Renew SLA", subtitle: "Owner: Devin", badges: [{ label: "Risk", color: "danger" }], status: "danger" },
267
+ { id: "C-3", columnId: "done", title: "Q2 review prep", subtitle: "Owner: Priya", status: "success" },
268
+ ];
269
+
270
+ const COLUMNS = [
271
+ { id: "todo", title: "To Do", limit: 5, accent: "default" },
272
+ { id: "doing", title: "In Progress", limit: 3, accent: "warning" }, // count > limit shows red ring on chip
273
+ { id: "done", title: "Done", accent: "success" },
274
+ ];
275
+
276
+ export default function PipelineBoard() {
277
+ const cards = useDataSource({ sample: SAMPLE_CARDS, live: [] });
278
+
279
+ return (
280
+ <KanbanBoard
281
+ title="Sprint Board"
282
+ subtitle="Q2 · Sprint 4"
283
+ columns={COLUMNS}
284
+ cards={cards}
285
+ isDraggable // enable @dnd-kit DnD (default true)
286
+ showColumnCounts // chip on each column header (default true)
287
+ showWipLimits // render "count / limit" when limit set (default true)
288
+ dense={false} // tighter card padding when true
289
+ accent="default" // fallback accent: default|primary|success|warning|danger|info
290
+ onCardClick={(card) => console.log(card.id)} // optional row click
291
+ onCardMove={({ cardId, fromColumnId, toColumnId, fromIndex, toIndex }) => {
292
+ // Optional. If provided, you own the state and must update `cards`.
293
+ // If omitted, KanbanBoard manages reorder/move internally.
294
+ }}
295
+ emptyMessage="No items" // shown in empty columns
296
+ loading={false}
297
+ error={null}
298
+ />
299
+ );
300
+ }
301
+ ```
302
+
303
+ **Card shape:**
304
+ ```js
305
+ {
306
+ id: "C-1", // required, unique
307
+ columnId: "todo", // required, must match one of columns[].id
308
+ title: "Onboard Acme", // required
309
+ subtitle: "Owner: Aria",
310
+ description: "Long-form text (clamped to 3 lines)",
311
+ status: "warning", // dot color: default|primary|success|warning|danger|info
312
+ accent: "primary", // overrides board accent for the card's left border
313
+ avatar: "/url" | <Icon /> | "AR" (auto-initials when omitted),
314
+ badges: ["Priority", { label: "Risk", color: "danger", size: "sm" }],
315
+ meta: ["Due Mon", { icon: <ClockIcon />, label: "2d" }],
316
+ actions: <UIButton size="sm">Open</UIButton>
317
+ }
318
+ ```
319
+
320
+ **Column shape:**
321
+ ```js
322
+ {
323
+ id: "todo", // required, unique
324
+ title: "To Do", // required
325
+ accent: "default" | "primary" | "success" | "warning" | "danger" | "info",
326
+ limit: 5, // WIP limit; over-limit columns get a red ring on the count chip
327
+ emptyMessage: "Nothing here yet", // overrides board-level emptyMessage
328
+ footer: <span>Capacity: 80%</span>,
329
+ actions: <UIButton size="xs">+</UIButton>
330
+ }
331
+ ```
332
+
333
+ **Mistakes:**
334
+ - ❌ Forgetting `columnId` on a card → card won't render. Every card MUST have `columnId` matching a column.
335
+ - ❌ Passing the full cards list back into a `setCards` from `onCardMove` (the callback fires on intent only — you decide if/how to update). If you want default reorder behavior, just omit `onCardMove`.
336
+ - ❌ Using `KanbanBoard` for a simple sortable list — use `ListCard` instead.
337
+ - ❌ Wrapping `KanbanBoard` in another card (`WidgetCard`, `BaseCard`) — it already renders inside a `BaseCard`.
338
+ - ❌ Installing `react-beautiful-dnd` or `react-dnd` — only `@dnd-kit/*` is supported.
339
+
252
340
  ---
253
341
 
@@ -6,6 +6,7 @@
6
6
  | A row of 2–4 KPIs | Grid of `MetricCard` | `MetricsStrip` (for compact inline) |
7
7
  | A data table | `TableCard` | `<table>` HTML |
8
8
  | A scrollable item list | `ListCard` | Custom divs with `.map()` |
9
+ | A board / swim-lanes (cards in columns) | `KanbanBoard` | Multiple stacked `ListCard`s |
9
10
  | An activity log with status icons | `ActivityCard` | ListCard (no status icons) |
10
11
  | A chart (line, bar) | `ChartCard` + `D3Chart` | Raw `<svg>` |
11
12
  | A donut/pie chart | `ChartCard` + `D3Chart` + custom renderChart | Any chart library |
package/CHANGELOG.md CHANGED
@@ -5,6 +5,46 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.5.0] - 2026-05-07
9
+
10
+ ### Added
11
+ - **`KanbanBoard` component** — board / swim-lane view for items in columns with drag-and-drop. Lives at `src/components/library/cards/KanbanBoard.jsx`, exported from the library barrel as `KanbanBoard`.
12
+ - Structured card shape: `{ id, columnId, title, subtitle?, description?, status?, accent?, avatar?, badges?, meta?, actions? }`.
13
+ - Structured column shape: `{ id, title, accent?, limit?, emptyMessage?, footer?, actions? }`.
14
+ - Drag-and-drop powered by `@dnd-kit/core` + `@dnd-kit/sortable` + `@dnd-kit/utilities` (new peer dependencies).
15
+ - Pointer + keyboard sensors for full a11y; drag overlay shows the active card; over-limit columns show a red ring on the count chip.
16
+ - `onCardMove({ cardId, fromColumnId, toColumnId, fromIndex, toIndex })` callback for state-owning parents; if omitted, the board manages reorder/move internally.
17
+ - `onCardClick(card)`, `loading`, `error`, `dense`, `accent`, `showColumnCounts`, `showWipLimits`, `emptyMessage` props.
18
+ - Renders inside its own `BaseCard` (do not wrap in another card).
19
+ - **Peer dependencies:** `@dnd-kit/core ^6.0.0`, `@dnd-kit/sortable ^8.0.0 || ^9.0.0 || ^10.0.0`, `@dnd-kit/utilities ^3.0.0`. Consuming projects must install these for `KanbanBoard` to work.
20
+ - **`vite.config.ts` externals updated** so `@dnd-kit/*` is not bundled into the library output.
21
+
22
+ ### Documentation
23
+ - Added `KanbanBoard` row to `.a4drules/skills/component-library/when-to-use.md` and the corresponding "When to Use" table in `SKILL.md`.
24
+ - Added full `KanbanBoard` API reference to `.a4drules/skills/component-library/card-components.md` (card shape, column shape, mistakes, peer-dep install hint).
25
+ - Added `KanbanBoard` API summary to `.a4drules/skills/component-library/SKILL.md` Card Components section.
26
+ - Updated `.a4drules/skills/command-center-builder/SKILL.md`: added `KanbanBoard` to the frontmatter description, the canonical import block, and the "Need / MUST use" table.
27
+ - Added Do-Not entries forbidding alternative DnD libraries (`react-beautiful-dnd`, `react-dnd`), forgetting `columnId` on a card, and double-wrapping `KanbanBoard` in another card.
28
+
29
+ ### Context
30
+ - Many command-center use cases (sprint boards, opportunity pipelines, ticket triage, approval queues) need a board layout. Without a library component, agents would either stack `ListCard`s (no DnD, no column counts/limits) or hand-roll a grid of columns (violates "every section is a library component"). `KanbanBoard` gives agents a single structured component that follows the same data-driven, sample/live-aware patterns as `ListCard` and `TableCard`.
31
+
32
+ ## [2.4.0] - 2026-05-07
33
+
34
+ ### Removed
35
+ - **Engine brand assets** — deleted `brands/engine/` (global.css, app/, sample data, PRD, GraphQL schema, Engine logo, Engine-specific hooks like `useEvaAgent` and `useEngineLiveData`).
36
+ - **Top-level `data/` directory** — removed all Engine Travel Command Center data, schema, agent API config, and the README/USAGE docs that documented Engine-only assets.
37
+ - **`brand:engine` and `demo:engine` npm scripts** — removed from `package.json` and from the postinstall script that adds scripts to consuming projects.
38
+
39
+ ### Changed
40
+ - **`src/styles/global.css` is now truly neutral** — replaced the `--color-engine-*` palette and the Cyan/Engine-Black brand ramp with a slate-based neutral `--color-brand-*` ramp; replaced the Engine-coded `.heroui-scope` (Engine Black `#0D1117`, Cyan `#7DCBD9`, Orange-Red `#FD4B23`, Amber `#FFB200`, Engine Blue `#157DE5`) with neutral `oklch()` defaults. The `:root` `--dash-*` tokens (which previously used Engine amber/cream) now use neutral slate/standard semantic colors.
41
+ - **`scripts/apply-brand.mjs` is now brand-agnostic** — sample-data file copying discovers `*-sample-data.js` / `*-live-data.js` / `*-data.{js,ts}` files dynamically instead of hardcoding `engine-sample-data.js`, `engine-live-data.js`, `partner-hub-sample-data.js`. Logo copying discovers any `*.png|jpg|svg|webp|gif` file at the brand root instead of hardcoding `engine_logo.png`. Updated header docs to remove Engine examples.
42
+ - **`CLAUDE.md` Brand System section rewritten** — removed the "Engine Brand" callout and the `brand:engine` example. Now explains the brand system as optional infrastructure (no brands ship by default) and documents the conventions a `brands/<name>/` folder can use.
43
+ - **`README.md` Sample Data section removed** — the package no longer ships Engine Travel sample data.
44
+
45
+ ### Context
46
+ - The package and its source files no longer carry any Engine Travel branding so it can be used as a neutral baseline for building a new app on top of the component library and Command Center builder. The brand system itself remains available; teams can drop their own brand under `brands/<name>/`.
47
+
8
48
  ## [2.3.17] - 2026-04-15
9
49
 
10
50
  ### Changed
package/CLAUDE.md CHANGED
@@ -67,8 +67,7 @@ sf-web-components/
67
67
  │ │ └── workspace/ # CommandCenter.tsx.template
68
68
  │ ├── lib/ # Utilities (utils.ts)
69
69
  │ └── styles/ # global.css with neutral brand tokens
70
- ├── brands/
71
- │ └── engine/ # Engine brand (global.css, sample data, PRD, hooks)
70
+ ├── brands/ # Optional brand bundles (none ship by default)
72
71
  ├── scripts/
73
72
  │ ├── postinstall.mjs # Runs after npm install in consuming projects
74
73
  │ ├── apply-brand.mjs # Apply/reset brand themes
@@ -86,23 +85,23 @@ sf-web-components/
86
85
 
87
86
  ## Brand System
88
87
 
89
- The package ships with a **neutral theme** by default. Opt into a specific brand:
88
+ The package ships with a **neutral theme** by default. No brands ship out of the box; the brand system is optional infrastructure for layering custom themes/demo apps on top of the neutral baseline.
90
89
 
91
90
  ```bash
92
- npm run brand:engine # Apply Engine brand (colors, sample data, PRD, hooks)
93
- npm run brand:reset # Revert to neutral theme
94
- npm run brand:list # Show available brands
91
+ npm run brand:list # Show available brands (empty by default)
92
+ npm run brand:reset # Restore neutral theme (clears any active brand)
95
93
  ```
96
94
 
97
- When a brand is active, the reset script (`npm run reset:command-center`) preserves the brand's `global.css` instead of restoring neutral. The `.brand` marker file tracks which brand is active.
95
+ To add a brand, drop a folder under `brands/<name>/` containing any of:
98
96
 
99
- ### Engine Brand
100
- - **Primary:** Engine Black `#0D1117`
101
- - **Accent:** Cyan `#7DCBD9`, Green `#1E9D6D`, Blue `#157DE5`
102
- - **Secondary:** Orange-Red `#FD4B23`, Amber `#FFB200`
103
- - **Neutrals:** Dark Gray `#616368`, Mid Gray `#B0B1B3`, Light Gray `#F3F3F4`
97
+ - `global.css` — overrides applied to `src/styles/global.css`
98
+ - `app/` full demo app (pages, hooks, api, features) installed via `--demo`
99
+ - `*.png` / `*.svg` logo assets installed into `src/assets/images/`
100
+ - `*-sample-data.js` / `*-live-data.js` installed into `src/data/`
101
+ - `*_PRD.md` / `*-prd.md` product requirements docs copied to project root
102
+ - `schema.graphql` — data schema copied to project root
104
103
 
105
- Installs: Engine-branded `global.css`, sample data, live data, PRD, GraphQL schema, Eva agent hook, agent config, and Engine logo.
104
+ When a brand is active, the reset script (`npm run reset:command-center`) preserves the brand's `global.css` instead of restoring neutral. The `.brand` marker file tracks which brand is active.
106
105
 
107
106
  ## Key Conventions
108
107
 
package/README.md CHANGED
@@ -75,21 +75,6 @@ This library includes:
75
75
  - Filter components
76
76
  - Layout components
77
77
 
78
- ## Sample Data
79
-
80
- The package includes pre-seeded sample data in the `data/` directory:
81
- - **Engine Travel Command Center** sample data with real Salesforce field names
82
- - Dashboard-ready data (map markers, chart data, metrics, etc.)
83
- - Easy to swap for live data later
84
-
85
- **Copy to your project:**
86
- ```bash
87
- # From your webapp directory
88
- cp node_modules/@schandlergarcia/sf-web-components/data/engine-sample-data.js src/data/
89
- ```
90
-
91
- See `data/README.md` and `data/USAGE.md` in the package for full documentation.
92
-
93
78
  ## Utilities
94
79
 
95
80
  ```tsx
@@ -0,0 +1,313 @@
1
+ import { jsx as t, jsxs as c } from "react/jsx-runtime";
2
+ import K, { useState as F, useMemo as re, useCallback as U } from "react";
3
+ import { useSensors as ne, useSensor as V, PointerSensor as se, KeyboardSensor as le, DndContext as ae, closestCenter as ie, DragOverlay as de, useDroppable as oe } from "@dnd-kit/core";
4
+ import { sortableKeyboardCoordinates as ce, arrayMove as ue, SortableContext as me, verticalListSortingStrategy as fe, useSortable as he } from "@dnd-kit/sortable";
5
+ import { CSS as pe } from "@dnd-kit/utilities";
6
+ import ge from "./BaseCard.js";
7
+ import N from "../ui/Text.js";
8
+ import T from "../ui/Chip.js";
9
+ const B = {
10
+ default: "bg-slate-400",
11
+ primary: "bg-brand-500",
12
+ success: "bg-emerald-500",
13
+ warning: "bg-amber-500",
14
+ danger: "bg-rose-500",
15
+ info: "bg-sky-500"
16
+ }, _ = {
17
+ default: "border-l-slate-300 dark:border-l-slate-700",
18
+ primary: "border-l-brand-500",
19
+ success: "border-l-emerald-500",
20
+ warning: "border-l-amber-500",
21
+ danger: "border-l-rose-500",
22
+ info: "border-l-sky-500"
23
+ };
24
+ function be({ badges: e }) {
25
+ return !e || e.length === 0 ? null : /* @__PURE__ */ t("div", { className: "mt-2 flex flex-wrap gap-1.5", children: e.map((r, a) => K.isValidElement(r) ? /* @__PURE__ */ t("span", { children: r }, a) : typeof r == "string" ? /* @__PURE__ */ t(T, { size: "sm", variant: "default", children: r }, a) : /* @__PURE__ */ t(
26
+ T,
27
+ {
28
+ size: r.size ?? "sm",
29
+ variant: r.variant ?? "default",
30
+ color: r.color,
31
+ children: r.label ?? r.text
32
+ },
33
+ r.id ?? a
34
+ )) });
35
+ }
36
+ function xe({ avatar: e, title: r }) {
37
+ return e ? typeof e == "string" ? /* @__PURE__ */ t(
38
+ "img",
39
+ {
40
+ src: e,
41
+ alt: "",
42
+ className: "h-7 w-7 shrink-0 rounded-full border border-slate-200 object-cover dark:border-slate-800"
43
+ }
44
+ ) : K.isValidElement(e) ? /* @__PURE__ */ t("div", { className: "grid h-7 w-7 shrink-0 place-items-center rounded-full border border-slate-200 bg-slate-50 dark:border-slate-800 dark:bg-slate-800", children: e }) : /* @__PURE__ */ t("div", { className: "grid h-7 w-7 shrink-0 place-items-center rounded-full border border-slate-200 bg-slate-100 text-[10px] font-semibold text-slate-700 dark:border-slate-800 dark:bg-slate-800 dark:text-slate-200", children: String(r ?? "??").slice(0, 2).toUpperCase() }) : null;
45
+ }
46
+ function M({ card: e, accent: r, dragging: a, onClick: s, dense: f }) {
47
+ const d = _[e?.accent ?? r ?? "default"] ?? _.default;
48
+ return /* @__PURE__ */ c(
49
+ s ? "button" : "div",
50
+ {
51
+ type: s ? "button" : void 0,
52
+ onClick: s ? () => s(e) : void 0,
53
+ className: [
54
+ "group block w-full rounded-xl border bg-white text-left shadow-sm transition",
55
+ "border-slate-200 dark:border-slate-800 dark:bg-slate-900",
56
+ "border-l-4",
57
+ d,
58
+ f ? "p-2.5" : "p-3",
59
+ a ? "opacity-60 ring-2 ring-brand-500" : "hover:-translate-y-[1px] hover:shadow-md"
60
+ ].filter(Boolean).join(" "),
61
+ children: [
62
+ /* @__PURE__ */ c("div", { className: "flex items-start gap-2", children: [
63
+ /* @__PURE__ */ t(xe, { avatar: e.avatar, title: e.title }),
64
+ /* @__PURE__ */ c("div", { className: "min-w-0 flex-1", children: [
65
+ /* @__PURE__ */ t(N, { as: "div", size: "sm", weight: "medium", className: "truncate", children: e.title }),
66
+ e.subtitle ? /* @__PURE__ */ t(N, { as: "div", size: "xs", muted: !0, className: "mt-0.5 truncate", children: e.subtitle }) : null
67
+ ] }),
68
+ e.status ? /* @__PURE__ */ t(
69
+ "span",
70
+ {
71
+ "aria-label": String(e.status),
72
+ className: [
73
+ "mt-1.5 h-2 w-2 shrink-0 rounded-full",
74
+ B[e.status] ?? B.default
75
+ ].join(" ")
76
+ }
77
+ ) : null
78
+ ] }),
79
+ e.description ? /* @__PURE__ */ t(N, { as: "div", size: "xs", muted: !0, className: "mt-2 line-clamp-3 text-left", children: e.description }) : null,
80
+ /* @__PURE__ */ t(be, { badges: e.badges }),
81
+ e.meta && e.meta.length > 0 ? /* @__PURE__ */ t("div", { className: "mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-slate-500 dark:text-slate-400", children: e.meta.map((l, x) => K.isValidElement(l) ? /* @__PURE__ */ t("span", { children: l }, x) : typeof l == "string" ? /* @__PURE__ */ t("span", { children: l }, x) : /* @__PURE__ */ c("span", { className: "inline-flex items-center gap-1", children: [
82
+ l.icon ? /* @__PURE__ */ t("span", { className: "opacity-70", children: l.icon }) : null,
83
+ /* @__PURE__ */ t("span", { children: l.label ?? l.text })
84
+ ] }, l.id ?? x)) }) : null,
85
+ e.actions ? /* @__PURE__ */ t(
86
+ "div",
87
+ {
88
+ className: "mt-2 flex justify-end gap-1.5",
89
+ onClick: (l) => l.stopPropagation(),
90
+ children: e.actions
91
+ }
92
+ ) : null
93
+ ]
94
+ }
95
+ );
96
+ }
97
+ function ve({ card: e, accent: r, onClick: a, isDraggable: s, dense: f }) {
98
+ const d = he({
99
+ id: e.id,
100
+ data: { columnId: e.columnId, type: "card" },
101
+ disabled: !s
102
+ }), b = {
103
+ transform: pe.Translate.toString(d.transform),
104
+ transition: d.transition
105
+ };
106
+ return /* @__PURE__ */ t(
107
+ "div",
108
+ {
109
+ ref: d.setNodeRef,
110
+ style: b,
111
+ ...s ? d.attributes : {},
112
+ ...s ? d.listeners : {},
113
+ className: s ? "touch-none" : void 0,
114
+ children: /* @__PURE__ */ t(
115
+ M,
116
+ {
117
+ card: e,
118
+ accent: r,
119
+ dragging: d.isDragging,
120
+ onClick: a,
121
+ dense: f
122
+ }
123
+ )
124
+ }
125
+ );
126
+ }
127
+ function Ne({
128
+ column: e,
129
+ cards: r,
130
+ accent: a,
131
+ onCardClick: s,
132
+ isDraggable: f,
133
+ dense: d,
134
+ showCounts: b,
135
+ showLimits: l,
136
+ emptyMessage: x
137
+ }) {
138
+ const k = oe({
139
+ id: `column:${e.id}`,
140
+ data: { columnId: e.id, type: "column" }
141
+ }), y = r.length, w = e.limit != null && y > e.limit;
142
+ return /* @__PURE__ */ c("div", { className: "flex w-72 shrink-0 flex-col rounded-2xl border border-slate-200 bg-slate-50 dark:border-slate-800 dark:bg-slate-900/60", children: [
143
+ /* @__PURE__ */ c("div", { className: "flex items-center justify-between gap-2 border-b border-slate-200 px-3 py-2 dark:border-slate-800", children: [
144
+ /* @__PURE__ */ c("div", { className: "flex min-w-0 items-center gap-2", children: [
145
+ /* @__PURE__ */ t(
146
+ "span",
147
+ {
148
+ "aria-hidden": !0,
149
+ className: [
150
+ "h-2 w-2 shrink-0 rounded-full",
151
+ B[e.accent ?? a ?? "default"] ?? B.default
152
+ ].join(" ")
153
+ }
154
+ ),
155
+ /* @__PURE__ */ t(N, { as: "div", size: "sm", weight: "medium", className: "truncate", children: e.title }),
156
+ b ? /* @__PURE__ */ t(
157
+ T,
158
+ {
159
+ size: "sm",
160
+ variant: "default",
161
+ className: w ? "ring-1 ring-rose-500" : void 0,
162
+ children: l && e.limit != null ? `${y} / ${e.limit}` : y
163
+ }
164
+ ) : null
165
+ ] }),
166
+ e.actions ? /* @__PURE__ */ t("div", { className: "shrink-0", children: e.actions }) : null
167
+ ] }),
168
+ /* @__PURE__ */ t(
169
+ "div",
170
+ {
171
+ ref: k.setNodeRef,
172
+ className: [
173
+ "flex flex-1 flex-col gap-2 overflow-y-auto p-2",
174
+ k.isOver ? "bg-brand-50/50 dark:bg-brand-950/30" : ""
175
+ ].filter(Boolean).join(" "),
176
+ style: { minHeight: 80 },
177
+ children: /* @__PURE__ */ t(
178
+ me,
179
+ {
180
+ items: r.map((h) => h.id),
181
+ strategy: fe,
182
+ children: r.length === 0 ? /* @__PURE__ */ t("div", { className: "grid flex-1 place-items-center px-2 py-6", children: /* @__PURE__ */ t(N, { as: "div", size: "xs", muted: !0, className: "text-center", children: e.emptyMessage ?? x ?? "No items" }) }) : r.map((h) => /* @__PURE__ */ t(
183
+ ve,
184
+ {
185
+ card: h,
186
+ accent: a,
187
+ onClick: s,
188
+ isDraggable: f,
189
+ dense: d
190
+ },
191
+ h.id
192
+ ))
193
+ }
194
+ )
195
+ }
196
+ ),
197
+ e.footer ? /* @__PURE__ */ t("div", { className: "border-t border-slate-200 px-3 py-2 text-xs text-slate-500 dark:border-slate-800 dark:text-slate-400", children: e.footer }) : null
198
+ ] });
199
+ }
200
+ function De({
201
+ columns: e = [],
202
+ cards: r = [],
203
+ title: a,
204
+ subtitle: s,
205
+ accent: f = "default",
206
+ isDraggable: d = !0,
207
+ dense: b = !1,
208
+ showColumnCounts: l = !0,
209
+ showWipLimits: x = !0,
210
+ emptyMessage: k = "No items",
211
+ loading: y = !1,
212
+ error: w,
213
+ actions: h,
214
+ onCardClick: P,
215
+ onCardMove: R,
216
+ className: G = "",
217
+ ...H
218
+ }) {
219
+ const [A, D] = F(null), [W, q] = F(null), u = W ?? r, J = ne(
220
+ V(se, { activationConstraint: { distance: 4 } }),
221
+ V(le, { coordinateGetter: ce })
222
+ ), E = re(() => {
223
+ const n = {};
224
+ for (const o of e) n[o.id] = [];
225
+ for (const o of u) {
226
+ const i = o.columnId;
227
+ n[i] || (n[i] = []), n[i].push(o);
228
+ }
229
+ return n;
230
+ }, [e, u]), Q = U(
231
+ (n) => u.find((i) => i.id === n)?.columnId ?? null,
232
+ [u]
233
+ ), X = U(
234
+ (n) => n ? typeof n == "string" && n.startsWith("column:") ? n.slice(7) : u.find((i) => i.id === n)?.columnId ?? null : null,
235
+ [u]
236
+ ), Y = (n) => D(n.active.id), Z = (n) => {
237
+ D(null);
238
+ const { active: o, over: i } = n;
239
+ if (!i) return;
240
+ const p = Q(o.id), m = X(i.id);
241
+ if (!p || !m) return;
242
+ const C = (E[p] ?? []).slice(), I = p === m ? C : (E[m] ?? []).slice(), S = C.findIndex((v) => v.id === o.id);
243
+ let g;
244
+ if (i.data?.current?.type === "column" || i.id === `column:${m}` ? g = I.length : (g = I.findIndex((v) => v.id === i.id), g === -1 && (g = I.length)), p === m && S === g) return;
245
+ let L;
246
+ if (p === m) {
247
+ const v = ue(C, S, g);
248
+ L = [...u.filter((z) => z.columnId !== p), ...v];
249
+ } else {
250
+ const v = { ...C[S], columnId: m }, $ = C.filter((j) => j.id !== o.id), z = I.slice();
251
+ z.splice(g, 0, v), L = [...u.filter(
252
+ (j) => j.columnId !== p && j.columnId !== m
253
+ ), ...$, ...z];
254
+ }
255
+ R ? R({
256
+ cardId: o.id,
257
+ fromColumnId: p,
258
+ toColumnId: m,
259
+ fromIndex: S,
260
+ toIndex: g
261
+ }) : q(L);
262
+ }, O = A ? u.find((n) => n.id === A) : null, ee = a || s || h ? /* @__PURE__ */ c("div", { className: "mb-3 flex items-start justify-between gap-3", children: [
263
+ /* @__PURE__ */ c("div", { className: "min-w-0", children: [
264
+ a ? /* @__PURE__ */ t(N, { as: "div", size: "sm", weight: "medium", children: a }) : null,
265
+ s ? /* @__PURE__ */ t(N, { as: "div", size: "xs", muted: !0, className: "mt-1", children: s }) : null
266
+ ] }),
267
+ h ? /* @__PURE__ */ t("div", { className: "shrink-0", children: h }) : null
268
+ ] }) : null, te = /* @__PURE__ */ t("div", { className: "flex gap-3 overflow-x-auto pb-1", children: e.map((n) => /* @__PURE__ */ t(
269
+ Ne,
270
+ {
271
+ column: n,
272
+ cards: E[n.id] ?? [],
273
+ accent: f,
274
+ onCardClick: P,
275
+ isDraggable: d,
276
+ dense: b,
277
+ showCounts: l,
278
+ showLimits: x,
279
+ emptyMessage: k
280
+ },
281
+ n.id
282
+ )) });
283
+ return /* @__PURE__ */ c(
284
+ ge,
285
+ {
286
+ padding: "default",
287
+ isLoading: y,
288
+ className: G,
289
+ ...H,
290
+ children: [
291
+ ee,
292
+ w ? /* @__PURE__ */ t("div", { className: "rounded-md border border-rose-200 bg-rose-50 px-3 py-2 text-sm text-rose-700 dark:border-rose-900/60 dark:bg-rose-950/40 dark:text-rose-300", children: String(w) }) : /* @__PURE__ */ c(
293
+ ae,
294
+ {
295
+ sensors: J,
296
+ collisionDetection: ie,
297
+ onDragStart: Y,
298
+ onDragEnd: Z,
299
+ onDragCancel: () => D(null),
300
+ children: [
301
+ te,
302
+ /* @__PURE__ */ t(de, { children: O ? /* @__PURE__ */ t(M, { card: O, accent: f, dragging: !0, dense: b }) : null })
303
+ ]
304
+ }
305
+ )
306
+ ]
307
+ }
308
+ );
309
+ }
310
+ export {
311
+ De as default
312
+ };
313
+ //# sourceMappingURL=KanbanBoard.js.map