@pixpilot/shadcn-kanban 0.0.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 (68) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +46 -0
  3. package/dist/AddColumnButton.cjs +82 -0
  4. package/dist/AddColumnButton.js +77 -0
  5. package/dist/ColumnFilterButton.cjs +68 -0
  6. package/dist/ColumnFilterButton.js +63 -0
  7. package/dist/KanbanBoard.cjs +137 -0
  8. package/dist/KanbanBoard.d.cts +36 -0
  9. package/dist/KanbanBoard.d.ts +36 -0
  10. package/dist/KanbanBoard.js +131 -0
  11. package/dist/KanbanColumn.cjs +142 -0
  12. package/dist/KanbanColumn.js +134 -0
  13. package/dist/KanbanColumnCards.cjs +25 -0
  14. package/dist/KanbanColumnCards.js +23 -0
  15. package/dist/KanbanDragOverlay.cjs +42 -0
  16. package/dist/KanbanDragOverlay.js +37 -0
  17. package/dist/KanbanItem.cjs +58 -0
  18. package/dist/KanbanItem.js +52 -0
  19. package/dist/KanbanVirtualColumnCards.cjs +70 -0
  20. package/dist/KanbanVirtualColumnCards.js +66 -0
  21. package/dist/_virtual/rolldown_runtime.cjs +25 -0
  22. package/dist/hooks/use-kanban-board-state.cjs +43 -0
  23. package/dist/hooks/use-kanban-board-state.js +41 -0
  24. package/dist/hooks/use-kanban-card-drag.cjs +83 -0
  25. package/dist/hooks/use-kanban-card-drag.js +81 -0
  26. package/dist/hooks/use-kanban-collision-detection.cjs +70 -0
  27. package/dist/hooks/use-kanban-collision-detection.js +67 -0
  28. package/dist/hooks/use-kanban-column-reorder.cjs +31 -0
  29. package/dist/hooks/use-kanban-column-reorder.js +28 -0
  30. package/dist/hooks/use-kanban-drag.cjs +98 -0
  31. package/dist/hooks/use-kanban-drag.js +95 -0
  32. package/dist/hooks/use-kanban-filters.cjs +49 -0
  33. package/dist/hooks/use-kanban-filters.js +47 -0
  34. package/dist/index.cjs +3 -0
  35. package/dist/index.d.cts +5 -0
  36. package/dist/index.d.ts +5 -0
  37. package/dist/index.js +3 -0
  38. package/dist/infinite-scroll/InfiniteScrollSentinel.cjs +54 -0
  39. package/dist/infinite-scroll/InfiniteScrollSentinel.d.cts +2 -0
  40. package/dist/infinite-scroll/InfiniteScrollSentinel.d.ts +2 -0
  41. package/dist/infinite-scroll/InfiniteScrollSentinel.js +49 -0
  42. package/dist/infinite-scroll/index.d.cts +3 -0
  43. package/dist/infinite-scroll/index.d.ts +3 -0
  44. package/dist/infinite-scroll/types.d.cts +29 -0
  45. package/dist/infinite-scroll/types.d.ts +29 -0
  46. package/dist/infinite-scroll/use-intersection-observer.cjs +46 -0
  47. package/dist/infinite-scroll/use-intersection-observer.d.cts +1 -0
  48. package/dist/infinite-scroll/use-intersection-observer.d.ts +1 -0
  49. package/dist/infinite-scroll/use-intersection-observer.js +44 -0
  50. package/dist/types.d.cts +251 -0
  51. package/dist/types.d.ts +251 -0
  52. package/dist/utils/apply-kanban-drop.cjs +37 -0
  53. package/dist/utils/apply-kanban-drop.js +35 -0
  54. package/dist/utils/column-sortable-id.cjs +23 -0
  55. package/dist/utils/column-sortable-id.js +20 -0
  56. package/dist/utils/is-below-over-item.cjs +17 -0
  57. package/dist/utils/is-below-over-item.js +16 -0
  58. package/dist/utils/items-order-equal.cjs +13 -0
  59. package/dist/utils/items-order-equal.js +12 -0
  60. package/dist/utils/kanban-keyboard-coordinates.cjs +64 -0
  61. package/dist/utils/kanban-keyboard-coordinates.js +62 -0
  62. package/dist/utils/move-kanban-item.cjs +28 -0
  63. package/dist/utils/move-kanban-item.js +27 -0
  64. package/dist/utils/replace-state.cjs +15 -0
  65. package/dist/utils/replace-state.js +14 -0
  66. package/dist/utils/resolve-column-id.cjs +25 -0
  67. package/dist/utils/resolve-column-id.js +24 -0
  68. package/package.json +97 -0
