@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
@@ -0,0 +1,507 @@
1
+ import React, { useMemo, useState, useCallback } from "react";
2
+ import {
3
+ DndContext,
4
+ DragOverlay,
5
+ PointerSensor,
6
+ KeyboardSensor,
7
+ useSensor,
8
+ useSensors,
9
+ closestCenter,
10
+ useDroppable,
11
+ } from "@dnd-kit/core";
12
+ import {
13
+ SortableContext,
14
+ useSortable,
15
+ verticalListSortingStrategy,
16
+ sortableKeyboardCoordinates,
17
+ arrayMove,
18
+ } from "@dnd-kit/sortable";
19
+ import { CSS } from "@dnd-kit/utilities";
20
+ import BaseCard from "./BaseCard";
21
+ import UIText from "../ui/Text";
22
+ import UIChip from "../ui/Chip";
23
+
24
+ /**
25
+ * KanbanBoard
26
+ *
27
+ * A swim-lane / board view with structured cards and (optional) drag-and-drop
28
+ * powered by @dnd-kit. Designed to live inside a CommandCenter dashboard and
29
+ * to behave consistently with ListCard / TableCard.
30
+ *
31
+ * Data shapes:
32
+ * columns: [{ id, title, accent?, limit?, footer?, emptyMessage? }]
33
+ * cards: [{ id, columnId, title, subtitle?, description?, badges?,
34
+ * status?, avatar?, meta?, actions?, accent? }]
35
+ *
36
+ * Callbacks:
37
+ * onCardClick(card)
38
+ * onCardMove({ cardId, fromColumnId, toColumnId, fromIndex, toIndex })
39
+ */
40
+
41
+ const ACCENT_RING = {
42
+ default: "ring-slate-200 dark:ring-slate-800",
43
+ primary: "ring-brand-500/40",
44
+ success: "ring-emerald-500/40",
45
+ warning: "ring-amber-500/40",
46
+ danger: "ring-rose-500/40",
47
+ info: "ring-sky-500/40",
48
+ };
49
+
50
+ const ACCENT_DOT = {
51
+ default: "bg-slate-400",
52
+ primary: "bg-brand-500",
53
+ success: "bg-emerald-500",
54
+ warning: "bg-amber-500",
55
+ danger: "bg-rose-500",
56
+ info: "bg-sky-500",
57
+ };
58
+
59
+ const ACCENT_BORDER_LEFT = {
60
+ default: "border-l-slate-300 dark:border-l-slate-700",
61
+ primary: "border-l-brand-500",
62
+ success: "border-l-emerald-500",
63
+ warning: "border-l-amber-500",
64
+ danger: "border-l-rose-500",
65
+ info: "border-l-sky-500",
66
+ };
67
+
68
+ function CardBadges({ badges }) {
69
+ if (!badges || badges.length === 0) return null;
70
+ return (
71
+ <div className="mt-2 flex flex-wrap gap-1.5">
72
+ {badges.map((b, i) => {
73
+ if (React.isValidElement(b)) return <span key={i}>{b}</span>;
74
+ if (typeof b === "string") {
75
+ return (
76
+ <UIChip key={i} size="sm" variant="default">
77
+ {b}
78
+ </UIChip>
79
+ );
80
+ }
81
+ return (
82
+ <UIChip
83
+ key={b.id ?? i}
84
+ size={b.size ?? "sm"}
85
+ variant={b.variant ?? "default"}
86
+ color={b.color}
87
+ >
88
+ {b.label ?? b.text}
89
+ </UIChip>
90
+ );
91
+ })}
92
+ </div>
93
+ );
94
+ }
95
+
96
+ function CardAvatar({ avatar, title }) {
97
+ if (!avatar) return null;
98
+ if (typeof avatar === "string") {
99
+ return (
100
+ <img
101
+ src={avatar}
102
+ alt=""
103
+ className="h-7 w-7 shrink-0 rounded-full border border-slate-200 object-cover dark:border-slate-800"
104
+ />
105
+ );
106
+ }
107
+ if (React.isValidElement(avatar)) {
108
+ return (
109
+ <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">
110
+ {avatar}
111
+ </div>
112
+ );
113
+ }
114
+ return (
115
+ <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">
116
+ {String(title ?? "??")
117
+ .slice(0, 2)
118
+ .toUpperCase()}
119
+ </div>
120
+ );
121
+ }
122
+
123
+ function KanbanCardBody({ card, accent, dragging, onClick, dense }) {
124
+ const accentBorder =
125
+ ACCENT_BORDER_LEFT[card?.accent ?? accent ?? "default"] ?? ACCENT_BORDER_LEFT.default;
126
+
127
+ const Comp = onClick ? "button" : "div";
128
+
129
+ return (
130
+ <Comp
131
+ type={onClick ? "button" : undefined}
132
+ onClick={onClick ? () => onClick(card) : undefined}
133
+ className={[
134
+ "group block w-full rounded-xl border bg-white text-left shadow-sm transition",
135
+ "border-slate-200 dark:border-slate-800 dark:bg-slate-900",
136
+ "border-l-4",
137
+ accentBorder,
138
+ dense ? "p-2.5" : "p-3",
139
+ dragging ? "opacity-60 ring-2 ring-brand-500" : "hover:-translate-y-[1px] hover:shadow-md",
140
+ ]
141
+ .filter(Boolean)
142
+ .join(" ")}
143
+ >
144
+ <div className="flex items-start gap-2">
145
+ <CardAvatar avatar={card.avatar} title={card.title} />
146
+ <div className="min-w-0 flex-1">
147
+ <UIText as="div" size="sm" weight="medium" className="truncate">
148
+ {card.title}
149
+ </UIText>
150
+ {card.subtitle ? (
151
+ <UIText as="div" size="xs" muted className="mt-0.5 truncate">
152
+ {card.subtitle}
153
+ </UIText>
154
+ ) : null}
155
+ </div>
156
+ {card.status ? (
157
+ <span
158
+ aria-label={String(card.status)}
159
+ className={[
160
+ "mt-1.5 h-2 w-2 shrink-0 rounded-full",
161
+ ACCENT_DOT[card.status] ?? ACCENT_DOT.default,
162
+ ].join(" ")}
163
+ />
164
+ ) : null}
165
+ </div>
166
+
167
+ {card.description ? (
168
+ <UIText as="div" size="xs" muted className="mt-2 line-clamp-3 text-left">
169
+ {card.description}
170
+ </UIText>
171
+ ) : null}
172
+
173
+ <CardBadges badges={card.badges} />
174
+
175
+ {card.meta && card.meta.length > 0 ? (
176
+ <div className="mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-slate-500 dark:text-slate-400">
177
+ {card.meta.map((m, i) => {
178
+ if (React.isValidElement(m)) return <span key={i}>{m}</span>;
179
+ if (typeof m === "string") return <span key={i}>{m}</span>;
180
+ return (
181
+ <span key={m.id ?? i} className="inline-flex items-center gap-1">
182
+ {m.icon ? <span className="opacity-70">{m.icon}</span> : null}
183
+ <span>{m.label ?? m.text}</span>
184
+ </span>
185
+ );
186
+ })}
187
+ </div>
188
+ ) : null}
189
+
190
+ {card.actions ? (
191
+ <div
192
+ className="mt-2 flex justify-end gap-1.5"
193
+ onClick={(e) => e.stopPropagation()}
194
+ >
195
+ {card.actions}
196
+ </div>
197
+ ) : null}
198
+ </Comp>
199
+ );
200
+ }
201
+
202
+ function SortableKanbanCard({ card, accent, onClick, isDraggable, dense }) {
203
+ const sortable = useSortable({
204
+ id: card.id,
205
+ data: { columnId: card.columnId, type: "card" },
206
+ disabled: !isDraggable,
207
+ });
208
+
209
+ const style = {
210
+ transform: CSS.Translate.toString(sortable.transform),
211
+ transition: sortable.transition,
212
+ };
213
+
214
+ return (
215
+ <div
216
+ ref={sortable.setNodeRef}
217
+ style={style}
218
+ {...(isDraggable ? sortable.attributes : {})}
219
+ {...(isDraggable ? sortable.listeners : {})}
220
+ className={isDraggable ? "touch-none" : undefined}
221
+ >
222
+ <KanbanCardBody
223
+ card={card}
224
+ accent={accent}
225
+ dragging={sortable.isDragging}
226
+ onClick={onClick}
227
+ dense={dense}
228
+ />
229
+ </div>
230
+ );
231
+ }
232
+
233
+ function KanbanColumn({
234
+ column,
235
+ cards,
236
+ accent,
237
+ onCardClick,
238
+ isDraggable,
239
+ dense,
240
+ showCounts,
241
+ showLimits,
242
+ emptyMessage,
243
+ }) {
244
+ const droppable = useDroppable({
245
+ id: `column:${column.id}`,
246
+ data: { columnId: column.id, type: "column" },
247
+ });
248
+
249
+ const count = cards.length;
250
+ const overLimit = column.limit != null && count > column.limit;
251
+
252
+ return (
253
+ <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">
254
+ <div className="flex items-center justify-between gap-2 border-b border-slate-200 px-3 py-2 dark:border-slate-800">
255
+ <div className="flex min-w-0 items-center gap-2">
256
+ <span
257
+ aria-hidden
258
+ className={[
259
+ "h-2 w-2 shrink-0 rounded-full",
260
+ ACCENT_DOT[column.accent ?? accent ?? "default"] ?? ACCENT_DOT.default,
261
+ ].join(" ")}
262
+ />
263
+ <UIText as="div" size="sm" weight="medium" className="truncate">
264
+ {column.title}
265
+ </UIText>
266
+ {showCounts ? (
267
+ <UIChip
268
+ size="sm"
269
+ variant="default"
270
+ className={overLimit ? "ring-1 ring-rose-500" : undefined}
271
+ >
272
+ {showLimits && column.limit != null ? `${count} / ${column.limit}` : count}
273
+ </UIChip>
274
+ ) : null}
275
+ </div>
276
+ {column.actions ? <div className="shrink-0">{column.actions}</div> : null}
277
+ </div>
278
+
279
+ <div
280
+ ref={droppable.setNodeRef}
281
+ className={[
282
+ "flex flex-1 flex-col gap-2 overflow-y-auto p-2",
283
+ droppable.isOver ? "bg-brand-50/50 dark:bg-brand-950/30" : "",
284
+ ]
285
+ .filter(Boolean)
286
+ .join(" ")}
287
+ style={{ minHeight: 80 }}
288
+ >
289
+ <SortableContext
290
+ items={cards.map((c) => c.id)}
291
+ strategy={verticalListSortingStrategy}
292
+ >
293
+ {cards.length === 0 ? (
294
+ <div className="grid flex-1 place-items-center px-2 py-6">
295
+ <UIText as="div" size="xs" muted className="text-center">
296
+ {column.emptyMessage ?? emptyMessage ?? "No items"}
297
+ </UIText>
298
+ </div>
299
+ ) : (
300
+ cards.map((card) => (
301
+ <SortableKanbanCard
302
+ key={card.id}
303
+ card={card}
304
+ accent={accent}
305
+ onClick={onCardClick}
306
+ isDraggable={isDraggable}
307
+ dense={dense}
308
+ />
309
+ ))
310
+ )}
311
+ </SortableContext>
312
+ </div>
313
+
314
+ {column.footer ? (
315
+ <div className="border-t border-slate-200 px-3 py-2 text-xs text-slate-500 dark:border-slate-800 dark:text-slate-400">
316
+ {column.footer}
317
+ </div>
318
+ ) : null}
319
+ </div>
320
+ );
321
+ }
322
+
323
+ export default function KanbanBoard({
324
+ columns = [],
325
+ cards = [],
326
+ title,
327
+ subtitle,
328
+ accent = "default",
329
+ isDraggable = true,
330
+ dense = false,
331
+ showColumnCounts = true,
332
+ showWipLimits = true,
333
+ emptyMessage = "No items",
334
+ loading = false,
335
+ error,
336
+ actions,
337
+ onCardClick,
338
+ onCardMove,
339
+ className = "",
340
+ ...cardProps
341
+ }) {
342
+ const [activeId, setActiveId] = useState(null);
343
+ const [internalCards, setInternalCards] = useState(null);
344
+
345
+ const liveCards = internalCards ?? cards;
346
+
347
+ const sensors = useSensors(
348
+ useSensor(PointerSensor, { activationConstraint: { distance: 4 } }),
349
+ useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
350
+ );
351
+
352
+ const cardsByColumn = useMemo(() => {
353
+ const map = {};
354
+ for (const col of columns) map[col.id] = [];
355
+ for (const c of liveCards) {
356
+ const colId = c.columnId;
357
+ if (!map[colId]) map[colId] = [];
358
+ map[colId].push(c);
359
+ }
360
+ return map;
361
+ }, [columns, liveCards]);
362
+
363
+ const findColumnIdForCard = useCallback(
364
+ (cardId) => {
365
+ const card = liveCards.find((c) => c.id === cardId);
366
+ return card?.columnId ?? null;
367
+ },
368
+ [liveCards]
369
+ );
370
+
371
+ const resolveColumnId = useCallback(
372
+ (id) => {
373
+ if (!id) return null;
374
+ if (typeof id === "string" && id.startsWith("column:")) return id.slice("column:".length);
375
+ const card = liveCards.find((c) => c.id === id);
376
+ return card?.columnId ?? null;
377
+ },
378
+ [liveCards]
379
+ );
380
+
381
+ const handleDragStart = (event) => setActiveId(event.active.id);
382
+
383
+ const handleDragEnd = (event) => {
384
+ setActiveId(null);
385
+ const { active, over } = event;
386
+ if (!over) return;
387
+
388
+ const fromColumnId = findColumnIdForCard(active.id);
389
+ const toColumnId = resolveColumnId(over.id);
390
+ if (!fromColumnId || !toColumnId) return;
391
+
392
+ const fromList = (cardsByColumn[fromColumnId] ?? []).slice();
393
+ const toList =
394
+ fromColumnId === toColumnId ? fromList : (cardsByColumn[toColumnId] ?? []).slice();
395
+
396
+ const fromIndex = fromList.findIndex((c) => c.id === active.id);
397
+ let toIndex;
398
+
399
+ if (over.data?.current?.type === "column" || over.id === `column:${toColumnId}`) {
400
+ toIndex = toList.length;
401
+ } else {
402
+ toIndex = toList.findIndex((c) => c.id === over.id);
403
+ if (toIndex === -1) toIndex = toList.length;
404
+ }
405
+
406
+ if (fromColumnId === toColumnId && fromIndex === toIndex) return;
407
+
408
+ let nextCards;
409
+ if (fromColumnId === toColumnId) {
410
+ const reordered = arrayMove(fromList, fromIndex, toIndex);
411
+ const others = liveCards.filter((c) => c.columnId !== fromColumnId);
412
+ nextCards = [...others, ...reordered];
413
+ } else {
414
+ const moved = { ...fromList[fromIndex], columnId: toColumnId };
415
+ const newFrom = fromList.filter((c) => c.id !== active.id);
416
+ const newTo = toList.slice();
417
+ newTo.splice(toIndex, 0, moved);
418
+ const others = liveCards.filter(
419
+ (c) => c.columnId !== fromColumnId && c.columnId !== toColumnId
420
+ );
421
+ nextCards = [...others, ...newFrom, ...newTo];
422
+ }
423
+
424
+ if (onCardMove) {
425
+ onCardMove({
426
+ cardId: active.id,
427
+ fromColumnId,
428
+ toColumnId,
429
+ fromIndex,
430
+ toIndex,
431
+ });
432
+ } else {
433
+ setInternalCards(nextCards);
434
+ }
435
+ };
436
+
437
+ const activeCard = activeId ? liveCards.find((c) => c.id === activeId) : null;
438
+
439
+ const header =
440
+ title || subtitle || actions ? (
441
+ <div className="mb-3 flex items-start justify-between gap-3">
442
+ <div className="min-w-0">
443
+ {title ? (
444
+ <UIText as="div" size="sm" weight="medium">
445
+ {title}
446
+ </UIText>
447
+ ) : null}
448
+ {subtitle ? (
449
+ <UIText as="div" size="xs" muted className="mt-1">
450
+ {subtitle}
451
+ </UIText>
452
+ ) : null}
453
+ </div>
454
+ {actions ? <div className="shrink-0">{actions}</div> : null}
455
+ </div>
456
+ ) : null;
457
+
458
+ const board = (
459
+ <div className="flex gap-3 overflow-x-auto pb-1">
460
+ {columns.map((col) => (
461
+ <KanbanColumn
462
+ key={col.id}
463
+ column={col}
464
+ cards={cardsByColumn[col.id] ?? []}
465
+ accent={accent}
466
+ onCardClick={onCardClick}
467
+ isDraggable={isDraggable}
468
+ dense={dense}
469
+ showCounts={showColumnCounts}
470
+ showLimits={showWipLimits}
471
+ emptyMessage={emptyMessage}
472
+ />
473
+ ))}
474
+ </div>
475
+ );
476
+
477
+ return (
478
+ <BaseCard
479
+ padding="default"
480
+ isLoading={loading}
481
+ className={className}
482
+ {...cardProps}
483
+ >
484
+ {header}
485
+ {error ? (
486
+ <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">
487
+ {String(error)}
488
+ </div>
489
+ ) : (
490
+ <DndContext
491
+ sensors={sensors}
492
+ collisionDetection={closestCenter}
493
+ onDragStart={handleDragStart}
494
+ onDragEnd={handleDragEnd}
495
+ onDragCancel={() => setActiveId(null)}
496
+ >
497
+ {board}
498
+ <DragOverlay>
499
+ {activeCard ? (
500
+ <KanbanCardBody card={activeCard} accent={accent} dragging dense={dense} />
501
+ ) : null}
502
+ </DragOverlay>
503
+ </DndContext>
504
+ )}
505
+ </BaseCard>
506
+ );
507
+ }
@@ -29,6 +29,7 @@ export { default as ActivityCard } from "./cards/ActivityCard";
29
29
  export { default as MetricsStrip } from "./cards/MetricsStrip";
