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