@particle-academy/react-fancy 2.7.1 → 2.8.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/dist/index.cjs +285 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +87 -13
- package/dist/index.d.ts +87 -13
- package/dist/index.js +285 -33
- package/dist/index.js.map +1 -1
- package/docs/Kanban.md +98 -27
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -11423,35 +11423,175 @@ var KanbanColumnContext = react.createContext("");
|
|
|
11423
11423
|
function useKanbanColumn() {
|
|
11424
11424
|
return react.useContext(KanbanColumnContext);
|
|
11425
11425
|
}
|
|
11426
|
+
var DEFAULT_CARD_CLASSES = "rounded-lg border border-zinc-200 bg-white p-3 shadow-sm transition-shadow hover:shadow-md dark:border-zinc-700 dark:bg-zinc-900";
|
|
11427
|
+
function KanbanCard({ children, id, className, unstyled }) {
|
|
11428
|
+
const { setDraggedCard, setDragSource } = useKanban();
|
|
11429
|
+
const columnId = useKanbanColumn();
|
|
11430
|
+
const handleDragStart = react.useCallback(() => {
|
|
11431
|
+
setDraggedCard(id);
|
|
11432
|
+
setDragSource(columnId);
|
|
11433
|
+
}, [id, columnId, setDraggedCard, setDragSource]);
|
|
11434
|
+
const handleDragEnd = react.useCallback(() => {
|
|
11435
|
+
setDraggedCard(null);
|
|
11436
|
+
setDragSource(null);
|
|
11437
|
+
}, [setDraggedCard, setDragSource]);
|
|
11438
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
11439
|
+
"div",
|
|
11440
|
+
{
|
|
11441
|
+
"data-react-fancy-kanban-card": "",
|
|
11442
|
+
draggable: true,
|
|
11443
|
+
onDragStart: handleDragStart,
|
|
11444
|
+
onDragEnd: handleDragEnd,
|
|
11445
|
+
className: cn(
|
|
11446
|
+
// Drag affordance — kept even when unstyled so users still see grab cursors.
|
|
11447
|
+
"cursor-grab active:cursor-grabbing",
|
|
11448
|
+
!unstyled && DEFAULT_CARD_CLASSES,
|
|
11449
|
+
className
|
|
11450
|
+
),
|
|
11451
|
+
children
|
|
11452
|
+
}
|
|
11453
|
+
);
|
|
11454
|
+
}
|
|
11455
|
+
KanbanCard.displayName = "KanbanCard";
|
|
11426
11456
|
var DEFAULT_COLUMN_CLASSES = "min-h-[200px] w-72 rounded-xl bg-zinc-50 p-3 dark:bg-zinc-800/50";
|
|
11457
|
+
function countCardChildren(children) {
|
|
11458
|
+
let n = 0;
|
|
11459
|
+
react.Children.forEach(children, (child) => {
|
|
11460
|
+
if (!react.isValidElement(child)) return;
|
|
11461
|
+
if (child.type === KanbanCard) {
|
|
11462
|
+
n += 1;
|
|
11463
|
+
return;
|
|
11464
|
+
}
|
|
11465
|
+
const inner = child.props.children;
|
|
11466
|
+
if (inner !== void 0) n += countCardChildren(inner);
|
|
11467
|
+
});
|
|
11468
|
+
return n;
|
|
11469
|
+
}
|
|
11470
|
+
function findCardIndex(children, cardId) {
|
|
11471
|
+
let idx = -1;
|
|
11472
|
+
let i = 0;
|
|
11473
|
+
function walk2(nodes) {
|
|
11474
|
+
react.Children.forEach(nodes, (child) => {
|
|
11475
|
+
if (idx !== -1) return;
|
|
11476
|
+
if (!react.isValidElement(child)) return;
|
|
11477
|
+
if (child.type === KanbanCard) {
|
|
11478
|
+
if (child.props.id === cardId) idx = i;
|
|
11479
|
+
i += 1;
|
|
11480
|
+
return;
|
|
11481
|
+
}
|
|
11482
|
+
const inner = child.props.children;
|
|
11483
|
+
if (inner !== void 0) walk2(inner);
|
|
11484
|
+
});
|
|
11485
|
+
}
|
|
11486
|
+
walk2(children);
|
|
11487
|
+
return idx;
|
|
11488
|
+
}
|
|
11427
11489
|
function KanbanColumn({
|
|
11428
11490
|
children,
|
|
11429
11491
|
id,
|
|
11430
11492
|
title,
|
|
11431
11493
|
className,
|
|
11432
|
-
unstyled
|
|
11494
|
+
unstyled,
|
|
11495
|
+
wipLimit,
|
|
11496
|
+
hideWhenEmpty
|
|
11433
11497
|
}) {
|
|
11434
|
-
const { onCardMove, draggedCard, dragSource } = useKanban();
|
|
11498
|
+
const { onCardMove, draggedCard, dragSource, registerColumn } = useKanban();
|
|
11435
11499
|
const [dragOver, setDragOver] = react.useState(false);
|
|
11500
|
+
const [dropIndex, setDropIndex] = react.useState(null);
|
|
11501
|
+
const [dropY, setDropY] = react.useState(null);
|
|
11502
|
+
const cardsRef = react.useRef(null);
|
|
11503
|
+
react.useEffect(() => registerColumn(id), [id, registerColumn]);
|
|
11504
|
+
const updateDrop = react.useCallback((clientY) => {
|
|
11505
|
+
const container = cardsRef.current;
|
|
11506
|
+
if (!container) {
|
|
11507
|
+
setDropIndex(null);
|
|
11508
|
+
setDropY(null);
|
|
11509
|
+
return;
|
|
11510
|
+
}
|
|
11511
|
+
const all = Array.from(
|
|
11512
|
+
container.querySelectorAll(
|
|
11513
|
+
"[data-react-fancy-kanban-card]"
|
|
11514
|
+
)
|
|
11515
|
+
);
|
|
11516
|
+
const cards = all.filter(
|
|
11517
|
+
(el) => el.closest("[data-react-fancy-kanban-column]") === cardsRef.current?.closest("[data-react-fancy-kanban-column]")
|
|
11518
|
+
);
|
|
11519
|
+
const containerRect = container.getBoundingClientRect();
|
|
11520
|
+
if (cards.length === 0) {
|
|
11521
|
+
setDropIndex(0);
|
|
11522
|
+
setDropY(0);
|
|
11523
|
+
return;
|
|
11524
|
+
}
|
|
11525
|
+
let idx = cards.length;
|
|
11526
|
+
let yRel = 0;
|
|
11527
|
+
for (let i = 0; i < cards.length; i++) {
|
|
11528
|
+
const rect = cards[i].getBoundingClientRect();
|
|
11529
|
+
if (clientY < rect.top + rect.height / 2) {
|
|
11530
|
+
idx = i;
|
|
11531
|
+
yRel = rect.top - containerRect.top;
|
|
11532
|
+
break;
|
|
11533
|
+
}
|
|
11534
|
+
}
|
|
11535
|
+
if (idx === cards.length) {
|
|
11536
|
+
const last = cards[cards.length - 1].getBoundingClientRect();
|
|
11537
|
+
yRel = last.bottom - containerRect.top;
|
|
11538
|
+
}
|
|
11539
|
+
setDropIndex(idx);
|
|
11540
|
+
setDropY(yRel);
|
|
11541
|
+
}, []);
|
|
11542
|
+
const handleDragOver = react.useCallback(
|
|
11543
|
+
(e) => {
|
|
11544
|
+
if (!draggedCard) return;
|
|
11545
|
+
e.preventDefault();
|
|
11546
|
+
e.stopPropagation();
|
|
11547
|
+
setDragOver(true);
|
|
11548
|
+
updateDrop(e.clientY);
|
|
11549
|
+
},
|
|
11550
|
+
[draggedCard, updateDrop]
|
|
11551
|
+
);
|
|
11552
|
+
const handleDragLeave = react.useCallback((e) => {
|
|
11553
|
+
if (e.currentTarget.contains(e.relatedTarget)) return;
|
|
11554
|
+
setDragOver(false);
|
|
11555
|
+
setDropIndex(null);
|
|
11556
|
+
setDropY(null);
|
|
11557
|
+
}, []);
|
|
11436
11558
|
const handleDrop = react.useCallback(
|
|
11437
11559
|
(e) => {
|
|
11560
|
+
if (!draggedCard) return;
|
|
11438
11561
|
e.preventDefault();
|
|
11439
|
-
|
|
11440
|
-
|
|
11441
|
-
|
|
11562
|
+
e.stopPropagation();
|
|
11563
|
+
const target = dropIndex ?? 0;
|
|
11564
|
+
if (dragSource) {
|
|
11565
|
+
let finalIdx = target;
|
|
11566
|
+
if (dragSource === id) {
|
|
11567
|
+
const srcIdx = findCardIndex(children, draggedCard);
|
|
11568
|
+
if (srcIdx !== -1 && target > srcIdx) finalIdx = target - 1;
|
|
11569
|
+
if (srcIdx === finalIdx) {
|
|
11570
|
+
setDragOver(false);
|
|
11571
|
+
setDropIndex(null);
|
|
11572
|
+
setDropY(null);
|
|
11573
|
+
return;
|
|
11574
|
+
}
|
|
11575
|
+
}
|
|
11576
|
+
onCardMove?.(draggedCard, dragSource, id, finalIdx);
|
|
11442
11577
|
}
|
|
11578
|
+
setDragOver(false);
|
|
11579
|
+
setDropIndex(null);
|
|
11580
|
+
setDropY(null);
|
|
11443
11581
|
},
|
|
11444
|
-
[draggedCard, dragSource, id, onCardMove]
|
|
11582
|
+
[draggedCard, dragSource, dropIndex, id, onCardMove, children]
|
|
11445
11583
|
);
|
|
11446
|
-
const
|
|
11447
|
-
|
|
11448
|
-
|
|
11449
|
-
|
|
11450
|
-
const handleDragLeave = react.useCallback(() => setDragOver(false), []);
|
|
11584
|
+
const cardCount = countCardChildren(children);
|
|
11585
|
+
if (hideWhenEmpty && cardCount === 0 && !draggedCard) return null;
|
|
11586
|
+
const showIndicator = draggedCard !== null && dropIndex !== null && dropY !== null && dragOver;
|
|
11587
|
+
const overWip = wipLimit !== void 0 && cardCount > wipLimit;
|
|
11451
11588
|
return /* @__PURE__ */ jsxRuntime.jsx(KanbanColumnContext.Provider, { value: id, children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
11452
11589
|
"div",
|
|
11453
11590
|
{
|
|
11454
11591
|
"data-react-fancy-kanban-column": "",
|
|
11592
|
+
"data-column-id": id,
|
|
11593
|
+
role: "group",
|
|
11594
|
+
"aria-label": title,
|
|
11455
11595
|
onDrop: handleDrop,
|
|
11456
11596
|
onDragOver: handleDragOver,
|
|
11457
11597
|
onDragLeave: handleDragLeave,
|
|
@@ -11462,55 +11602,167 @@ function KanbanColumn({
|
|
|
11462
11602
|
className
|
|
11463
11603
|
),
|
|
11464
11604
|
children: [
|
|
11465
|
-
title && /* @__PURE__ */ jsxRuntime.
|
|
11466
|
-
|
|
11605
|
+
title && /* @__PURE__ */ jsxRuntime.jsxs("h3", { className: "mb-3 flex items-center gap-2 text-sm font-semibold text-zinc-600 dark:text-zinc-400", children: [
|
|
11606
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex-1", children: title }),
|
|
11607
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
11608
|
+
"span",
|
|
11609
|
+
{
|
|
11610
|
+
className: cn(
|
|
11611
|
+
"rounded-full px-1.5 py-0.5 text-[10px] font-semibold",
|
|
11612
|
+
overWip ? "bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-300" : "bg-zinc-200 text-zinc-700 dark:bg-zinc-700 dark:text-zinc-300"
|
|
11613
|
+
),
|
|
11614
|
+
children: [
|
|
11615
|
+
cardCount,
|
|
11616
|
+
wipLimit !== void 0 ? `/${wipLimit}` : ""
|
|
11617
|
+
]
|
|
11618
|
+
}
|
|
11619
|
+
)
|
|
11620
|
+
] }),
|
|
11621
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { ref: cardsRef, className: "relative flex flex-1 flex-col gap-2", children: [
|
|
11622
|
+
children,
|
|
11623
|
+
showIndicator && /* @__PURE__ */ jsxRuntime.jsx(
|
|
11624
|
+
"div",
|
|
11625
|
+
{
|
|
11626
|
+
"data-react-fancy-kanban-drop-indicator": "",
|
|
11627
|
+
style: { top: dropY },
|
|
11628
|
+
className: "pointer-events-none absolute left-0 right-0 h-0.5 -translate-y-1/2 rounded-full bg-blue-500/80 shadow-[0_0_0_3px_rgba(59,130,246,0.15)]"
|
|
11629
|
+
}
|
|
11630
|
+
)
|
|
11631
|
+
] })
|
|
11467
11632
|
]
|
|
11468
11633
|
}
|
|
11469
11634
|
) });
|
|
11470
11635
|
}
|
|
11471
11636
|
KanbanColumn.displayName = "KanbanColumn";
|
|
11472
|
-
|
|
11473
|
-
|
|
11474
|
-
|
|
11637
|
+
function KanbanColumnHandle({
|
|
11638
|
+
children,
|
|
11639
|
+
className
|
|
11640
|
+
}) {
|
|
11641
|
+
const { setDraggedColumn } = useKanban();
|
|
11475
11642
|
const columnId = useKanbanColumn();
|
|
11476
|
-
const handleDragStart = react.useCallback(
|
|
11477
|
-
|
|
11478
|
-
|
|
11479
|
-
|
|
11643
|
+
const handleDragStart = react.useCallback(
|
|
11644
|
+
(e) => {
|
|
11645
|
+
e.dataTransfer.effectAllowed = "move";
|
|
11646
|
+
e.dataTransfer.setData("text/plain", columnId);
|
|
11647
|
+
e.stopPropagation();
|
|
11648
|
+
setDraggedColumn(columnId);
|
|
11649
|
+
},
|
|
11650
|
+
[columnId, setDraggedColumn]
|
|
11651
|
+
);
|
|
11480
11652
|
const handleDragEnd = react.useCallback(() => {
|
|
11481
|
-
|
|
11482
|
-
|
|
11483
|
-
}, [setDraggedCard, setDragSource]);
|
|
11653
|
+
setDraggedColumn(null);
|
|
11654
|
+
}, [setDraggedColumn]);
|
|
11484
11655
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
11485
11656
|
"div",
|
|
11486
11657
|
{
|
|
11487
|
-
"data-react-fancy-kanban-card": "",
|
|
11488
11658
|
draggable: true,
|
|
11489
11659
|
onDragStart: handleDragStart,
|
|
11490
11660
|
onDragEnd: handleDragEnd,
|
|
11661
|
+
"data-react-fancy-kanban-column-handle": "",
|
|
11491
11662
|
className: cn(
|
|
11492
|
-
|
|
11493
|
-
"cursor-grab active:cursor-grabbing",
|
|
11494
|
-
!unstyled && DEFAULT_CARD_CLASSES,
|
|
11663
|
+
"cursor-grab active:cursor-grabbing select-none",
|
|
11495
11664
|
className
|
|
11496
11665
|
),
|
|
11497
11666
|
children
|
|
11498
11667
|
}
|
|
11499
11668
|
);
|
|
11500
11669
|
}
|
|
11501
|
-
|
|
11502
|
-
function KanbanRoot({
|
|
11670
|
+
KanbanColumnHandle.displayName = "KanbanColumnHandle";
|
|
11671
|
+
function KanbanRoot({
|
|
11672
|
+
children,
|
|
11673
|
+
onCardMove,
|
|
11674
|
+
onColumnMove,
|
|
11675
|
+
className
|
|
11676
|
+
}) {
|
|
11503
11677
|
const [draggedCard, setDraggedCard] = react.useState(null);
|
|
11504
11678
|
const [dragSource, setDragSource] = react.useState(null);
|
|
11679
|
+
const [draggedColumn, setDraggedColumn] = react.useState(null);
|
|
11680
|
+
const orderRef = react.useRef([]);
|
|
11681
|
+
const [columnIds, setColumnIds] = react.useState([]);
|
|
11682
|
+
const registerColumn = react.useCallback((id) => {
|
|
11683
|
+
if (!orderRef.current.includes(id)) {
|
|
11684
|
+
orderRef.current = [...orderRef.current, id];
|
|
11685
|
+
setColumnIds(orderRef.current);
|
|
11686
|
+
}
|
|
11687
|
+
return () => {
|
|
11688
|
+
orderRef.current = orderRef.current.filter((x) => x !== id);
|
|
11689
|
+
setColumnIds(orderRef.current);
|
|
11690
|
+
};
|
|
11691
|
+
}, []);
|
|
11505
11692
|
const ctx = react.useMemo(
|
|
11506
|
-
() => ({
|
|
11507
|
-
|
|
11693
|
+
() => ({
|
|
11694
|
+
draggedCard,
|
|
11695
|
+
setDraggedCard,
|
|
11696
|
+
dragSource,
|
|
11697
|
+
setDragSource,
|
|
11698
|
+
draggedColumn,
|
|
11699
|
+
setDraggedColumn,
|
|
11700
|
+
onCardMove,
|
|
11701
|
+
onColumnMove,
|
|
11702
|
+
columnIds,
|
|
11703
|
+
registerColumn
|
|
11704
|
+
}),
|
|
11705
|
+
[
|
|
11706
|
+
draggedCard,
|
|
11707
|
+
dragSource,
|
|
11708
|
+
draggedColumn,
|
|
11709
|
+
onCardMove,
|
|
11710
|
+
onColumnMove,
|
|
11711
|
+
columnIds,
|
|
11712
|
+
registerColumn
|
|
11713
|
+
]
|
|
11714
|
+
);
|
|
11715
|
+
const containerRef = react.useRef(null);
|
|
11716
|
+
const handleDragOver = react.useCallback(
|
|
11717
|
+
(e) => {
|
|
11718
|
+
if (!draggedColumn) return;
|
|
11719
|
+
e.preventDefault();
|
|
11720
|
+
},
|
|
11721
|
+
[draggedColumn]
|
|
11508
11722
|
);
|
|
11509
|
-
|
|
11723
|
+
const handleDrop = react.useCallback(
|
|
11724
|
+
(e) => {
|
|
11725
|
+
if (!draggedColumn || !containerRef.current) return;
|
|
11726
|
+
e.preventDefault();
|
|
11727
|
+
const cols = containerRef.current.querySelectorAll(
|
|
11728
|
+
"[data-react-fancy-kanban-column]"
|
|
11729
|
+
);
|
|
11730
|
+
const x = e.clientX;
|
|
11731
|
+
let dropIdx = cols.length;
|
|
11732
|
+
for (let i = 0; i < cols.length; i++) {
|
|
11733
|
+
const rect = cols[i].getBoundingClientRect();
|
|
11734
|
+
if (x < rect.left + rect.width / 2) {
|
|
11735
|
+
dropIdx = i;
|
|
11736
|
+
break;
|
|
11737
|
+
}
|
|
11738
|
+
}
|
|
11739
|
+
const sourceIdx = columnIds.indexOf(draggedColumn);
|
|
11740
|
+
const finalIdx = sourceIdx >= 0 && dropIdx > sourceIdx ? dropIdx - 1 : dropIdx;
|
|
11741
|
+
if (finalIdx !== sourceIdx) {
|
|
11742
|
+
onColumnMove?.(draggedColumn, finalIdx);
|
|
11743
|
+
}
|
|
11744
|
+
setDraggedColumn(null);
|
|
11745
|
+
},
|
|
11746
|
+
[draggedColumn, columnIds, onColumnMove]
|
|
11747
|
+
);
|
|
11748
|
+
return /* @__PURE__ */ jsxRuntime.jsx(KanbanContext.Provider, { value: ctx, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
11749
|
+
"div",
|
|
11750
|
+
{
|
|
11751
|
+
ref: containerRef,
|
|
11752
|
+
"data-react-fancy-kanban": "",
|
|
11753
|
+
onDragOver: handleDragOver,
|
|
11754
|
+
onDrop: handleDrop,
|
|
11755
|
+
role: "application",
|
|
11756
|
+
"aria-roledescription": "kanban board",
|
|
11757
|
+
className: cn("flex gap-4 overflow-x-auto p-4", className),
|
|
11758
|
+
children
|
|
11759
|
+
}
|
|
11760
|
+
) });
|
|
11510
11761
|
}
|
|
11511
11762
|
var Kanban = Object.assign(KanbanRoot, {
|
|
11512
11763
|
Column: KanbanColumn,
|
|
11513
|
-
Card: KanbanCard
|
|
11764
|
+
Card: KanbanCard,
|
|
11765
|
+
ColumnHandle: KanbanColumnHandle
|
|
11514
11766
|
});
|
|
11515
11767
|
var CanvasContext = react.createContext(null);
|
|
11516
11768
|
function useCanvas() {
|