30
30
  export { default as CalloutCard } from "./cards/CalloutCard";
31
31
  export { default as ActionList } from "./cards/ActionList";
32
+ export { default as KanbanBoard } from "./cards/KanbanBoard";
32
33
 
33
34
  // Charts
34
35
  export { default as D3Chart } from "./charts/D3Chart";
@@ -58,31 +58,18 @@
58
58
  --color-sidebar-border: var(--sidebar-border);
59
59
  --color-sidebar-ring: var(--sidebar-ring);
60
60
 
61
- /* Engine brand paletteofficial guidelines */
62
- --color-engine-black: #0D1117;
63
- --color-engine-cyan: #7DCBD9;
64
- --color-engine-green: #1E9D6D;
65
- --color-engine-blue: #157DE5;
66
- --color-engine-orange: #FD4B23;
67
- --color-engine-amber: #FFB200;
68
- --color-engine-bg: #F3F3F4;
69
- --color-engine-border: #B0B1B3;
70
- --color-engine-text: #0D1117;
71
- --color-engine-muted: #616368;
72
- --color-engine-label: #B0B1B3;
73
-
74
- /* Engine Cyan brand ramp */
75
- --color-brand-50: #F0FAFB;
76
- --color-brand-100: #D9F2F5;
77
- --color-brand-200: #B3E5EB;
78
- --color-brand-300: #7DCBD9;
79
- --color-brand-400: #5BB8CA;
80
- --color-brand-500: #3AA0B5;
81
- --color-brand-600: #2D849A;
82
- --color-brand-700: #266B7E;
83
- --color-brand-800: #235768;
84
- --color-brand-900: #1F4858;
85
- --color-brand-950: #112E3A;
61
+ /* Neutral brand rampoverride per-app to apply your brand colors */
62
+ --color-brand-50: #f8fafc;
63
+ --color-brand-100: #f1f5f9;
64
+ --color-brand-200: #e2e8f0;
65
+ --color-brand-300: #cbd5e1;
66
+ --color-brand-400: #94a3b8;
67
+ --color-brand-500: #64748b;
68
+ --color-brand-600: #475569;
69
+ --color-brand-700: #334155;
70
+ --color-brand-800: #1e293b;
71
+ --color-brand-900: #0f172a;
72
+ --color-brand-950: #020617;
86
73
 