@@ -0,0 +1,131 @@
1
+ 'use client';
2
+
3
+
4
+ import { AddColumnButton } from "./AddColumnButton.js";
5
+ import { toColumnSortableId } from "./utils/column-sortable-id.js";
6
+ import { useKanbanDrag } from "./hooks/use-kanban-drag.js";
7
+ import { useKanbanFilters } from "./hooks/use-kanban-filters.js";
8
+ import { KanbanColumn } from "./KanbanColumn.js";
9
+ import { KanbanDragOverlay } from "./KanbanDragOverlay.js";
10
+ import { DndContext, MeasuringStrategy } from "@dnd-kit/core";
11
+ import { SortableContext, horizontalListSortingStrategy } from "@dnd-kit/sortable";
12
+ import { cn } from "@pixpilot/shadcn-ui";
13
+ import React from "react";
14
+ import { jsx, jsxs } from "react/jsx-runtime";
15
+
16
+ //#region src/KanbanBoard.tsx
17
+ const MEASURING = { droppable: { strategy: MeasuringStrategy.Always } };
18
+ /**
19
+ * Warns once, in development only, when a scroll-only feature is requested for
20
+ * a column mode that cannot support it. Deliberately non-throwing: a mis-set
21
+ * prop should not take a board down in production.
22
+ */
23
+ function useScrollOnlyFeatureWarning(feature, consequence, requested, columnOverflow) {
24
+ const warned = React.useRef(false);
25
+ React.useEffect(() => {
26
+ if (process.env.NODE_ENV === "production") return;
27
+ if (!requested || columnOverflow === "scroll" || warned.current) return;
28
+ warned.current = true;
29
+ console.error(`[KanbanBoard] \`${feature}\` is not implemented for columnOverflow="${columnOverflow}" — only "scroll" is supported. ${consequence}`);
30
+ }, [
31
+ feature,
32
+ consequence,
33
+ requested,
34
+ columnOverflow
35
+ ]);
36
+ }
37
+ /**
38
+ * A generic, reusable Kanban board with drag-and-drop powered by `@dnd-kit`.
39
+ *
40
+ * Accepts an array of {@link KanbanItem} items and {@link KanbanColumn}
41
+ * column definitions and calls `onChange` whenever an item is moved
42
+ * between (or within) columns.
43
+ */
44
+ function KanbanBoard({ items: externalItems, columns, onChange, renderItem, renderColumnHeader, className, style, columnClassName, getColumnProps, hideColumnHeaders = false, itemClassName, columnOverflow = "scroll", allowAddColumn, onAddColumn, onColumnChange, filters, onFilterChange, infiniteScroll, virtualization, dragDisabled = false }) {
45
+ useScrollOnlyFeatureWarning("infiniteScroll", "The sentinel will not be rendered.", Boolean(infiniteScroll), columnOverflow);
46
+ useScrollOnlyFeatureWarning("virtualization", "Every card will be rendered.", Boolean(virtualization), columnOverflow);
47
+ const activeInfiniteScroll = columnOverflow === "scroll" ? infiniteScroll : void 0;
48
+ const activeVirtualization = columnOverflow === "scroll" && virtualization?.disabled !== true ? virtualization : void 0;
49
+ const { items, internalColumns, activeId, isDraggingColumn, sensors, collisionDetection, handleDragStart, handleDragOver, handleDragEnd, handleDragCancel } = useKanbanDrag({
50
+ externalItems,
51
+ columns,
52
+ onChange,
53
+ onColumnChange
54
+ });
55
+ const { activeFilters, resolveFilters, handleToggleFilter, handleClearFilters } = useKanbanFilters({
56
+ filters,
57
+ onFilterChange
58
+ });
59
+ const activeItem = activeId !== null && !isDraggingColumn ? items.find((i) => i.id === activeId) ?? null : null;
60
+ const activeItemColumn = activeItem ? internalColumns.find((c) => c.id === activeItem.columnId) : void 0;
61
+ const activeColumn = activeId !== null && isDraggingColumn ? internalColumns.find((c) => toColumnSortableId(c.id) === String(activeId)) : void 0;
62
+ const columnItemsMap = React.useMemo(() => internalColumns.map((col) => {
63
+ const colFilters = resolveFilters(col);
64
+ const activeIds = activeFilters[col.id] ?? [];
65
+ const activePredicates = colFilters.filter((f) => activeIds.includes(f.id) && typeof f.predicate === "function");
66
+ let colItems = items.filter((i) => i.columnId === col.id);
67
+ if (activePredicates.length > 0) colItems = colItems.filter((item) => activePredicates.every((f) => f.predicate(item, col)));
68
+ return {
69
+ column: col,
70
+ colItems,
71
+ colFilters,
72
+ activeIds
73
+ };
74
+ }), [
75
+ internalColumns,
76
+ items,
77
+ resolveFilters,
78
+ activeFilters
79
+ ]);
80
+ const columnSortableIds = React.useMemo(() => internalColumns.map((c) => toColumnSortableId(c.id)), [internalColumns]);
81
+ const isColumnSortable = Boolean(onColumnChange) && !dragDisabled;
82
+ return /* @__PURE__ */ jsxs(DndContext, {
83
+ sensors,
84
+ collisionDetection,
85
+ measuring: MEASURING,
86
+ onDragStart: handleDragStart,
87
+ onDragOver: handleDragOver,
88
+ onDragEnd: handleDragEnd,
89
+ onDragCancel: handleDragCancel,
90
+ children: [/* @__PURE__ */ jsx(SortableContext, {
91
+ items: columnSortableIds,
92
+ strategy: horizontalListSortingStrategy,
93
+ children: /* @__PURE__ */ jsxs("div", {
94
+ "data-testid": "kanban-board",
95
+ className: cn("relative flex gap-4 overflow-x-auto overscroll-x-contain p-2", columnOverflow === "scroll" ? "h-full min-h-0" : "min-h-full", className),
96
+ style,
97
+ children: [columnItemsMap.map(({ column, colItems, colFilters, activeIds }) => /* @__PURE__ */ jsx(KanbanColumn, {
98
+ column,
99
+ items: colItems,
100
+ renderItem,
101
+ renderColumnHeader,
102
+ columnClassName,
103
+ containerProps: getColumnProps?.(column),
104
+ hideHeader: hideColumnHeaders,
105
+ itemClassName,
106
+ sortable: isColumnSortable,
107
+ filters: colFilters,
108
+ activeFilterIds: activeIds,
109
+ columnOverflow,
110
+ infiniteScroll: activeInfiniteScroll,
111
+ virtualization: activeVirtualization,
112
+ dragDisabled,
113
+ activeItemId: activeItem?.id ?? null,
114
+ onToggleFilter: (filterId) => handleToggleFilter(column, filterId),
115
+ onClearFilters: () => handleClearFilters(column)
116
+ }, column.id)), allowAddColumn && onAddColumn ? /* @__PURE__ */ jsx(AddColumnButton, { onAdd: onAddColumn }) : null]
117
+ })
118
+ }), /* @__PURE__ */ jsx(KanbanDragOverlay, {
119
+ activeItem,
120
+ activeItemColumn,
121
+ activeColumn,
122
+ activeColumnItemCount: activeColumn ? items.filter((i) => i.columnId === activeColumn.id).length : 0,
123
+ renderItem,
124
+ itemClassName,
125
+ columnClassName
126
+ })]
127
+ });
128
+ }
129
+
130
+ //#endregion
131
+ export { KanbanBoard };
@@ -0,0 +1,142 @@
1
+ 'use client';
2
+
3
+
4
+ const require_rolldown_runtime = require('./_virtual/rolldown_runtime.cjs');
5
+ const require_column_sortable_id = require('./utils/column-sortable-id.cjs');
6
+ const require_ColumnFilterButton = require('./ColumnFilterButton.cjs');
7
+ const require_InfiniteScrollSentinel = require('./infinite-scroll/InfiniteScrollSentinel.cjs');
8
+ const require_KanbanColumnCards = require('./KanbanColumnCards.cjs');
9
+ const require_KanbanVirtualColumnCards = require('./KanbanVirtualColumnCards.cjs');
10
+ let __dnd_kit_core = require("@dnd-kit/core");
11
+ __dnd_kit_core = require_rolldown_runtime.__toESM(__dnd_kit_core);
12
+ let __dnd_kit_sortable = require("@dnd-kit/sortable");
13
+ __dnd_kit_sortable = require_rolldown_runtime.__toESM(__dnd_kit_sortable);
14
+ let __pixpilot_shadcn_ui = require("@pixpilot/shadcn-ui");
15
+ __pixpilot_shadcn_ui = require_rolldown_runtime.__toESM(__pixpilot_shadcn_ui);
16
+ let react = require("react");
17
+ react = require_rolldown_runtime.__toESM(react);
18
+ let lucide_react = require("lucide-react");
19
+ lucide_react = require_rolldown_runtime.__toESM(lucide_react);
20
+ let react_jsx_runtime = require("react/jsx-runtime");
21
+ react_jsx_runtime = require_rolldown_runtime.__toESM(react_jsx_runtime);
22
+ let __dnd_kit_utilities = require("@dnd-kit/utilities");
23
+ __dnd_kit_utilities = require_rolldown_runtime.__toESM(__dnd_kit_utilities);
24
+
25
+ //#region src/KanbanColumn.tsx
26
+ function KanbanColumn({ column, items, renderItem, renderColumnHeader, columnClassName, itemClassName, containerProps, hideHeader = false, sortable = false, filters, activeFilterIds, columnOverflow, infiniteScroll, virtualization, dragDisabled = false, activeItemId = null, onToggleFilter, onClearFilters }) {
27
+ const { className: containerClassName, style: containerStyle,...containerAttributes } = containerProps ?? {};
28
+ const filterButton = filters && filters.length > 0 && onToggleFilter && onClearFilters ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_ColumnFilterButton.ColumnFilterButton, {
29
+ filters,
30
+ activeFilterIds: activeFilterIds ?? [],
31
+ onToggle: onToggleFilter,
32
+ onClear: onClearFilters
33
+ }) : null;
34
+ const { setNodeRef: setDroppableRef, isOver } = (0, __dnd_kit_core.useDroppable)({
35
+ id: column.id,
36
+ data: {
37
+ type: "column",
38
+ column
39
+ }
40
+ });
41
+ const { attributes, listeners, setNodeRef: setSortableRef, transform, transition, isDragging } = (0, __dnd_kit_sortable.useSortable)({
42
+ id: require_column_sortable_id.toColumnSortableId(column.id),
43
+ data: {
44
+ type: "column-sortable",
45
+ column
46
+ },
47
+ disabled: !sortable
48
+ });
49
+ const style = sortable ? {
50
+ transform: __dnd_kit_utilities.CSS.Transform.toString(transform),
51
+ transition: transition ?? void 0
52
+ } : {};
53
+ const itemIds = react.default.useMemo(() => items.map((i) => i.id), [items]);
54
+ const [scroller, setScroller] = react.default.useState(null);
55
+ const setScrollerRef = react.default.useCallback((node) => {
56
+ setDroppableRef(node);
57
+ setScroller(node);
58
+ }, [setDroppableRef]);
59
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
60
+ ref: setSortableRef,
61
+ style: {
62
+ ...containerStyle,
63
+ ...style
64
+ },
65
+ className: (0, __pixpilot_shadcn_ui.cn)("bg-muted/40 relative flex min-w-[250px] flex-1 flex-col rounded-lg border", columnOverflow === "scroll" ? "h-full min-h-0 overflow-hidden" : "min-h-full overflow-visible", isOver && "ring-primary/30 ring-2", isDragging && "opacity-50", columnClassName, containerClassName),
66
+ ...sortable ? attributes : {},
67
+ ...containerAttributes,
68
+ children: [!hideHeader && (renderColumnHeader ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
69
+ className: "flex items-center gap-1 px-3 pt-3 pb-3",
70
+ children: [
71
+ sortable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
72
+ type: "button",
73
+ className: "text-muted-foreground hover:text-foreground cursor-grab touch-none active:cursor-grabbing",
74
+ ...listeners,
75
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.GripVertical, { className: "h-4 w-4" })
76
+ }) : null,
77
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
78
+ className: "flex-1",
79
+ children: renderColumnHeader(column, items.length)
80
+ }),
81
+ filterButton
82
+ ]
83
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
84
+ className: "flex items-center justify-between px-3 pt-3 pb-3",
85
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
86
+ className: "flex items-center gap-1",
87
+ children: [sortable ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
88
+ type: "button",
89
+ className: "text-muted-foreground hover:text-foreground cursor-grab touch-none active:cursor-grabbing",
90
+ ...listeners,
91
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.GripVertical, { className: "h-4 w-4" })
92
+ }) : null, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
93
+ className: "text-sm font-semibold",
94
+ children: column.title
95
+ })]
96
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
97
+ className: "flex items-center gap-1",
98
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
99
+ className: "bg-muted text-muted-foreground rounded-full px-2 py-0.5 text-xs",
100
+ children: items.length
101
+ }), filterButton]
102
+ })]
103
+ })), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(__dnd_kit_sortable.SortableContext, {
104
+ items: itemIds,
105
+ strategy: __dnd_kit_sortable.verticalListSortingStrategy,
106
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
107
+ ref: setScrollerRef,
108
+ "data-testid": `kanban-column-scroller-${column.id}`,
109
+ className: (0, __pixpilot_shadcn_ui.cn)("relative flex flex-1 flex-col gap-2 px-3 pb-3", columnOverflow === "scroll" ? "min-h-0 overflow-y-auto pr-2" : "min-h-[60px] overflow-visible"),
110
+ children: [virtualization ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_KanbanVirtualColumnCards.KanbanVirtualColumnCards, {
111
+ column,
112
+ items,
113
+ renderItem,
114
+ itemClassName,
115
+ scroller,
116
+ activeItemId,
117
+ options: virtualization,
118
+ dragDisabled
119
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_KanbanColumnCards.KanbanColumnCards, {
120
+ column,
121
+ items,
122
+ renderItem,
123
+ itemClassName,
124
+ dragDisabled
125
+ }), infiniteScroll ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_InfiniteScrollSentinel.InfiniteScrollSentinel, {
126
+ root: scroller,
127
+ distance: infiniteScroll.distance,
128
+ threshold: infiniteScroll.threshold,
129
+ disabled: infiniteScroll.disabled,
130
+ hasMore: infiniteScroll.hasMore(column),
131
+ isLoading: infiniteScroll.isLoading?.(column) ?? false,
132
+ loadingComponent: infiniteScroll.loadingComponent,
133
+ endMessage: infiniteScroll.endMessage,
134
+ onLoadMore: () => infiniteScroll.onLoadMore(column)
135
+ }) : null]
136
+ })
137
+ })]
138
+ });
139
+ }
140
+
141
+ //#endregion
142
+ exports.KanbanColumn = KanbanColumn;
@@ -0,0 +1,134 @@
1
+ 'use client';
2
+
3
+
4
+ import { toColumnSortableId } from "./utils/column-sortable-id.js";
5
+ import { ColumnFilterButton } from "./ColumnFilterButton.js";
6
+ import { InfiniteScrollSentinel } from "./infinite-scroll/InfiniteScrollSentinel.js";
7
+ import { KanbanColumnCards } from "./KanbanColumnCards.js";
8
+ import { KanbanVirtualColumnCards } from "./KanbanVirtualColumnCards.js";
9
+ import { useDroppable } from "@dnd-kit/core";
10
+ import { SortableContext, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable";
11
+ import { cn } from "@pixpilot/shadcn-ui";
12
+ import React from "react";
13
+ import { GripVertical } from "lucide-react";
14
+ import { jsx, jsxs } from "react/jsx-runtime";
15
+ import { CSS } from "@dnd-kit/utilities";
16
+
17
+ //#region src/KanbanColumn.tsx
18
+ function KanbanColumn({ column, items, renderItem, renderColumnHeader, columnClassName, itemClassName, containerProps, hideHeader = false, sortable = false, filters, activeFilterIds, columnOverflow, infiniteScroll, virtualization, dragDisabled = false, activeItemId = null, onToggleFilter, onClearFilters }) {
19
+ const { className: containerClassName, style: containerStyle,...containerAttributes } = containerProps ?? {};
20
+ const filterButton = filters && filters.length > 0 && onToggleFilter && onClearFilters ? /* @__PURE__ */ jsx(ColumnFilterButton, {
21
+ filters,
22
+ activeFilterIds: activeFilterIds ?? [],
23
+ onToggle: onToggleFilter,
24
+ onClear: onClearFilters
25
+ }) : null;
26
+ const { setNodeRef: setDroppableRef, isOver } = useDroppable({
27
+ id: column.id,
28
+ data: {
29
+ type: "column",
30
+ column
31
+ }
32
+ });
33
+ const { attributes, listeners, setNodeRef: setSortableRef, transform, transition, isDragging } = useSortable({
34
+ id: toColumnSortableId(column.id),
35
+ data: {
36
+ type: "column-sortable",
37
+ column
38
+ },
39
+ disabled: !sortable
40
+ });
41
+ const style = sortable ? {
42
+ transform: CSS.Transform.toString(transform),
43
+ transition: transition ?? void 0
44
+ } : {};
45
+ const itemIds = React.useMemo(() => items.map((i) => i.id), [items]);
46
+ const [scroller, setScroller] = React.useState(null);
47
+ const setScrollerRef = React.useCallback((node) => {
48
+ setDroppableRef(node);
49
+ setScroller(node);
50
+ }, [setDroppableRef]);
51
+ return /* @__PURE__ */ jsxs("div", {
52
+ ref: setSortableRef,
53
+ style: {
54
+ ...containerStyle,
55
+ ...style
56
+ },
57
+ className: cn("bg-muted/40 relative flex min-w-[250px] flex-1 flex-col rounded-lg border", columnOverflow === "scroll" ? "h-full min-h-0 overflow-hidden" : "min-h-full overflow-visible", isOver && "ring-primary/30 ring-2", isDragging && "opacity-50", columnClassName, containerClassName),
58
+ ...sortable ? attributes : {},
59
+ ...containerAttributes,
60
+ children: [!hideHeader && (renderColumnHeader ? /* @__PURE__ */ jsxs("div", {
61
+ className: "flex items-center gap-1 px-3 pt-3 pb-3",
62
+ children: [
63
+ sortable ? /* @__PURE__ */ jsx("button", {
64
+ type: "button",
65
+ className: "text-muted-foreground hover:text-foreground cursor-grab touch-none active:cursor-grabbing",
66
+ ...listeners,
67
+ children: /* @__PURE__ */ jsx(GripVertical, { className: "h-4 w-4" })
68
+ }) : null,
69
+ /* @__PURE__ */ jsx("div", {
70
+ className: "flex-1",
71
+ children: renderColumnHeader(column, items.length)
72
+ }),
73
+ filterButton
74
+ ]
75
+ }) : /* @__PURE__ */ jsxs("div", {
76
+ className: "flex items-center justify-between px-3 pt-3 pb-3",
77
+ children: [/* @__PURE__ */ jsxs("div", {
78
+ className: "flex items-center gap-1",
79
+ children: [sortable ? /* @__PURE__ */ jsx("button", {
80
+ type: "button",
81
+ className: "text-muted-foreground hover:text-foreground cursor-grab touch-none active:cursor-grabbing",
82
+ ...listeners,
83
+ children: /* @__PURE__ */ jsx(GripVertical, { className: "h-4 w-4" })
84
+ }) : null, /* @__PURE__ */ jsx("h3", {
85
+ className: "text-sm font-semibold",
86
+ children: column.title
87
+ })]
88
+ }), /* @__PURE__ */ jsxs("div", {
89
+ className: "flex items-center gap-1",
90
+ children: [/* @__PURE__ */ jsx("span", {
91
+ className: "bg-muted text-muted-foreground rounded-full px-2 py-0.5 text-xs",
92
+ children: items.length
93
+ }), filterButton]
94
+ })]
95
+ })), /* @__PURE__ */ jsx(SortableContext, {
96
+ items: itemIds,
97
+ strategy: verticalListSortingStrategy,
98
+ children: /* @__PURE__ */ jsxs("div", {
99
+ ref: setScrollerRef,
100
+ "data-testid": `kanban-column-scroller-${column.id}`,
101
+ className: cn("relative flex flex-1 flex-col gap-2 px-3 pb-3", columnOverflow === "scroll" ? "min-h-0 overflow-y-auto pr-2" : "min-h-[60px] overflow-visible"),
102
+ children: [virtualization ? /* @__PURE__ */ jsx(KanbanVirtualColumnCards, {
103
+ column,
104
+ items,
105
+ renderItem,
106
+ itemClassName,
107
+ scroller,
108
+ activeItemId,
109
+ options: virtualization,
110
+ dragDisabled
111
+ }) : /* @__PURE__ */ jsx(KanbanColumnCards, {
112
+ column,
113
+ items,
114
+ renderItem,
115
+ itemClassName,
116
+ dragDisabled
117
+ }), infiniteScroll ? /* @__PURE__ */ jsx(InfiniteScrollSentinel, {
118
+ root: scroller,
119
+ distance: infiniteScroll.distance,
120
+ threshold: infiniteScroll.threshold,
121
+ disabled: infiniteScroll.disabled,
122
+ hasMore: infiniteScroll.hasMore(column),
123
+ isLoading: infiniteScroll.isLoading?.(column) ?? false,
124
+ loadingComponent: infiniteScroll.loadingComponent,
125
+ endMessage: infiniteScroll.endMessage,
126
+ onLoadMore: () => infiniteScroll.onLoadMore(column)
127
+ }) : null]
128
+ })
129
+ })]
130
+ });
131
+ }
132
+
133
+ //#endregion
134
+ export { KanbanColumn };
@@ -0,0 +1,25 @@
1
+ 'use client';
2
+
3
+
4
+ const require_rolldown_runtime = require('./_virtual/rolldown_runtime.cjs');
5
+ const require_KanbanItem = require('./KanbanItem.cjs');
6
+ let react_jsx_runtime = require("react/jsx-runtime");
7
+ react_jsx_runtime = require_rolldown_runtime.__toESM(react_jsx_runtime);
8
+
9
+ //#region src/KanbanColumnCards.tsx
10
+ /**
11
+ * The card list of a column, rendered in full. Each card is a flex child of
12
+ * the column's scroller, so spacing comes from the scroller's `gap`.
13
+ */
14
+ function KanbanColumnCards({ column, items, renderItem, itemClassName, dragDisabled = false }) {
15
+ return items.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_KanbanItem.KanbanItem, {
16
+ item,
17
+ column,
18
+ renderItem,
19
+ className: itemClassName,
20
+ dragDisabled
21
+ }, item.id));
22
+ }
23
+
24
+ //#endregion
25
+ exports.KanbanColumnCards = KanbanColumnCards;
@@ -0,0 +1,23 @@
1
+ 'use client';
2
+
3
+
4
+ import { KanbanItem } from "./KanbanItem.js";
5
+ import { jsx } from "react/jsx-runtime";
6
+
7
+ //#region src/KanbanColumnCards.tsx
8
+ /**
9
+ * The card list of a column, rendered in full. Each card is a flex child of
10
+ * the column's scroller, so spacing comes from the scroller's `gap`.
11
+ */
12
+ function KanbanColumnCards({ column, items, renderItem, itemClassName, dragDisabled = false }) {
13
+ return items.map((item) => /* @__PURE__ */ jsx(KanbanItem, {
14
+ item,
15
+ column,
16
+ renderItem,
17
+ className: itemClassName,
18
+ dragDisabled
19
+ }, item.id));
20
+ }
21
+
22
+ //#endregion
23
+ export { KanbanColumnCards };
@@ -0,0 +1,42 @@
1
+ 'use client';
2
+
3
+
4
+ const require_rolldown_runtime = require('./_virtual/rolldown_runtime.cjs');
5
+ let __dnd_kit_core = require("@dnd-kit/core");
6
+ __dnd_kit_core = require_rolldown_runtime.__toESM(__dnd_kit_core);
7
+ let __pixpilot_shadcn_ui = require("@pixpilot/shadcn-ui");
8
+ __pixpilot_shadcn_ui = require_rolldown_runtime.__toESM(__pixpilot_shadcn_ui);
9
+ let react = require("react");
10
+ react = require_rolldown_runtime.__toESM(react);
11
+ let react_jsx_runtime = require("react/jsx-runtime");
12
+ react_jsx_runtime = require_rolldown_runtime.__toESM(react_jsx_runtime);
13
+
14
+ //#region src/KanbanDragOverlay.tsx
15
+ /** Floating card/column preview that follows the cursor while dragging. */
16
+ function KanbanDragOverlay({ activeItem, activeItemColumn, activeColumn, activeColumnItemCount, renderItem, itemClassName, columnClassName }) {
17
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(__dnd_kit_core.DragOverlay, { children: [activeItem && activeItemColumn ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
18
+ className: (0, __pixpilot_shadcn_ui.cn)("bg-background cursor-grabbing rounded-md border p-3 shadow-lg", itemClassName),
19
+ children: renderItem ? renderItem(activeItem, activeItemColumn) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
20
+ className: "text-sm",
21
+ children: activeItem.name
22
+ })
23
+ }) : null, activeColumn ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
24
+ className: (0, __pixpilot_shadcn_ui.cn)("bg-muted/40 flex min-w-[250px] flex-col rounded-lg border p-3 opacity-90 shadow-lg", columnClassName),
25
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
26
+ className: "mb-3 flex items-center justify-between px-1",
27
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
28
+ className: "text-sm font-semibold",
29
+ children: activeColumn.title
30
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
31
+ className: "bg-muted text-muted-foreground rounded-full px-2 py-0.5 text-xs",
32
+ children: activeColumnItemCount
33
+ })]
34
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
35
+ className: "text-muted-foreground flex min-h-[60px] items-center justify-center text-xs",
36
+ children: "Dragging column…"
37
+ })]
38
+ }) : null] });
39
+ }
40
+
41
+ //#endregion
42
+ exports.KanbanDragOverlay = KanbanDragOverlay;
@@ -0,0 +1,37 @@
1
+ 'use client';
2
+
3
+
4
+ import { DragOverlay } from "@dnd-kit/core";
5
+ import { cn } from "@pixpilot/shadcn-ui";
6
+ import React from "react";
7
+ import { jsx, jsxs } from "react/jsx-runtime";
8
+
9
+ //#region src/KanbanDragOverlay.tsx
10
+ /** Floating card/column preview that follows the cursor while dragging. */
11
+ function KanbanDragOverlay({ activeItem, activeItemColumn, activeColumn, activeColumnItemCount, renderItem, itemClassName, columnClassName }) {
12
+ return /* @__PURE__ */ jsxs(DragOverlay, { children: [activeItem && activeItemColumn ? /* @__PURE__ */ jsx("div", {
13
+ className: cn("bg-background cursor-grabbing rounded-md border p-3 shadow-lg", itemClassName),
14
+ children: renderItem ? renderItem(activeItem, activeItemColumn) : /* @__PURE__ */ jsx("span", {
15
+ className: "text-sm",
16
+ children: activeItem.name
17
+ })
18
+ }) : null, activeColumn ? /* @__PURE__ */ jsxs("div", {
19
+ className: cn("bg-muted/40 flex min-w-[250px] flex-col rounded-lg border p-3 opacity-90 shadow-lg", columnClassName),
20
+ children: [/* @__PURE__ */ jsxs("div", {
21
+ className: "mb-3 flex items-center justify-between px-1",
22
+ children: [/* @__PURE__ */ jsx("h3", {
23
+ className: "text-sm font-semibold",
24
+ children: activeColumn.title
25
+ }), /* @__PURE__ */ jsx("span", {
26
+ className: "bg-muted text-muted-foreground rounded-full px-2 py-0.5 text-xs",
27
+ children: activeColumnItemCount
28
+ })]
29
+ }), /* @__PURE__ */ jsx("div", {
30
+ className: "text-muted-foreground flex min-h-[60px] items-center justify-center text-xs",
31
+ children: "Dragging column…"
32
+ })]
33
+ }) : null] });
34
+ }
35
+
36
+ //#endregion
37
+ export { KanbanDragOverlay };
@@ -0,0 +1,58 @@
1
+ 'use client';
2
+
3
+
4
+ const require_rolldown_runtime = require('./_virtual/rolldown_runtime.cjs');
5
+ let __dnd_kit_sortable = require("@dnd-kit/sortable");
6
+ __dnd_kit_sortable = require_rolldown_runtime.__toESM(__dnd_kit_sortable);
7
+ let __pixpilot_shadcn_ui = require("@pixpilot/shadcn-ui");
8
+ __pixpilot_shadcn_ui = require_rolldown_runtime.__toESM(__pixpilot_shadcn_ui);
9
+ let react = require("react");
10
+ react = require_rolldown_runtime.__toESM(react);
11
+ let react_jsx_runtime = require("react/jsx-runtime");
12
+ react_jsx_runtime = require_rolldown_runtime.__toESM(react_jsx_runtime);
13
+ let __dnd_kit_utilities = require("@dnd-kit/utilities");
14
+ __dnd_kit_utilities = require_rolldown_runtime.__toESM(__dnd_kit_utilities);
15
+
16
+ //#region src/KanbanItem.tsx
17
+ function KanbanItemContent({ item, column, renderItem }) {
18
+ return renderItem ? renderItem(item, column) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
19
+ className: "text-sm",
20
+ children: item.name
21
+ });
22
+ }
23
+ const MemoizedKanbanItemContent = react.default.memo(KanbanItemContent);
24
+ /**
25
+ * A single draggable item inside a Kanban column.
26
+ * Wraps `useSortable` from `@dnd-kit/sortable`.
27
+ */
28
+ function KanbanItem({ item, column, renderItem, className, dragDisabled = false }) {
29
+ const sortableData = react.default.useMemo(() => ({
30
+ type: "item",
31
+ item
32
+ }), [item]);
33
+ const { attributes, listeners, setNodeRef, transform, transition, isDragging } = (0, __dnd_kit_sortable.useSortable)({
34
+ id: item.id,
35
+ data: sortableData,
36
+ disabled: dragDisabled
37
+ });
38
+ const style = {
39
+ transform: __dnd_kit_utilities.CSS.Transform.toString(transform),
40
+ transition: transition ?? void 0
41
+ };
42
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
43
+ ref: setNodeRef,
44
+ "data-testid": `kanban-item-${item.id}`,
45
+ style,
46
+ className: (0, __pixpilot_shadcn_ui.cn)("bg-background touch-none rounded-md border p-3 shadow-sm transition-shadow", "hover:shadow-md", dragDisabled ? "cursor-default" : "cursor-grab active:cursor-grabbing", isDragging && "z-50 opacity-50 shadow-lg", className),
47
+ ...attributes,
48
+ ...listeners,
49
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedKanbanItemContent, {
50
+ item,
51
+ column,
52
+ renderItem
53
+ })
54
+ });
55
+ }
56
+
57
+ //#endregion
58
+ exports.KanbanItem = KanbanItem;
@@ -0,0 +1,52 @@
1
+ 'use client';
2
+
3
+
4
+ import { useSortable } from "@dnd-kit/sortable";
5
+ import { cn } from "@pixpilot/shadcn-ui";
6
+ import React from "react";
7
+ import { jsx } from "react/jsx-runtime";
8
+ import { CSS } from "@dnd-kit/utilities";
9
+
10
+ //#region src/KanbanItem.tsx
11
+ function KanbanItemContent({ item, column, renderItem }) {
12
+ return renderItem ? renderItem(item, column) : /* @__PURE__ */ jsx("span", {
13
+ className: "text-sm",
14
+ children: item.name
15
+ });
16
+ }
17
+ const MemoizedKanbanItemContent = React.memo(KanbanItemContent);
18
+ /**
19
+ * A single draggable item inside a Kanban column.
20
+ * Wraps `useSortable` from `@dnd-kit/sortable`.
21
+ */
22
+ function KanbanItem({ item, column, renderItem, className, dragDisabled = false }) {
23
+ const sortableData = React.useMemo(() => ({
24
+ type: "item",
25
+ item
26
+ }), [item]);
27
+ const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
28
+ id: item.id,
29
+ data: sortableData,
30
+ disabled: dragDisabled
31
+ });
32
+ const style = {
33
+ transform: CSS.Transform.toString(transform),
34
+ transition: transition ?? void 0
35
+ };
36
+ return /* @__PURE__ */ jsx("div", {
37
+ ref: setNodeRef,
38
+ "data-testid": `kanban-item-${item.id}`,
39
+ style,
40
+ className: cn("bg-background touch-none rounded-md border p-3 shadow-sm transition-shadow", "hover:shadow-md", dragDisabled ? "cursor-default" : "cursor-grab active:cursor-grabbing", isDragging && "z-50 opacity-50 shadow-lg", className),
41
+ ...attributes,
42
+ ...listeners,
43
+ children: /* @__PURE__ */ jsx(MemoizedKanbanItemContent, {
44
+ item,
45
+ column,
46
+ renderItem
47
+ })
48
+ });
49
+ }
50
+
51
+ //#endregion
52
+ export { KanbanItem };