@object-ui/plugin-kanban 3.0.3 → 3.1.1
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/.turbo/turbo-build.log +9 -9
- package/CHANGELOG.md +10 -0
- package/dist/{KanbanEnhanced-BPIKjTDv.js → KanbanEnhanced-CXDSLlGR.js} +338 -324
- package/dist/KanbanImpl-4dgoNPtI.js +350 -0
- package/dist/index-CyNcIIS1.js +1077 -0
- package/dist/index.js +9 -4
- package/dist/index.umd.cjs +4 -4
- package/dist/src/CardTemplates.d.ts +25 -0
- package/dist/src/CardTemplates.d.ts.map +1 -0
- package/dist/src/InlineQuickAdd.d.ts +29 -0
- package/dist/src/InlineQuickAdd.d.ts.map +1 -0
- package/dist/src/KanbanEnhanced.d.ts +12 -1
- package/dist/src/KanbanEnhanced.d.ts.map +1 -1
- package/dist/src/KanbanImpl.d.ts +15 -1
- package/dist/src/KanbanImpl.d.ts.map +1 -1
- package/dist/src/ObjectKanban.d.ts.map +1 -1
- package/dist/src/index.d.ts +22 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/types.d.ts +97 -1
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/useColumnWidths.d.ts +30 -0
- package/dist/src/useColumnWidths.d.ts.map +1 -0
- package/dist/src/useCrossSwimlaneMove.d.ts +46 -0
- package/dist/src/useCrossSwimlaneMove.d.ts.map +1 -0
- package/dist/src/useQuickAddReorder.d.ts +28 -0
- package/dist/src/useQuickAddReorder.d.ts.map +1 -0
- package/package.json +9 -9
- package/src/CardTemplates.tsx +123 -0
- package/src/InlineQuickAdd.tsx +189 -0
- package/src/KanbanEnhanced.tsx +140 -9
- package/src/KanbanImpl.tsx +266 -23
- package/src/ObjectKanban.tsx +39 -24
- package/src/__tests__/KanbanGrouping.test.tsx +164 -0
- package/src/__tests__/KanbanSwimlanes.test.tsx +194 -0
- package/src/__tests__/ObjectKanbanTitle.test.tsx +93 -0
- package/src/__tests__/SwimlanePersistence.test.tsx +159 -0
- package/src/__tests__/performance-benchmark.test.tsx +14 -14
- package/src/__tests__/phase13-features.test.tsx +387 -0
- package/src/index.tsx +49 -6
- package/src/types.ts +106 -1
- package/src/useColumnWidths.ts +125 -0
- package/src/useCrossSwimlaneMove.ts +116 -0
- package/src/useQuickAddReorder.ts +107 -0
- package/dist/KanbanImpl-BfOKAnJS.js +0 -194
- package/dist/index-CWGTi2xn.js +0 -600
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObjectUI
|
|
3
|
+
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { useState, useCallback, useMemo } from "react"
|
|
10
|
+
import type { KanbanColumn, ColumnWidthConfig } from "./types"
|
|
11
|
+
|
|
12
|
+
const STORAGE_KEY = "objectui:kanban-column-widths"
|
|
13
|
+
|
|
14
|
+
export interface UseColumnWidthsOptions {
|
|
15
|
+
/** Columns on the board */
|
|
16
|
+
columns: KanbanColumn[]
|
|
17
|
+
/** Default width in pixels applied to every column */
|
|
18
|
+
defaultWidth?: number
|
|
19
|
+
/** Minimum allowed column width in pixels */
|
|
20
|
+
minWidth?: number
|
|
21
|
+
/** Maximum allowed column width in pixels */
|
|
22
|
+
maxWidth?: number
|
|
23
|
+
/** Optional persistence key suffix (to scope per-board) */
|
|
24
|
+
storageKey?: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface UseColumnWidthsReturn {
|
|
28
|
+
/** Get the current width for a column (in px) */
|
|
29
|
+
getColumnWidth: (columnId: string) => number
|
|
30
|
+
/** Set an override width for a column */
|
|
31
|
+
setColumnWidth: (columnId: string, width: number) => void
|
|
32
|
+
/** Reset all overrides back to default widths */
|
|
33
|
+
resetWidths: () => void
|
|
34
|
+
/** The full config (useful for serialization) */
|
|
35
|
+
config: ColumnWidthConfig
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function loadPersistedWidths(key: string): Record<string, number> {
|
|
39
|
+
try {
|
|
40
|
+
const raw = localStorage.getItem(key)
|
|
41
|
+
if (raw) {
|
|
42
|
+
const parsed = JSON.parse(raw)
|
|
43
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
44
|
+
return parsed as Record<string, number>
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
} catch {
|
|
48
|
+
/* ignore corrupt data */
|
|
49
|
+
}
|
|
50
|
+
return {}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function persistWidths(key: string, overrides: Record<string, number>) {
|
|
54
|
+
try {
|
|
55
|
+
localStorage.setItem(key, JSON.stringify(overrides))
|
|
56
|
+
} catch {
|
|
57
|
+
/* quota exceeded */
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Hook for managing custom per-column widths with localStorage persistence.
|
|
63
|
+
* Supports per-column overrides clamped between minWidth and maxWidth.
|
|
64
|
+
*/
|
|
65
|
+
export function useColumnWidths({
|
|
66
|
+
columns: _columns,
|
|
67
|
+
defaultWidth = 320,
|
|
68
|
+
minWidth = 200,
|
|
69
|
+
maxWidth = 600,
|
|
70
|
+
storageKey,
|
|
71
|
+
}: UseColumnWidthsOptions): UseColumnWidthsReturn {
|
|
72
|
+
const fullKey = storageKey ? `${STORAGE_KEY}:${storageKey}` : STORAGE_KEY
|
|
73
|
+
|
|
74
|
+
const [overrides, setOverrides] = useState<Record<string, number>>(() =>
|
|
75
|
+
loadPersistedWidths(fullKey),
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
const clamp = useCallback(
|
|
79
|
+
(value: number) => Math.max(minWidth, Math.min(maxWidth, value)),
|
|
80
|
+
[minWidth, maxWidth],
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
const getColumnWidth = useCallback(
|
|
84
|
+
(columnId: string): number => {
|
|
85
|
+
const override = overrides[columnId]
|
|
86
|
+
return override != null ? clamp(override) : defaultWidth
|
|
87
|
+
},
|
|
88
|
+
[overrides, defaultWidth, clamp],
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
const setColumnWidth = useCallback(
|
|
92
|
+
(columnId: string, width: number) => {
|
|
93
|
+
const clamped = clamp(width)
|
|
94
|
+
setOverrides(prev => {
|
|
95
|
+
const next = { ...prev, [columnId]: clamped }
|
|
96
|
+
persistWidths(fullKey, next)
|
|
97
|
+
return next
|
|
98
|
+
})
|
|
99
|
+
},
|
|
100
|
+
[clamp, fullKey],
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
const resetWidths = useCallback(() => {
|
|
104
|
+
setOverrides({})
|
|
105
|
+
try {
|
|
106
|
+
localStorage.removeItem(fullKey)
|
|
107
|
+
} catch {
|
|
108
|
+
/* ignore */
|
|
109
|
+
}
|
|
110
|
+
}, [fullKey])
|
|
111
|
+
|
|
112
|
+
const config: ColumnWidthConfig = useMemo(
|
|
113
|
+
() => ({
|
|
114
|
+
defaultWidth,
|
|
115
|
+
minWidth,
|
|
116
|
+
maxWidth,
|
|
117
|
+
overrides: { ...overrides },
|
|
118
|
+
}),
|
|
119
|
+
[defaultWidth, minWidth, maxWidth, overrides],
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
return { getColumnWidth, setColumnWidth, resetWidths, config }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export default useColumnWidths
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObjectUI
|
|
3
|
+
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { useState, useCallback, useMemo } from "react"
|
|
10
|
+
|
|
11
|
+
export interface Swimlane {
|
|
12
|
+
/** Unique identifier for the swimlane row */
|
|
13
|
+
id: string
|
|
14
|
+
/** Display label */
|
|
15
|
+
title: string
|
|
16
|
+
/** Optional list of swimlane IDs this lane accepts cards from. Omit for unrestricted. */
|
|
17
|
+
acceptFrom?: string[]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface CrossSwimlaneMoveEvent {
|
|
21
|
+
cardId: string
|
|
22
|
+
fromSwimlane: string
|
|
23
|
+
toSwimlane: string
|
|
24
|
+
columnId: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface UseCrossSwimlaneOptions {
|
|
28
|
+
/** Swimlane definitions */
|
|
29
|
+
swimlanes: Swimlane[]
|
|
30
|
+
/** Callback executed after a successful cross-swimlane move */
|
|
31
|
+
onCardMove?: (event: CrossSwimlaneMoveEvent) => void
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface UseCrossSwimlaneMoveReturn {
|
|
35
|
+
/** Execute a cross-swimlane move. Returns true if the move was allowed. */
|
|
36
|
+
handleCrossSwimlaneMove: (
|
|
37
|
+
cardId: string,
|
|
38
|
+
fromSwimlane: string,
|
|
39
|
+
toSwimlane: string,
|
|
40
|
+
columnId: string,
|
|
41
|
+
) => boolean
|
|
42
|
+
/** Whether a card is currently being dragged across swimlanes */
|
|
43
|
+
isDraggingAcrossSwimlanes: boolean
|
|
44
|
+
/** Start tracking a cross-swimlane drag */
|
|
45
|
+
startCrossSwimlaneDrag: (fromSwimlane: string) => void
|
|
46
|
+
/** End the cross-swimlane drag */
|
|
47
|
+
endCrossSwimlaneDrag: () => void
|
|
48
|
+
/** Check whether a card may be moved into the target swimlane */
|
|
49
|
+
canMoveTo: (fromSwimlane: string, toSwimlane: string) => boolean
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Hook for managing cross-swimlane card movements.
|
|
54
|
+
* Validates movement constraints (acceptFrom rules) and tracks drag state.
|
|
55
|
+
*/
|
|
56
|
+
export function useCrossSwimlaneMove({
|
|
57
|
+
swimlanes,
|
|
58
|
+
onCardMove,
|
|
59
|
+
}: UseCrossSwimlaneOptions): UseCrossSwimlaneMoveReturn {
|
|
60
|
+
const [dragSource, setDragSource] = useState<string | null>(null)
|
|
61
|
+
|
|
62
|
+
const swimlaneMap = useMemo(() => {
|
|
63
|
+
const map = new Map<string, Swimlane>()
|
|
64
|
+
for (const lane of swimlanes) {
|
|
65
|
+
map.set(lane.id, lane)
|
|
66
|
+
}
|
|
67
|
+
return map
|
|
68
|
+
}, [swimlanes])
|
|
69
|
+
|
|
70
|
+
const canMoveTo = useCallback(
|
|
71
|
+
(fromSwimlane: string, toSwimlane: string): boolean => {
|
|
72
|
+
if (fromSwimlane === toSwimlane) return true
|
|
73
|
+
const target = swimlaneMap.get(toSwimlane)
|
|
74
|
+
if (!target) return false
|
|
75
|
+
// When acceptFrom is not specified the lane is unrestricted
|
|
76
|
+
if (!target.acceptFrom) return true
|
|
77
|
+
return target.acceptFrom.includes(fromSwimlane)
|
|
78
|
+
},
|
|
79
|
+
[swimlaneMap],
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
const handleCrossSwimlaneMove = useCallback(
|
|
83
|
+
(
|
|
84
|
+
cardId: string,
|
|
85
|
+
fromSwimlane: string,
|
|
86
|
+
toSwimlane: string,
|
|
87
|
+
columnId: string,
|
|
88
|
+
): boolean => {
|
|
89
|
+
if (!canMoveTo(fromSwimlane, toSwimlane)) return false
|
|
90
|
+
onCardMove?.({ cardId, fromSwimlane, toSwimlane, columnId })
|
|
91
|
+
setDragSource(null)
|
|
92
|
+
return true
|
|
93
|
+
},
|
|
94
|
+
[canMoveTo, onCardMove],
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
const startCrossSwimlaneDrag = useCallback((fromSwimlane: string) => {
|
|
98
|
+
setDragSource(fromSwimlane)
|
|
99
|
+
}, [])
|
|
100
|
+
|
|
101
|
+
const endCrossSwimlaneDrag = useCallback(() => {
|
|
102
|
+
setDragSource(null)
|
|
103
|
+
}, [])
|
|
104
|
+
|
|
105
|
+
const isDraggingAcrossSwimlanes = dragSource !== null
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
handleCrossSwimlaneMove,
|
|
109
|
+
isDraggingAcrossSwimlanes,
|
|
110
|
+
startCrossSwimlaneDrag,
|
|
111
|
+
endCrossSwimlaneDrag,
|
|
112
|
+
canMoveTo,
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export default useCrossSwimlaneMove
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObjectUI
|
|
3
|
+
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { useState, useCallback } from "react"
|
|
10
|
+
import type { KanbanCard } from "./types"
|
|
11
|
+
|
|
12
|
+
export interface UseQuickAddReorderOptions {
|
|
13
|
+
/** Initial set of cards in the column */
|
|
14
|
+
cards: KanbanCard[]
|
|
15
|
+
/** Called when the user finishes reordering */
|
|
16
|
+
onReorderComplete?: (reorderedCards: KanbanCard[]) => void
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface UseQuickAddReorderReturn {
|
|
20
|
+
/** Cards in their current (possibly reordered) order */
|
|
21
|
+
reorderedCards: KanbanCard[]
|
|
22
|
+
/** Move a card from one index to another within the list */
|
|
23
|
+
onReorder: (fromIndex: number, toIndex: number) => void
|
|
24
|
+
/** Whether a card is currently being dragged for reorder */
|
|
25
|
+
isDragging: boolean
|
|
26
|
+
/** Signal the start of a drag-to-reorder interaction */
|
|
27
|
+
startDrag: () => void
|
|
28
|
+
/** Signal the end of a drag-to-reorder interaction */
|
|
29
|
+
endDrag: () => void
|
|
30
|
+
/** Reset reordered state back to the source cards */
|
|
31
|
+
reset: () => void
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Hook for drag-to-reorder of quick-add cards within a single column.
|
|
36
|
+
* Manages local ordering state and exposes drag lifecycle helpers.
|
|
37
|
+
*/
|
|
38
|
+
export function useQuickAddReorder({
|
|
39
|
+
cards,
|
|
40
|
+
onReorderComplete,
|
|
41
|
+
}: UseQuickAddReorderOptions): UseQuickAddReorderReturn {
|
|
42
|
+
const [reorderedCards, setReorderedCards] = useState<KanbanCard[]>(cards)
|
|
43
|
+
const [isDragging, setIsDragging] = useState(false)
|
|
44
|
+
|
|
45
|
+
// Keep reorderedCards in sync when the source cards change externally
|
|
46
|
+
// (e.g. when a new card is added via quick-add)
|
|
47
|
+
if (!cardsMatch(reorderedCards, cards) && !isDragging) {
|
|
48
|
+
setReorderedCards(cards)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const reset = useCallback(() => {
|
|
52
|
+
setReorderedCards(cards)
|
|
53
|
+
setIsDragging(false)
|
|
54
|
+
}, [cards])
|
|
55
|
+
|
|
56
|
+
const onReorder = useCallback(
|
|
57
|
+
(fromIndex: number, toIndex: number) => {
|
|
58
|
+
setReorderedCards(prev => {
|
|
59
|
+
if (
|
|
60
|
+
fromIndex < 0 ||
|
|
61
|
+
toIndex < 0 ||
|
|
62
|
+
fromIndex >= prev.length ||
|
|
63
|
+
toIndex >= prev.length
|
|
64
|
+
) {
|
|
65
|
+
return prev
|
|
66
|
+
}
|
|
67
|
+
const next = [...prev]
|
|
68
|
+
const [moved] = next.splice(fromIndex, 1)
|
|
69
|
+
next.splice(toIndex, 0, moved)
|
|
70
|
+
return next
|
|
71
|
+
})
|
|
72
|
+
},
|
|
73
|
+
[],
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
const startDrag = useCallback(() => {
|
|
77
|
+
setIsDragging(true)
|
|
78
|
+
}, [])
|
|
79
|
+
|
|
80
|
+
const endDrag = useCallback(() => {
|
|
81
|
+
setIsDragging(false)
|
|
82
|
+
setReorderedCards(current => {
|
|
83
|
+
onReorderComplete?.(current)
|
|
84
|
+
return current
|
|
85
|
+
})
|
|
86
|
+
}, [onReorderComplete])
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
reorderedCards,
|
|
90
|
+
onReorder,
|
|
91
|
+
isDragging,
|
|
92
|
+
startDrag,
|
|
93
|
+
endDrag,
|
|
94
|
+
reset,
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Shallow check that two card arrays contain the same IDs in the same order. */
|
|
99
|
+
function cardsMatch(a: KanbanCard[], b: KanbanCard[]): boolean {
|
|
100
|
+
if (a.length !== b.length) return false
|
|
101
|
+
for (let i = 0; i < a.length; i++) {
|
|
102
|
+
if (a[i].id !== b[i].id) return false
|
|
103
|
+
}
|
|
104
|
+
return true
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export default useQuickAddReorder
|
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
import { j as e } from "./index-CWGTi2xn.js";
|
|
2
|
-
import * as h from "react";
|
|
3
|
-
import { u as H, a as B, D as L, c as q, b as z, d as G, T as J, P as M, e as E, S as Q, v as U, C as V } from "./sortable.esm-CNNHgHk5.js";
|
|
4
|
-
import { Badge as K, ScrollArea as W, Card as X, CardHeader as Y, CardTitle as Z, CardDescription as _, CardContent as ee } from "@object-ui/components";
|
|
5
|
-
import { useHasDndProvider as se, useDnd as re } from "@object-ui/react";
|
|
6
|
-
const R = (...s) => s.filter(Boolean).join(" ");
|
|
7
|
-
function T({ card: s, onCardClick: i }) {
|
|
8
|
-
const {
|
|
9
|
-
attributes: x,
|
|
10
|
-
listeners: n,
|
|
11
|
-
setNodeRef: t,
|
|
12
|
-
transform: d,
|
|
13
|
-
transition: c,
|
|
14
|
-
isDragging: C
|
|
15
|
-
} = E({ id: s.id }), o = {
|
|
16
|
-
transform: V.Transform.toString(d),
|
|
17
|
-
transition: c,
|
|
18
|
-
opacity: C ? 0.5 : void 0
|
|
19
|
-
};
|
|
20
|
-
return /* @__PURE__ */ e.jsx(
|
|
21
|
-
"div",
|
|
22
|
-
{
|
|
23
|
-
ref: t,
|
|
24
|
-
style: o,
|
|
25
|
-
...x,
|
|
26
|
-
...n,
|
|
27
|
-
role: "listitem",
|
|
28
|
-
"aria-label": s.title,
|
|
29
|
-
onClick: () => i?.(s),
|
|
30
|
-
children: /* @__PURE__ */ e.jsxs(X, { className: "mb-2 cursor-grab active:cursor-grabbing border-border bg-card/60 hover:border-primary/40 hover:shadow-lg hover:shadow-primary/10 transition-all duration-300 group touch-manipulation", children: [
|
|
31
|
-
/* @__PURE__ */ e.jsxs(Y, { className: "p-2 sm:p-4", children: [
|
|
32
|
-
/* @__PURE__ */ e.jsx(Z, { className: "text-xs sm:text-sm font-medium font-mono tracking-tight text-foreground group-hover:text-primary transition-colors", children: s.title }),
|
|
33
|
-
s.description && /* @__PURE__ */ e.jsx(_, { className: "text-xs text-muted-foreground font-mono line-clamp-2 sm:line-clamp-none", children: s.description })
|
|
34
|
-
] }),
|
|
35
|
-
s.badges && s.badges.length > 0 && /* @__PURE__ */ e.jsx(ee, { className: "p-2 sm:p-4 pt-0", children: /* @__PURE__ */ e.jsx("div", { className: "flex flex-wrap gap-1", children: s.badges.map((b, S) => /* @__PURE__ */ e.jsx(K, { variant: b.variant || "default", className: "text-xs", children: b.label }, S)) }) })
|
|
36
|
-
] })
|
|
37
|
-
}
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
function te({
|
|
41
|
-
column: s,
|
|
42
|
-
cards: i,
|
|
43
|
-
onCardClick: x
|
|
44
|
-
}) {
|
|
45
|
-
const n = i || [], { setNodeRef: t } = E({
|
|
46
|
-
id: s.id,
|
|
47
|
-
data: {
|
|
48
|
-
type: "column"
|
|
49
|
-
}
|
|
50
|
-
}), d = s.limit && n.length >= s.limit;
|
|
51
|
-
return /* @__PURE__ */ e.jsxs(
|
|
52
|
-
"div",
|
|
53
|
-
{
|
|
54
|
-
ref: t,
|
|
55
|
-
role: "group",
|
|
56
|
-
"aria-label": s.title,
|
|
57
|
-
className: R(
|
|
58
|
-
"flex flex-col w-[85vw] sm:w-80 flex-shrink-0 rounded-lg border border-border bg-card/20 backdrop-blur-sm shadow-xl snap-start",
|
|
59
|
-
s.className
|
|
60
|
-
),
|
|
61
|
-
children: [
|
|
62
|
-
/* @__PURE__ */ e.jsx("div", { className: "p-3 sm:p-4 border-b border-border/50 bg-muted/20", children: /* @__PURE__ */ e.jsxs("div", { className: "flex items-center justify-between", children: [
|
|
63
|
-
/* @__PURE__ */ e.jsx("h3", { id: `kanban-col-${s.id}`, className: "font-mono text-xs sm:text-sm font-semibold tracking-wider text-primary/90 uppercase truncate", children: s.title }),
|
|
64
|
-
/* @__PURE__ */ e.jsxs("div", { className: "flex items-center gap-2", children: [
|
|
65
|
-
/* @__PURE__ */ e.jsxs("span", { className: "font-mono text-xs text-muted-foreground", "aria-label": `${n.length} cards${s.limit ? ` of ${s.limit} maximum` : ""}`, children: [
|
|
66
|
-
n.length,
|
|
67
|
-
s.limit && ` / ${s.limit}`
|
|
68
|
-
] }),
|
|
69
|
-
d && /* @__PURE__ */ e.jsx(K, { variant: "destructive", className: "text-xs", children: "Full" })
|
|
70
|
-
] })
|
|
71
|
-
] }) }),
|
|
72
|
-
/* @__PURE__ */ e.jsx(W, { className: "flex-1 p-4", children: /* @__PURE__ */ e.jsx(
|
|
73
|
-
Q,
|
|
74
|
-
{
|
|
75
|
-
items: n.map((c) => c.id),
|
|
76
|
-
strategy: U,
|
|
77
|
-
children: /* @__PURE__ */ e.jsx("div", { className: "space-y-2", role: "list", "aria-label": `${s.title} cards`, children: n.map((c) => /* @__PURE__ */ e.jsx(T, { card: c, onCardClick: x }, c.id)) })
|
|
78
|
-
}
|
|
79
|
-
) })
|
|
80
|
-
]
|
|
81
|
-
}
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
function ae({ children: s }) {
|
|
85
|
-
const i = re();
|
|
86
|
-
return /* @__PURE__ */ e.jsx(e.Fragment, { children: s(i) });
|
|
87
|
-
}
|
|
88
|
-
function le({ columns: s, onCardMove: i, onCardClick: x, className: n }) {
|
|
89
|
-
return se() ? /* @__PURE__ */ e.jsx(ae, { children: (d) => /* @__PURE__ */ e.jsx($, { columns: s, onCardMove: i, onCardClick: x, className: n, dnd: d }) }) : /* @__PURE__ */ e.jsx($, { columns: s, onCardMove: i, onCardClick: x, className: n, dnd: null });
|
|
90
|
-
}
|
|
91
|
-
function $({ columns: s, onCardMove: i, onCardClick: x, className: n, dnd: t }) {
|
|
92
|
-
const [d, c] = h.useState(null), C = h.useMemo(() => (s || []).map((r) => ({
|
|
93
|
-
...r,
|
|
94
|
-
cards: r.cards || []
|
|
95
|
-
})), [s]), [o, b] = h.useState(C);
|
|
96
|
-
h.useEffect(() => {
|
|
97
|
-
b(C);
|
|
98
|
-
}, [C]);
|
|
99
|
-
const S = H(
|
|
100
|
-
B(M, {
|
|
101
|
-
activationConstraint: {
|
|
102
|
-
distance: 5
|
|
103
|
-
}
|
|
104
|
-
}),
|
|
105
|
-
B(J, {
|
|
106
|
-
activationConstraint: {
|
|
107
|
-
delay: 200,
|
|
108
|
-
tolerance: 5
|
|
109
|
-
}
|
|
110
|
-
})
|
|
111
|
-
), O = (r) => {
|
|
112
|
-
const { active: m } = r, a = A(m.id);
|
|
113
|
-
if (c(a), t && a) {
|
|
114
|
-
const l = w(a.id);
|
|
115
|
-
l && t.startDrag({ id: a.id, type: "kanban-card", data: a, sourceId: l.id });
|
|
116
|
-
}
|
|
117
|
-
}, P = (r) => {
|
|
118
|
-
const { active: m, over: a } = r;
|
|
119
|
-
if (c(null), !a) {
|
|
120
|
-
t && t.endDrag();
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
const l = m.id, v = a.id;
|
|
124
|
-
if (l === v) {
|
|
125
|
-
t && t.endDrag();
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
const u = w(l), p = w(v) || F(v);
|
|
129
|
-
if (!u || !p) {
|
|
130
|
-
t && t.endDrag();
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
if (u.id === p.id) {
|
|
134
|
-
const g = [...u.cards], j = g.findIndex((f) => f.id === l), I = g.findIndex((f) => f.id === v), k = G(g, j, I);
|
|
135
|
-
b(
|
|
136
|
-
(f) => f.map(
|
|
137
|
-
(D) => D.id === u.id ? { ...D, cards: k } : D
|
|
138
|
-
)
|
|
139
|
-
);
|
|
140
|
-
} else {
|
|
141
|
-
const g = [...u.cards], j = [...p.cards], I = g.findIndex((N) => N.id === l), f = v === p.id ? j.length : j.findIndex((N) => N.id === v), [D] = g.splice(I, 1);
|
|
142
|
-
j.splice(f, 0, D), b(
|
|
143
|
-
(N) => N.map((y) => y.id === u.id ? { ...y, cards: g } : y.id === p.id ? { ...y, cards: j } : y)
|
|
144
|
-
), i && i(l, u.id, p.id, f);
|
|
145
|
-
}
|
|
146
|
-
t && t.endDrag(p.id);
|
|
147
|
-
}, A = h.useCallback(
|
|
148
|
-
(r) => {
|
|
149
|
-
for (const m of o) {
|
|
150
|
-
const a = m.cards.find((l) => l.id === r);
|
|
151
|
-
if (a) return a;
|
|
152
|
-
}
|
|
153
|
-
return null;
|
|
154
|
-
},
|
|
155
|
-
[o]
|
|
156
|
-
), w = h.useCallback(
|
|
157
|
-
(r) => o.find((m) => m.cards.some((a) => a.id === r)) || null,
|
|
158
|
-
[o]
|
|
159
|
-
), F = h.useCallback(
|
|
160
|
-
(r) => o.find((m) => m.id === r) || null,
|
|
161
|
-
[o]
|
|
162
|
-
);
|
|
163
|
-
return /* @__PURE__ */ e.jsxs(
|
|
164
|
-
L,
|
|
165
|
-
{
|
|
166
|
-
sensors: S,
|
|
167
|
-
collisionDetection: q,
|
|
168
|
-
onDragStart: O,
|
|
169
|
-
onDragEnd: P,
|
|
170
|
-
children: [
|
|
171
|
-
/* @__PURE__ */ e.jsxs("div", { className: "flex sm:hidden items-center justify-between px-3 pb-2 text-xs text-muted-foreground", children: [
|
|
172
|
-
/* @__PURE__ */ e.jsxs("span", { children: [
|
|
173
|
-
o.length,
|
|
174
|
-
" columns"
|
|
175
|
-
] }),
|
|
176
|
-
/* @__PURE__ */ e.jsx("span", { children: "← Swipe to navigate →" })
|
|
177
|
-
] }),
|
|
178
|
-
/* @__PURE__ */ e.jsx("div", { className: R("flex gap-3 sm:gap-4 overflow-x-auto snap-x snap-mandatory p-2 sm:p-4 [-webkit-overflow-scrolling:touch]", n), role: "region", "aria-label": "Kanban board", children: o.map((r) => /* @__PURE__ */ e.jsx(
|
|
179
|
-
te,
|
|
180
|
-
{
|
|
181
|
-
column: r,
|
|
182
|
-
cards: r.cards,
|
|
183
|
-
onCardClick: x
|
|
184
|
-
},
|
|
185
|
-
r.id
|
|
186
|
-
)) }),
|
|
187
|
-
/* @__PURE__ */ e.jsx(z, { children: /* @__PURE__ */ e.jsx("div", { "aria-live": "assertive", "aria-label": d ? `Dragging ${d.title}` : void 0, children: d ? /* @__PURE__ */ e.jsx(T, { card: d }) : null }) })
|
|
188
|
-
]
|
|
189
|
-
}
|
|
190
|
-
);
|
|
191
|
-
}
|
|
192
|
-
export {
|
|
193
|
-
le as default
|
|
194
|
-
};
|