87
74
  --font-sans: 'Inter', ui-sans-serif, system-ui, -apple-system, sans-serif;
88
75
  --font-mono: 'JetBrains Mono', ui-monospace, monospace;
@@ -92,22 +79,22 @@
92
79
  }
93
80
 
94
81
  :root {
95
- --dash-text: #0D1117;
96
- --dash-muted: #3d4047;
97
- --dash-label: #8b8d91;
98
- --dash-surface: #fef9ef;
99
- --dash-border: #e2c97a;
100
- --dash-accent: #FFB200;
82
+ --dash-text: #0f172a;
83
+ --dash-muted: #475569;
84
+ --dash-label: #94a3b8;
85
+ --dash-surface: #ffffff;
86
+ --dash-border: #e2e8f0;
87
+ --dash-accent: #64748b;
101
88
  --dash-success: #34d399;
102
89
  --dash-info: #67e8f9;
103
- --dash-warning: #FD4B23;
90
+ --dash-warning: #f59e0b;
104
91
  --dash-danger: #dc2626;
105
- --dash-dark: #0D1117;
106
- --dash-darker: #06090d;
107
- --dash-chart-1: #FFB200;
108
- --dash-chart-2: #1E9D6D;
109
- --dash-chart-3: #7DCBD9;
110
- --dash-chart-4: #FD4B23;
92
+ --dash-dark: #0f172a;
93
+ --dash-darker: #020617;
94
+ --dash-chart-1: #64748b;
95
+ --dash-chart-2: #34d399;
96
+ --dash-chart-3: #67e8f9;
97
+ --dash-chart-4: #f59e0b;
111
98
  --dash-metric-size: 2.5rem;
112
99
  --dash-metric-sub: 2.25rem;
113
100
  --color-dash-text: var(--dash-text);
@@ -206,37 +193,37 @@
206
193
 
207
194
  /*
208
195
  * Restore HeroUI theme variables inside the Command Center scope.
209
- * Uses Engine brand colors: Cyan #7DCBD9 as secondary, Orange-Red #FD4B23 as danger.
196
+ * Neutral defaults override per-app to apply your brand colors.
210
197
  */
211
198
  .heroui-scope {
212
- --primary: #0D1117;
199
+ --primary: oklch(0.205 0 0);
213
200
  --primary-foreground: oklch(0.9911 0 0);
214
- --secondary: #7DCBD9;
215
- --secondary-foreground: #0D1117;
216
- --success: #1E9D6D;
201
+ --secondary: oklch(0.55 0.02 240);
202
+ --secondary-foreground: oklch(0.9911 0 0);
203
+ --success: #16a34a;
217
204
  --success-foreground: oklch(0.9911 0 0);
218
- --warning: #FFB200;
219
- --warning-foreground: #0D1117;
220
- --danger: #FD4B23;
205
+ --warning: #f59e0b;
206
+ --warning-foreground: oklch(0.205 0 0);
207
+ --danger: #dc2626;
221
208
  --danger-foreground: oklch(0.9911 0 0);
222
209
 
223
210
  --muted: oklch(0.5517 0.0138 285.94);
224
211
  --accent: oklch(0.6204 0.195 253.83);
225
212
  --accent-foreground: oklch(0.9911 0 0);
226
- --background: #F3F3F4;
227
- --foreground: #0D1117;
213
+ --background: oklch(0.985 0 0);
214
+ --foreground: oklch(0.145 0 0);
228
215
  --default: oklch(94% 0.001 286.375);
229
- --default-foreground: #0D1117;
230
- --border: #B0B1B3;
216
+ --default-foreground: oklch(0.145 0 0);
217
+ --border: oklch(0.85 0.005 286);
231
218
  --separator: oklch(92% 0.004 286.32);
232
219
  --segment: oklch(100% 0 0);
233
- --segment-foreground: #0D1117;
220
+ --segment-foreground: oklch(0.145 0 0);
234
221
  --surface: oklch(100% 0 0);
235
- --surface-foreground: #0D1117;
222
+ --surface-foreground: oklch(0.145 0 0);
236
223
  --overlay: oklch(100% 0 0);
237
- --overlay-foreground: #0D1117;
238
- --focus: #157DE5;
239
- --link: #0D1117;
224
+ --overlay-foreground: oklch(0.145 0 0);
225
+ --focus: oklch(0.6 0.18 250);
226
+ --link: oklch(0.205 0 0);
240
227
  }
241
228
 
242
229
  /* ChatBar expanded overlay — horizontal padding so it doesn't hit window edges */
@@ -261,9 +248,9 @@ body > .fixed.inset-x-0.rounded-2xl {
261
248
  --overlay: oklch(0.2103 0.0059 285.89);
262
249
  --overlay-foreground: oklch(0.9911 0 0);
263
250
  --warning: oklch(0.8203 0.1388 76.34);
264
- --warning-foreground: #0D1117;
251
+ --warning-foreground: oklch(0.145 0 0);
265
252
  --danger: oklch(0.594 0.1967 24.63);
266
253
  --danger-foreground: oklch(0.9911 0 0);
267
- --focus: #157DE5;
254
+ --focus: oklch(0.6 0.18 250);
268
255
  --link: oklch(0.9911 0 0);
269
256
  }