@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.js
CHANGED
|
@@ -11113,35 +11113,175 @@ var KanbanColumnContext = createContext("");
|
|
|
11113
11113
|
function useKanbanColumn() {
|
|
11114
11114
|
return useContext(KanbanColumnContext);
|
|
11115
11115
|
}
|
|
11116
|
+
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";
|
|
11117
|
+
function KanbanCard({ children, id, className, unstyled }) {
|
|
11118
|
+
const { setDraggedCard, setDragSource } = useKanban();
|
|
11119
|
+
const columnId = useKanbanColumn();
|
|
11120
|
+
const handleDragStart = useCallback(() => {
|
|
11121
|
+
setDraggedCard(id);
|
|
11122
|
+
setDragSource(columnId);
|
|
11123
|
+
}, [id, columnId, setDraggedCard, setDragSource]);
|
|
11124
|
+
const handleDragEnd = useCallback(() => {
|
|
11125
|
+
setDraggedCard(null);
|
|
11126
|
+
setDragSource(null);
|
|
11127
|
+
}, [setDraggedCard, setDragSource]);
|
|
11128
|
+
return /* @__PURE__ */ jsx(
|
|
11129
|
+
"div",
|
|
11130
|
+
{
|
|
11131
|
+
"data-react-fancy-kanban-card": "",
|
|
11132
|
+
draggable: true,
|
|
11133
|
+
onDragStart: handleDragStart,
|
|
11134
|
+
onDragEnd: handleDragEnd,
|
|
11135
|
+
className: cn(
|
|
11136
|
+
// Drag affordance — kept even when unstyled so users still see grab cursors.
|
|
11137
|
+
"cursor-grab active:cursor-grabbing",
|
|
11138
|
+
!unstyled && DEFAULT_CARD_CLASSES,
|
|
11139
|
+
className
|
|
11140
|
+
),
|
|
11141
|
+
children
|
|
11142
|
+
}
|
|
11143
|
+
);
|
|
11144
|
+
}
|
|
11145
|
+
KanbanCard.displayName = "KanbanCard";
|
|
11116
11146
|
var DEFAULT_COLUMN_CLASSES = "min-h-[200px] w-72 rounded-xl bg-zinc-50 p-3 dark:bg-zinc-800/50";
|
|
11147
|
+
function countCardChildren(children) {
|
|
11148
|
+
let n = 0;
|
|
11149
|
+
Children.forEach(children, (child) => {
|
|
11150
|
+
if (!isValidElement(child)) return;
|
|
11151
|
+
if (child.type === KanbanCard) {
|
|
11152
|
+
n += 1;
|
|
11153
|
+
return;
|
|
11154
|
+
}
|
|
11155
|
+
const inner = child.props.children;
|
|
11156
|
+
if (inner !== void 0) n += countCardChildren(inner);
|
|
11157
|
+
});
|
|
11158
|
+
return n;
|
|
11159
|
+
}
|
|
11160
|
+
function findCardIndex(children, cardId) {
|
|
11161
|
+
let idx = -1;
|
|
11162
|
+
let i = 0;
|
|
11163
|
+
function walk2(nodes) {
|
|
11164
|
+
Children.forEach(nodes, (child) => {
|
|
11165
|
+
if (idx !== -1) return;
|
|
11166
|
+
if (!isValidElement(child)) return;
|
|
11167
|
+
if (child.type === KanbanCard) {
|
|
11168
|
+
if (child.props.id === cardId) idx = i;
|
|
11169
|
+
i += 1;
|
|
11170
|
+
return;
|
|
11171
|
+
}
|
|
11172
|
+
const inner = child.props.children;
|
|
11173
|
+
if (inner !== void 0) walk2(inner);
|
|
11174
|
+
});
|
|
11175
|
+
}
|
|
11176
|
+
walk2(children);
|
|
11177
|
+
return idx;
|
|
11178
|
+
}
|
|
11117
11179
|
function KanbanColumn({
|
|
11118
11180
|
children,
|
|
11119
11181
|
id,
|
|
11120
11182
|
title,
|
|
11121
11183
|
className,
|
|
11122
|
-
unstyled
|
|
11184
|
+
unstyled,
|
|
11185
|
+
wipLimit,
|
|
11186
|
+
hideWhenEmpty
|
|
11123
11187
|
}) {
|
|
11124
|
-
const { onCardMove, draggedCard, dragSource } = useKanban();
|
|
11188
|
+
const { onCardMove, draggedCard, dragSource, registerColumn } = useKanban();
|
|
11125
11189
|
const [dragOver, setDragOver] = useState(false);
|
|
11190
|
+
const [dropIndex, setDropIndex] = useState(null);
|
|
11191
|
+
const [dropY, setDropY] = useState(null);
|
|
11192
|
+
const cardsRef = useRef(null);
|
|
11193
|
+
useEffect(() => registerColumn(id), [id, registerColumn]);
|
|
11194
|
+
const updateDrop = useCallback((clientY) => {
|
|
11195
|
+
const container = cardsRef.current;
|
|
11196
|
+
if (!container) {
|
|
11197
|
+
setDropIndex(null);
|
|
11198
|
+
setDropY(null);
|
|
11199
|
+
return;
|
|
11200
|
+
}
|
|
11201
|
+
const all = Array.from(
|
|
11202
|
+
container.querySelectorAll(
|
|
11203
|
+
"[data-react-fancy-kanban-card]"
|
|
11204
|
+
)
|
|
11205
|
+
);
|
|
11206
|
+
const cards = all.filter(
|
|
11207
|
+
(el) => el.closest("[data-react-fancy-kanban-column]") === cardsRef.current?.closest("[data-react-fancy-kanban-column]")
|
|
11208
|
+
);
|
|
11209
|
+
const containerRect = container.getBoundingClientRect();
|
|
11210
|
+
if (cards.length === 0) {
|
|
11211
|
+
setDropIndex(0);
|
|
11212
|
+
setDropY(0);
|
|
11213
|
+
return;
|
|
11214
|
+
}
|
|
11215
|
+
let idx = cards.length;
|
|
11216
|
+
let yRel = 0;
|
|
11217
|
+
for (let i = 0; i < cards.length; i++) {
|
|
11218
|
+
const rect = cards[i].getBoundingClientRect();
|
|
11219
|
+
if (clientY < rect.top + rect.height / 2) {
|
|
11220
|
+
idx = i;
|
|
11221
|
+
yRel = rect.top - containerRect.top;
|
|
11222
|
+
break;
|
|
11223
|
+
}
|
|
11224
|
+
}
|
|
11225
|
+
if (idx === cards.length) {
|
|
11226
|
+
const last = cards[cards.length - 1].getBoundingClientRect();
|
|
11227
|
+
yRel = last.bottom - containerRect.top;
|
|
11228
|
+
}
|
|
11229
|
+
setDropIndex(idx);
|
|
11230
|
+
setDropY(yRel);
|
|
11231
|
+
}, []);
|
|
11232
|
+
const handleDragOver = useCallback(
|
|
11233
|
+
(e) => {
|
|
11234
|
+
if (!draggedCard) return;
|
|
11235
|
+
e.preventDefault();
|
|
11236
|
+
e.stopPropagation();
|
|
11237
|
+
setDragOver(true);
|
|
11238
|
+
updateDrop(e.clientY);
|
|
11239
|
+
},
|
|
11240
|
+
[draggedCard, updateDrop]
|
|
11241
|
+
);
|
|
11242
|
+
const handleDragLeave = useCallback((e) => {
|
|
11243
|
+
if (e.currentTarget.contains(e.relatedTarget)) return;
|
|
11244
|
+
setDragOver(false);
|
|
11245
|
+
setDropIndex(null);
|
|
11246
|
+
setDropY(null);
|
|
11247
|
+
}, []);
|
|
11126
11248
|
const handleDrop = useCallback(
|
|
11127
11249
|
(e) => {
|
|
11250
|
+
if (!draggedCard) return;
|
|
11128
11251
|
e.preventDefault();
|
|
11129
|
-
|
|
11130
|
-
|
|
11131
|
-
|
|
11252
|
+
e.stopPropagation();
|
|
11253
|
+
const target = dropIndex ?? 0;
|
|
11254
|
+
if (dragSource) {
|
|
11255
|
+
let finalIdx = target;
|
|
11256
|
+
if (dragSource === id) {
|
|
11257
|
+
const srcIdx = findCardIndex(children, draggedCard);
|
|
11258
|
+
if (srcIdx !== -1 && target > srcIdx) finalIdx = target - 1;
|
|
11259
|
+
if (srcIdx === finalIdx) {
|
|
11260
|
+
setDragOver(false);
|
|
11261
|
+
setDropIndex(null);
|
|
11262
|
+
setDropY(null);
|
|
11263
|
+
return;
|
|
11264
|
+
}
|
|
11265
|
+
}
|
|
11266
|
+
onCardMove?.(draggedCard, dragSource, id, finalIdx);
|
|
11132
11267
|
}
|
|
11268
|
+
setDragOver(false);
|
|
11269
|
+
setDropIndex(null);
|
|
11270
|
+
setDropY(null);
|
|
11133
11271
|
},
|
|
11134
|
-
[draggedCard, dragSource, id, onCardMove]
|
|
11272
|
+
[draggedCard, dragSource, dropIndex, id, onCardMove, children]
|
|
11135
11273
|
);
|
|
11136
|
-
const
|
|
11137
|
-
|
|
11138
|
-
|
|
11139
|
-
|
|
11140
|
-
const handleDragLeave = useCallback(() => setDragOver(false), []);
|
|
11274
|
+
const cardCount = countCardChildren(children);
|
|
11275
|
+
if (hideWhenEmpty && cardCount === 0 && !draggedCard) return null;
|
|
11276
|
+
const showIndicator = draggedCard !== null && dropIndex !== null && dropY !== null && dragOver;
|
|
11277
|
+
const overWip = wipLimit !== void 0 && cardCount > wipLimit;
|
|
11141
11278
|
return /* @__PURE__ */ jsx(KanbanColumnContext.Provider, { value: id, children: /* @__PURE__ */ jsxs(
|
|
11142
11279
|
"div",
|
|
11143
11280
|
{
|
|
11144
11281
|
"data-react-fancy-kanban-column": "",
|
|
11282
|
+
"data-column-id": id,
|
|
11283
|
+
role: "group",
|
|
11284
|
+
"aria-label": title,
|
|
11145
11285
|
onDrop: handleDrop,
|
|
11146
11286
|
onDragOver: handleDragOver,
|
|
11147
11287
|
onDragLeave: handleDragLeave,
|
|
@@ -11152,55 +11292,167 @@ function KanbanColumn({
|
|
|
11152
11292
|
className
|
|
11153
11293
|
),
|
|
11154
11294
|
children: [
|
|
11155
|
-
title && /* @__PURE__ */
|
|
11156
|
-
|
|
11295
|
+
title && /* @__PURE__ */ jsxs("h3", { className: "mb-3 flex items-center gap-2 text-sm font-semibold text-zinc-600 dark:text-zinc-400", children: [
|
|
11296
|
+
/* @__PURE__ */ jsx("span", { className: "flex-1", children: title }),
|
|
11297
|
+
/* @__PURE__ */ jsxs(
|
|
11298
|
+
"span",
|
|
11299
|
+
{
|
|
11300
|
+
className: cn(
|
|
11301
|
+
"rounded-full px-1.5 py-0.5 text-[10px] font-semibold",
|
|
11302
|
+
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"
|
|
11303
|
+
),
|
|
11304
|
+
children: [
|
|
11305
|
+
cardCount,
|
|
11306
|
+
wipLimit !== void 0 ? `/${wipLimit}` : ""
|
|
11307
|
+
]
|
|
11308
|
+
}
|
|
11309
|
+
)
|
|
11310
|
+
] }),
|
|
11311
|
+
/* @__PURE__ */ jsxs("div", { ref: cardsRef, className: "relative flex flex-1 flex-col gap-2", children: [
|
|
11312
|
+
children,
|
|
11313
|
+
showIndicator && /* @__PURE__ */ jsx(
|
|
11314
|
+
"div",
|
|
11315
|
+
{
|
|
11316
|
+
"data-react-fancy-kanban-drop-indicator": "",
|
|
11317
|
+
style: { top: dropY },
|
|
11318
|
+
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)]"
|
|
11319
|
+
}
|
|
11320
|
+
)
|
|
11321
|
+
] })
|
|
11157
11322
|
]
|
|
11158
11323
|
}
|
|
11159
11324
|
) });
|
|
11160
11325
|
}
|
|
11161
11326
|
KanbanColumn.displayName = "KanbanColumn";
|
|
11162
|
-
|
|
11163
|
-
|
|
11164
|
-
|
|
11327
|
+
function KanbanColumnHandle({
|
|
11328
|
+
children,
|
|
11329
|
+
className
|
|
11330
|
+
}) {
|
|
11331
|
+
const { setDraggedColumn } = useKanban();
|
|
11165
11332
|
const columnId = useKanbanColumn();
|
|
11166
|
-
const handleDragStart = useCallback(
|
|
11167
|
-
|
|
11168
|
-
|
|
11169
|
-
|
|
11333
|
+
const handleDragStart = useCallback(
|
|
11334
|
+
(e) => {
|
|
11335
|
+
e.dataTransfer.effectAllowed = "move";
|
|
11336
|
+
e.dataTransfer.setData("text/plain", columnId);
|
|
11337
|
+
e.stopPropagation();
|
|
11338
|
+
setDraggedColumn(columnId);
|
|
11339
|
+
},
|
|
11340
|
+
[columnId, setDraggedColumn]
|
|
11341
|
+
);
|
|
11170
11342
|
const handleDragEnd = useCallback(() => {
|
|
11171
|
-
|
|
11172
|
-
|
|
11173
|
-
}, [setDraggedCard, setDragSource]);
|
|
11343
|
+
setDraggedColumn(null);
|
|
11344
|
+
}, [setDraggedColumn]);
|
|
11174
11345
|
return /* @__PURE__ */ jsx(
|
|
11175
11346
|
"div",
|
|
11176
11347
|
{
|
|
11177
|
-
"data-react-fancy-kanban-card": "",
|
|
11178
11348
|
draggable: true,
|
|
11179
11349
|
onDragStart: handleDragStart,
|
|
11180
11350
|
onDragEnd: handleDragEnd,
|
|
11351
|
+
"data-react-fancy-kanban-column-handle": "",
|
|
11181
11352
|
className: cn(
|
|
11182
|
-
|
|
11183
|
-
"cursor-grab active:cursor-grabbing",
|
|
11184
|
-
!unstyled && DEFAULT_CARD_CLASSES,
|
|
11353
|
+
"cursor-grab active:cursor-grabbing select-none",
|
|
11185
11354
|
className
|
|
11186
11355
|
),
|
|
11187
11356
|
children
|
|
11188
11357
|
}
|
|
11189
11358
|
);
|
|
11190
11359
|
}
|
|
11191
|
-
|
|
11192
|
-
function KanbanRoot({
|
|
11360
|
+
KanbanColumnHandle.displayName = "KanbanColumnHandle";
|
|
11361
|
+
function KanbanRoot({
|
|
11362
|
+
children,
|
|
11363
|
+
onCardMove,
|
|
11364
|
+
onColumnMove,
|
|
11365
|
+
className
|
|
11366
|
+
}) {
|
|
11193
11367
|
const [draggedCard, setDraggedCard] = useState(null);
|
|
11194
11368
|
const [dragSource, setDragSource] = useState(null);
|
|
11369
|
+
const [draggedColumn, setDraggedColumn] = useState(null);
|
|
11370
|
+
const orderRef = useRef([]);
|
|
11371
|
+
const [columnIds, setColumnIds] = useState([]);
|
|
11372
|
+
const registerColumn = useCallback((id) => {
|
|
11373
|
+
if (!orderRef.current.includes(id)) {
|
|
11374
|
+
orderRef.current = [...orderRef.current, id];
|
|
11375
|
+
setColumnIds(orderRef.current);
|
|
11376
|
+
}
|
|
11377
|
+
return () => {
|
|
11378
|
+
orderRef.current = orderRef.current.filter((x) => x !== id);
|
|
11379
|
+
setColumnIds(orderRef.current);
|
|
11380
|
+
};
|
|
11381
|
+
}, []);
|
|
11195
11382
|
const ctx = useMemo(
|
|
11196
|
-
() => ({
|
|
11197
|
-
|
|
11383
|
+
() => ({
|
|
11384
|
+
draggedCard,
|
|
11385
|
+
setDraggedCard,
|
|
11386
|
+
dragSource,
|
|
11387
|
+
setDragSource,
|
|
11388
|
+
draggedColumn,
|
|
11389
|
+
setDraggedColumn,
|
|
11390
|
+
onCardMove,
|
|
11391
|
+
onColumnMove,
|
|
11392
|
+
columnIds,
|
|
11393
|
+
registerColumn
|
|
11394
|
+
}),
|
|
11395
|
+
[
|
|
11396
|
+
draggedCard,
|
|
11397
|
+
dragSource,
|
|
11398
|
+
draggedColumn,
|
|
11399
|
+
onCardMove,
|
|
11400
|
+
onColumnMove,
|
|
11401
|
+
columnIds,
|
|
11402
|
+
registerColumn
|
|
11403
|
+
]
|
|
11404
|
+
);
|
|
11405
|
+
const containerRef = useRef(null);
|
|
11406
|
+
const handleDragOver = useCallback(
|
|
11407
|
+
(e) => {
|
|
11408
|
+
if (!draggedColumn) return;
|
|
11409
|
+
e.preventDefault();
|
|
11410
|
+
},
|
|
11411
|
+
[draggedColumn]
|
|
11198
11412
|
);
|
|
11199
|
-
|
|
11413
|
+
const handleDrop = useCallback(
|
|
11414
|
+
(e) => {
|
|
11415
|
+
if (!draggedColumn || !containerRef.current) return;
|
|
11416
|
+
e.preventDefault();
|
|
11417
|
+
const cols = containerRef.current.querySelectorAll(
|
|
11418
|
+
"[data-react-fancy-kanban-column]"
|
|
11419
|
+
);
|
|
11420
|
+
const x = e.clientX;
|
|
11421
|
+
let dropIdx = cols.length;
|
|
11422
|
+
for (let i = 0; i < cols.length; i++) {
|
|
11423
|
+
const rect = cols[i].getBoundingClientRect();
|
|
11424
|
+
if (x < rect.left + rect.width / 2) {
|
|
11425
|
+
dropIdx = i;
|
|
11426
|
+
break;
|
|
11427
|
+
}
|
|
11428
|
+
}
|
|
11429
|
+
const sourceIdx = columnIds.indexOf(draggedColumn);
|
|
11430
|
+
const finalIdx = sourceIdx >= 0 && dropIdx > sourceIdx ? dropIdx - 1 : dropIdx;
|
|
11431
|
+
if (finalIdx !== sourceIdx) {
|
|
11432
|
+
onColumnMove?.(draggedColumn, finalIdx);
|
|
11433
|
+
}
|
|
11434
|
+
setDraggedColumn(null);
|
|
11435
|
+
},
|
|
11436
|
+
[draggedColumn, columnIds, onColumnMove]
|
|
11437
|
+
);
|
|
11438
|
+
return /* @__PURE__ */ jsx(KanbanContext.Provider, { value: ctx, children: /* @__PURE__ */ jsx(
|
|
11439
|
+
"div",
|
|
11440
|
+
{
|
|
11441
|
+
ref: containerRef,
|
|
11442
|
+
"data-react-fancy-kanban": "",
|
|
11443
|
+
onDragOver: handleDragOver,
|
|
11444
|
+
onDrop: handleDrop,
|
|
11445
|
+
role: "application",
|
|
11446
|
+
"aria-roledescription": "kanban board",
|
|
11447
|
+
className: cn("flex gap-4 overflow-x-auto p-4", className),
|
|
11448
|
+
children
|
|
11449
|
+
}
|
|
11450
|
+
) });
|
|
11200
11451
|
}
|
|
11201
11452
|
var Kanban = Object.assign(KanbanRoot, {
|
|
11202
11453
|
Column: KanbanColumn,
|
|
11203
|
-
Card: KanbanCard
|
|
11454
|
+
Card: KanbanCard,
|
|
11455
|
+
ColumnHandle: KanbanColumnHandle
|
|
11204
11456
|
});
|
|
11205
11457
|
var CanvasContext = createContext(null);
|
|
11206
11458
|
function useCanvas() {
|