@schandlergarcia/sf-web-components 2.4.0 → 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.
- package/.a4drules/skills/command-center-builder/SKILL.md +3 -2
- package/.a4drules/skills/component-library/SKILL.md +50 -4
- package/.a4drules/skills/component-library/card-components.md +88 -0
- package/.a4drules/skills/component-library/when-to-use.md +1 -0
- package/CHANGELOG.md +24 -0
- package/dist/components/library/cards/KanbanBoard.js +313 -0
- package/dist/components/library/cards/KanbanBoard.js.map +1 -0
- package/dist/components/library/index.js +60 -57
- package/dist/components/library/index.js.map +1 -1
- package/dist/components/workspace/ComponentRegistry.js +5 -2
- package/dist/components/workspace/ComponentRegistry.js.map +1 -1
- package/dist/index.js +84 -82
- package/dist/index.js.map +1 -1
- package/package.json +7 -1
- package/src/components/library/cards/KanbanBoard.jsx +507 -0
- package/src/components/library/index.jsx +1 -0
|
@@ -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,
|
|
7
|
-
StatusCard, WidgetCard, BaseCard, ChatBar, Avatar, UIChip,
|
|
8
|
-
their props, data shapes, and import patterns. Use when
|
|
9
|
-
to use a specific component or choosing which component
|
|
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,30 @@ 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
|
+
|
|
8
32
|
## [2.4.0] - 2026-05-07
|
|
9
33
|
|
|
10
34
|
### Removed
|
|
@@ -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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"KanbanBoard.js","sources":["../../../../src/components/library/cards/KanbanBoard.jsx"],"sourcesContent":["import React, { useMemo, useState, useCallback } from \"react\";\nimport {\n DndContext,\n DragOverlay,\n PointerSensor,\n KeyboardSensor,\n useSensor,\n useSensors,\n closestCenter,\n useDroppable,\n} from \"@dnd-kit/core\";\nimport {\n SortableContext,\n useSortable,\n verticalListSortingStrategy,\n sortableKeyboardCoordinates,\n arrayMove,\n} from \"@dnd-kit/sortable\";\nimport { CSS } from \"@dnd-kit/utilities\";\nimport BaseCard from \"./BaseCard\";\nimport UIText from \"../ui/Text\";\nimport UIChip from \"../ui/Chip\";\n\n/**\n * KanbanBoard\n *\n * A swim-lane / board view with structured cards and (optional) drag-and-drop\n * powered by @dnd-kit. Designed to live inside a CommandCenter dashboard and\n * to behave consistently with ListCard / TableCard.\n *\n * Data shapes:\n * columns: [{ id, title, accent?, limit?, footer?, emptyMessage? }]\n * cards: [{ id, columnId, title, subtitle?, description?, badges?,\n * status?, avatar?, meta?, actions?, accent? }]\n *\n * Callbacks:\n * onCardClick(card)\n * onCardMove({ cardId, fromColumnId, toColumnId, fromIndex, toIndex })\n */\n\nconst ACCENT_RING = {\n default: \"ring-slate-200 dark:ring-slate-800\",\n primary: \"ring-brand-500/40\",\n success: \"ring-emerald-500/40\",\n warning: \"ring-amber-500/40\",\n danger: \"ring-rose-500/40\",\n info: \"ring-sky-500/40\",\n};\n\nconst ACCENT_DOT = {\n default: \"bg-slate-400\",\n primary: \"bg-brand-500\",\n success: \"bg-emerald-500\",\n warning: \"bg-amber-500\",\n danger: \"bg-rose-500\",\n info: \"bg-sky-500\",\n};\n\nconst ACCENT_BORDER_LEFT = {\n default: \"border-l-slate-300 dark:border-l-slate-700\",\n primary: \"border-l-brand-500\",\n success: \"border-l-emerald-500\",\n warning: \"border-l-amber-500\",\n danger: \"border-l-rose-500\",\n info: \"border-l-sky-500\",\n};\n\nfunction CardBadges({ badges }) {\n if (!badges || badges.length === 0) return null;\n return (\n <div className=\"mt-2 flex flex-wrap gap-1.5\">\n {badges.map((b, i) => {\n if (React.isValidElement(b)) return <span key={i}>{b}</span>;\n if (typeof b === \"string\") {\n return (\n <UIChip key={i} size=\"sm\" variant=\"default\">\n {b}\n </UIChip>\n );\n }\n return (\n <UIChip\n key={b.id ?? i}\n size={b.size ?? \"sm\"}\n variant={b.variant ?? \"default\"}\n color={b.color}\n >\n {b.label ?? b.text}\n </UIChip>\n );\n })}\n </div>\n );\n}\n\nfunction CardAvatar({ avatar, title }) {\n if (!avatar) return null;\n if (typeof avatar === \"string\") {\n return (\n <img\n src={avatar}\n alt=\"\"\n className=\"h-7 w-7 shrink-0 rounded-full border border-slate-200 object-cover dark:border-slate-800\"\n />\n );\n }\n if (React.isValidElement(avatar)) {\n return (\n <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\">\n {avatar}\n </div>\n );\n }\n return (\n <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\">\n {String(title ?? \"??\")\n .slice(0, 2)\n .toUpperCase()}\n </div>\n );\n}\n\nfunction KanbanCardBody({ card, accent, dragging, onClick, dense }) {\n const accentBorder =\n ACCENT_BORDER_LEFT[card?.accent ?? accent ?? \"default\"] ?? ACCENT_BORDER_LEFT.default;\n\n const Comp = onClick ? \"button\" : \"div\";\n\n return (\n <Comp\n type={onClick ? \"button\" : undefined}\n onClick={onClick ? () => onClick(card) : undefined}\n className={[\n \"group block w-full rounded-xl border bg-white text-left shadow-sm transition\",\n \"border-slate-200 dark:border-slate-800 dark:bg-slate-900\",\n \"border-l-4\",\n accentBorder,\n dense ? \"p-2.5\" : \"p-3\",\n dragging ? \"opacity-60 ring-2 ring-brand-500\" : \"hover:-translate-y-[1px] hover:shadow-md\",\n ]\n .filter(Boolean)\n .join(\" \")}\n >\n <div className=\"flex items-start gap-2\">\n <CardAvatar avatar={card.avatar} title={card.title} />\n <div className=\"min-w-0 flex-1\">\n <UIText as=\"div\" size=\"sm\" weight=\"medium\" className=\"truncate\">\n {card.title}\n </UIText>\n {card.subtitle ? (\n <UIText as=\"div\" size=\"xs\" muted className=\"mt-0.5 truncate\">\n {card.subtitle}\n </UIText>\n ) : null}\n </div>\n {card.status ? (\n <span\n aria-label={String(card.status)}\n className={[\n \"mt-1.5 h-2 w-2 shrink-0 rounded-full\",\n ACCENT_DOT[card.status] ?? ACCENT_DOT.default,\n ].join(\" \")}\n />\n ) : null}\n </div>\n\n {card.description ? (\n <UIText as=\"div\" size=\"xs\" muted className=\"mt-2 line-clamp-3 text-left\">\n {card.description}\n </UIText>\n ) : null}\n\n <CardBadges badges={card.badges} />\n\n {card.meta && card.meta.length > 0 ? (\n <div className=\"mt-2 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-slate-500 dark:text-slate-400\">\n {card.meta.map((m, i) => {\n if (React.isValidElement(m)) return <span key={i}>{m}</span>;\n if (typeof m === \"string\") return <span key={i}>{m}</span>;\n return (\n <span key={m.id ?? i} className=\"inline-flex items-center gap-1\">\n {m.icon ? <span className=\"opacity-70\">{m.icon}</span> : null}\n <span>{m.label ?? m.text}</span>\n </span>\n );\n })}\n </div>\n ) : null}\n\n {card.actions ? (\n <div\n className=\"mt-2 flex justify-end gap-1.5\"\n onClick={(e) => e.stopPropagation()}\n >\n {card.actions}\n </div>\n ) : null}\n </Comp>\n );\n}\n\nfunction SortableKanbanCard({ card, accent, onClick, isDraggable, dense }) {\n const sortable = useSortable({\n id: card.id,\n data: { columnId: card.columnId, type: \"card\" },\n disabled: !isDraggable,\n });\n\n const style = {\n transform: CSS.Translate.toString(sortable.transform),\n transition: sortable.transition,\n };\n\n return (\n <div\n ref={sortable.setNodeRef}\n style={style}\n {...(isDraggable ? sortable.attributes : {})}\n {...(isDraggable ? sortable.listeners : {})}\n className={isDraggable ? \"touch-none\" : undefined}\n >\n <KanbanCardBody\n card={card}\n accent={accent}\n dragging={sortable.isDragging}\n onClick={onClick}\n dense={dense}\n />\n </div>\n );\n}\n\nfunction KanbanColumn({\n column,\n cards,\n accent,\n onCardClick,\n isDraggable,\n dense,\n showCounts,\n showLimits,\n emptyMessage,\n}) {\n const droppable = useDroppable({\n id: `column:${column.id}`,\n data: { columnId: column.id, type: \"column\" },\n });\n\n const count = cards.length;\n const overLimit = column.limit != null && count > column.limit;\n\n return (\n <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\">\n <div className=\"flex items-center justify-between gap-2 border-b border-slate-200 px-3 py-2 dark:border-slate-800\">\n <div className=\"flex min-w-0 items-center gap-2\">\n <span\n aria-hidden\n className={[\n \"h-2 w-2 shrink-0 rounded-full\",\n ACCENT_DOT[column.accent ?? accent ?? \"default\"] ?? ACCENT_DOT.default,\n ].join(\" \")}\n />\n <UIText as=\"div\" size=\"sm\" weight=\"medium\" className=\"truncate\">\n {column.title}\n </UIText>\n {showCounts ? (\n <UIChip\n size=\"sm\"\n variant=\"default\"\n className={overLimit ? \"ring-1 ring-rose-500\" : undefined}\n >\n {showLimits && column.limit != null ? `${count} / ${column.limit}` : count}\n </UIChip>\n ) : null}\n </div>\n {column.actions ? <div className=\"shrink-0\">{column.actions}</div> : null}\n </div>\n\n <div\n ref={droppable.setNodeRef}\n className={[\n \"flex flex-1 flex-col gap-2 overflow-y-auto p-2\",\n droppable.isOver ? \"bg-brand-50/50 dark:bg-brand-950/30\" : \"\",\n ]\n .filter(Boolean)\n .join(\" \")}\n style={{ minHeight: 80 }}\n >\n <SortableContext\n items={cards.map((c) => c.id)}\n strategy={verticalListSortingStrategy}\n >\n {cards.length === 0 ? (\n <div className=\"grid flex-1 place-items-center px-2 py-6\">\n <UIText as=\"div\" size=\"xs\" muted className=\"text-center\">\n {column.emptyMessage ?? emptyMessage ?? \"No items\"}\n </UIText>\n </div>\n ) : (\n cards.map((card) => (\n <SortableKanbanCard\n key={card.id}\n card={card}\n accent={accent}\n onClick={onCardClick}\n isDraggable={isDraggable}\n dense={dense}\n />\n ))\n )}\n </SortableContext>\n </div>\n\n {column.footer ? (\n <div className=\"border-t border-slate-200 px-3 py-2 text-xs text-slate-500 dark:border-slate-800 dark:text-slate-400\">\n {column.footer}\n </div>\n ) : null}\n </div>\n );\n}\n\nexport default function KanbanBoard({\n columns = [],\n cards = [],\n title,\n subtitle,\n accent = \"default\",\n isDraggable = true,\n dense = false,\n showColumnCounts = true,\n showWipLimits = true,\n emptyMessage = \"No items\",\n loading = false,\n error,\n actions,\n onCardClick,\n onCardMove,\n className = \"\",\n ...cardProps\n}) {\n const [activeId, setActiveId] = useState(null);\n const [internalCards, setInternalCards] = useState(null);\n\n const liveCards = internalCards ?? cards;\n\n const sensors = useSensors(\n useSensor(PointerSensor, { activationConstraint: { distance: 4 } }),\n useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })\n );\n\n const cardsByColumn = useMemo(() => {\n const map = {};\n for (const col of columns) map[col.id] = [];\n for (const c of liveCards) {\n const colId = c.columnId;\n if (!map[colId]) map[colId] = [];\n map[colId].push(c);\n }\n return map;\n }, [columns, liveCards]);\n\n const findColumnIdForCard = useCallback(\n (cardId) => {\n const card = liveCards.find((c) => c.id === cardId);\n return card?.columnId ?? null;\n },\n [liveCards]\n );\n\n const resolveColumnId = useCallback(\n (id) => {\n if (!id) return null;\n if (typeof id === \"string\" && id.startsWith(\"column:\")) return id.slice(\"column:\".length);\n const card = liveCards.find((c) => c.id === id);\n return card?.columnId ?? null;\n },\n [liveCards]\n );\n\n const handleDragStart = (event) => setActiveId(event.active.id);\n\n const handleDragEnd = (event) => {\n setActiveId(null);\n const { active, over } = event;\n if (!over) return;\n\n const fromColumnId = findColumnIdForCard(active.id);\n const toColumnId = resolveColumnId(over.id);\n if (!fromColumnId || !toColumnId) return;\n\n const fromList = (cardsByColumn[fromColumnId] ?? []).slice();\n const toList =\n fromColumnId === toColumnId ? fromList : (cardsByColumn[toColumnId] ?? []).slice();\n\n const fromIndex = fromList.findIndex((c) => c.id === active.id);\n let toIndex;\n\n if (over.data?.current?.type === \"column\" || over.id === `column:${toColumnId}`) {\n toIndex = toList.length;\n } else {\n toIndex = toList.findIndex((c) => c.id === over.id);\n if (toIndex === -1) toIndex = toList.length;\n }\n\n if (fromColumnId === toColumnId && fromIndex === toIndex) return;\n\n let nextCards;\n if (fromColumnId === toColumnId) {\n const reordered = arrayMove(fromList, fromIndex, toIndex);\n const others = liveCards.filter((c) => c.columnId !== fromColumnId);\n nextCards = [...others, ...reordered];\n } else {\n const moved = { ...fromList[fromIndex], columnId: toColumnId };\n const newFrom = fromList.filter((c) => c.id !== active.id);\n const newTo = toList.slice();\n newTo.splice(toIndex, 0, moved);\n const others = liveCards.filter(\n (c) => c.columnId !== fromColumnId && c.columnId !== toColumnId\n );\n nextCards = [...others, ...newFrom, ...newTo];\n }\n\n if (onCardMove) {\n onCardMove({\n cardId: active.id,\n fromColumnId,\n toColumnId,\n fromIndex,\n toIndex,\n });\n } else {\n setInternalCards(nextCards);\n }\n };\n\n const activeCard = activeId ? liveCards.find((c) => c.id === activeId) : null;\n\n const header =\n title || subtitle || actions ? (\n <div className=\"mb-3 flex items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n {title ? (\n <UIText as=\"div\" size=\"sm\" weight=\"medium\">\n {title}\n </UIText>\n ) : null}\n {subtitle ? (\n <UIText as=\"div\" size=\"xs\" muted className=\"mt-1\">\n {subtitle}\n </UIText>\n ) : null}\n </div>\n {actions ? <div className=\"shrink-0\">{actions}</div> : null}\n </div>\n ) : null;\n\n const board = (\n <div className=\"flex gap-3 overflow-x-auto pb-1\">\n {columns.map((col) => (\n <KanbanColumn\n key={col.id}\n column={col}\n cards={cardsByColumn[col.id] ?? []}\n accent={accent}\n onCardClick={onCardClick}\n isDraggable={isDraggable}\n dense={dense}\n showCounts={showColumnCounts}\n showLimits={showWipLimits}\n emptyMessage={emptyMessage}\n />\n ))}\n </div>\n );\n\n return (\n <BaseCard\n padding=\"default\"\n isLoading={loading}\n className={className}\n {...cardProps}\n >\n {header}\n {error ? (\n <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\">\n {String(error)}\n </div>\n ) : (\n <DndContext\n sensors={sensors}\n collisionDetection={closestCenter}\n onDragStart={handleDragStart}\n onDragEnd={handleDragEnd}\n onDragCancel={() => setActiveId(null)}\n >\n {board}\n <DragOverlay>\n {activeCard ? (\n <KanbanCardBody card={activeCard} accent={accent} dragging dense={dense} />\n ) : null}\n </DragOverlay>\n </DndContext>\n )}\n </BaseCard>\n );\n}\n"],"names":["ACCENT_DOT","ACCENT_BORDER_LEFT","CardBadges","badges","jsx","b","i","React","UIChip","CardAvatar","avatar","title","KanbanCardBody","card","accent","dragging","onClick","dense","accentBorder","jsxs","UIText","m","e","SortableKanbanCard","isDraggable","sortable","useSortable","style","CSS","KanbanColumn","column","cards","onCardClick","showCounts","showLimits","emptyMessage","droppable","useDroppable","count","overLimit","SortableContext","c","verticalListSortingStrategy","KanbanBoard","columns","subtitle","showColumnCounts","showWipLimits","loading","error","actions","onCardMove","className","cardProps","activeId","setActiveId","useState","internalCards","setInternalCards","liveCards","sensors","useSensors","useSensor","PointerSensor","KeyboardSensor","sortableKeyboardCoordinates","cardsByColumn","useMemo","map","col","colId","findColumnIdForCard","useCallback","cardId","resolveColumnId","id","handleDragStart","event","handleDragEnd","active","over","fromColumnId","toColumnId","fromList","toList","fromIndex","toIndex","nextCards","reordered","arrayMove","moved","newFrom","newTo","activeCard","header","board","BaseCard","DndContext","closestCenter","DragOverlay"],"mappings":"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"}
|