gridsmith-ui 0.29.1 → 0.30.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/CHANGELOG.md +54 -0
- package/README.md +5 -1
- package/dist/DropdownMenu-DqT7ZQ0D.d.ts +39 -0
- package/dist/charts.js +4 -2
- package/dist/charts.js.map +1 -1
- package/dist/chat.js +4 -4
- package/dist/{chunk-OYNUU346.js → chunk-7QLXYRPD.js} +3 -12
- package/dist/chunk-7QLXYRPD.js.map +1 -0
- package/dist/chunk-F2BNJSON.js +1348 -0
- package/dist/chunk-F2BNJSON.js.map +1 -0
- package/dist/{chunk-37WOC7F7.js → chunk-GQVBZ4GW.js} +29 -8
- package/dist/chunk-GQVBZ4GW.js.map +1 -0
- package/dist/{chunk-MFAWJSMJ.js → chunk-INGF2CPW.js} +3 -22
- package/dist/chunk-INGF2CPW.js.map +1 -0
- package/dist/{chunk-I3MTQ3CS.js → chunk-KTPGMJUK.js} +5 -5
- package/dist/{chunk-I3MTQ3CS.js.map → chunk-KTPGMJUK.js.map} +1 -1
- package/dist/{chunk-MKFWHWBK.js → chunk-L6HWACK7.js} +7 -7
- package/dist/chunk-L6HWACK7.js.map +1 -0
- package/dist/chunk-LJYSVZMI.js +25 -0
- package/dist/chunk-LJYSVZMI.js.map +1 -0
- package/dist/{chunk-FUKSLQ77.js → chunk-NI5542ZM.js} +39 -35
- package/dist/chunk-NI5542ZM.js.map +1 -0
- package/dist/chunk-NKUCS67T.js +13 -0
- package/dist/chunk-NKUCS67T.js.map +1 -0
- package/dist/{chunk-RECCCA7Z.js → chunk-NLXQ323J.js} +4 -4
- package/dist/{chunk-RECCCA7Z.js.map → chunk-NLXQ323J.js.map} +1 -1
- package/dist/{chunk-ZI3OQDET.js → chunk-OIOII5L6.js} +93 -4
- package/dist/chunk-OIOII5L6.js.map +1 -0
- package/dist/{chunk-RJEUQ7RL.js → chunk-Q36JVJX4.js} +46 -89
- package/dist/chunk-Q36JVJX4.js.map +1 -0
- package/dist/{chunk-FD4YED7O.js → chunk-RHWTMTU5.js} +3 -3
- package/dist/{chunk-FD4YED7O.js.map → chunk-RHWTMTU5.js.map} +1 -1
- package/dist/chunk-TYNDXMBV.js +105 -0
- package/dist/chunk-TYNDXMBV.js.map +1 -0
- package/dist/{chunk-4TUJXDRU.js → chunk-UNS4TGGK.js} +20 -11
- package/dist/chunk-UNS4TGGK.js.map +1 -0
- package/dist/chunk-Z4MCD73B.js +316 -0
- package/dist/chunk-Z4MCD73B.js.map +1 -0
- package/dist/data-grid.d.ts +25 -6
- package/dist/data-grid.js +177 -55
- package/dist/data-grid.js.map +1 -1
- package/dist/editor-mentions.js +1 -1
- package/dist/editor.js +3 -3
- package/dist/{feather-FNBQ74RR.js → feather-XFFEFLTV.js} +5 -3
- package/dist/feather-XFFEFLTV.js.map +1 -0
- package/dist/{heroicons-DODCSYWD.js → heroicons-L6DOHBEA.js} +5 -3
- package/dist/heroicons-L6DOHBEA.js.map +1 -0
- package/dist/icons.d.ts +1 -1
- package/dist/icons.js +1 -1
- package/dist/index.d.ts +138 -39
- package/dist/index.js +660 -1568
- package/dist/index.js.map +1 -1
- package/dist/kanban.d.ts +26 -8
- package/dist/kanban.js +200 -76
- package/dist/kanban.js.map +1 -1
- package/dist/{lucide-ZWE3EPGP.js → lucide-JOORDTO6.js} +5 -3
- package/dist/lucide-JOORDTO6.js.map +1 -0
- package/dist/{phosphor-DIHPA4AJ.js → phosphor-TKGDYNGR.js} +5 -3
- package/dist/phosphor-TKGDYNGR.js.map +1 -0
- package/dist/resizable.js +10 -9
- package/dist/resizable.js.map +1 -1
- package/dist/roots.css +12 -8
- package/dist/{table-utils-CxnpFHRF.d.ts → table-utils-BXkP-2Hx.d.ts} +3 -1
- package/dist/{tabler-D3VYTSGI.js → tabler-YGETLYRU.js} +5 -3
- package/dist/tabler-YGETLYRU.js.map +1 -0
- package/dist/tokens.css +14 -0
- package/dist/utils.js.map +1 -1
- package/dist/virtualized.js +8 -7
- package/dist/virtualized.js.map +1 -1
- package/dist-static/tokens.css +14 -0
- package/package.json +1 -1
- package/dist/chunk-37WOC7F7.js.map +0 -1
- package/dist/chunk-4TUJXDRU.js.map +0 -1
- package/dist/chunk-FUKSLQ77.js.map +0 -1
- package/dist/chunk-MFAWJSMJ.js.map +0 -1
- package/dist/chunk-MKFWHWBK.js.map +0 -1
- package/dist/chunk-OYNUU346.js.map +0 -1
- package/dist/chunk-QM4SJKWV.js +0 -51
- package/dist/chunk-QM4SJKWV.js.map +0 -1
- package/dist/chunk-QNHVPE2B.js +0 -117
- package/dist/chunk-QNHVPE2B.js.map +0 -1
- package/dist/chunk-RJEUQ7RL.js.map +0 -1
- package/dist/chunk-ZI3OQDET.js.map +0 -1
- package/dist/feather-FNBQ74RR.js.map +0 -1
- package/dist/heroicons-DODCSYWD.js.map +0 -1
- package/dist/lucide-ZWE3EPGP.js.map +0 -1
- package/dist/phosphor-DIHPA4AJ.js.map +0 -1
- package/dist/tabler-D3VYTSGI.js.map +0 -1
package/dist/kanban.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
|
+
import { D as DropdownMenuSubmenu, a as DropdownMenuEntry } from './DropdownMenu-DqT7ZQ0D.js';
|
|
4
|
+
import './icons.js';
|
|
3
5
|
|
|
4
6
|
interface KanbanCard {
|
|
5
7
|
id: string;
|
|
@@ -13,6 +15,12 @@ interface KanbanColumn {
|
|
|
13
15
|
title: string;
|
|
14
16
|
cards: KanbanCard[];
|
|
15
17
|
color?: string;
|
|
18
|
+
/**
|
|
19
|
+
* The column's work-in-progress limit, in cards: the header shows its count against it ("4/3"), and a column holding
|
|
20
|
+
* more is marked with a warning icon, its count's words saying so to a screen reader. A limit informs and never refuses
|
|
21
|
+
* a drop; a page that must refuse a move still can, in onCardMove. Default: no limit, the count alone.
|
|
22
|
+
*/
|
|
23
|
+
limit?: number;
|
|
16
24
|
}
|
|
17
25
|
interface KanbanBoardProps {
|
|
18
26
|
/**
|
|
@@ -28,19 +36,22 @@ interface KanbanBoardProps {
|
|
|
28
36
|
*/
|
|
29
37
|
onColumnsChange?: (columns: KanbanColumn[]) => unknown;
|
|
30
38
|
/**
|
|
31
|
-
* A card was
|
|
32
|
-
*
|
|
33
|
-
*
|
|
39
|
+
* A card was moved to a new place: its column and its index there, once on drop, or on a choice from the card's Move
|
|
40
|
+
* menu (the button beside each card that moves it without dragging: to the top of another column, or a step up or
|
|
41
|
+
* down) — a move between columns, into an empty one, or a reorder within one. Save the move here (or in
|
|
42
|
+
* onColumnsChange); a board with neither saves nothing. The index counts the cards the board shows: when `columns` is
|
|
43
|
+
* filtered, it is the place among the cards passed.
|
|
34
44
|
*
|
|
35
45
|
* Return the save's promise (an async function, or a mutation's mutateAsync) and the card stays where it was dropped
|
|
36
46
|
* while it saves, and while an earlier move of the same card still saves, whatever `columns` arrive meanwhile; when
|
|
37
47
|
* it rejects, the card goes back to where `columns` has it, a screen reader hears so, and the app says why (a toast
|
|
38
48
|
* inside the app shell). The save must settle (give a request a timeout): one that never does holds the card. A
|
|
39
|
-
* handler that throws refuses a move the same way (
|
|
40
|
-
* then saves, puts the card back itself when its save fails; the board still says the move was not
|
|
49
|
+
* handler that throws refuses a move the same way (one the person isn't allowed to make). An app that updates
|
|
50
|
+
* `columns` at once, then saves, puts the card back itself when its save fails; the board still says the move was not
|
|
51
|
+
* saved.
|
|
41
52
|
*/
|
|
42
53
|
onCardMove?: (card: KanbanCard, toColumnId: string, index: number) => unknown;
|
|
43
|
-
/** A board to look at, not rearrange: no dragging and no
|
|
54
|
+
/** A board to look at, not rearrange: no dragging, no grab cursor and no Move menu; cards still open with onCardClick. */
|
|
44
55
|
readOnly?: boolean;
|
|
45
56
|
renderCard?: (card: KanbanCard) => ReactNode;
|
|
46
57
|
/**
|
|
@@ -49,8 +60,15 @@ interface KanbanBoardProps {
|
|
|
49
60
|
* renderCard — a role="button" div fights the drag listeners and the judge cites it (Sprint 25.4 K-1).
|
|
50
61
|
*/
|
|
51
62
|
onCardClick?: (card: KanbanCard) => void;
|
|
63
|
+
/**
|
|
64
|
+
* A card's own actions (open, edit its labels, copy a link, delete…), for its ⋯ menu. `move` is the board's Move entry,
|
|
65
|
+
* a submenu of the columns the card can go to and its steps up and down: put it where it belongs among the actions;
|
|
66
|
+
* left out, the board adds it at the end, since it is the way to move a card without dragging (WCAG 2.5.7). Without
|
|
67
|
+
* cardActions the menu is the move choices alone. On a read-only board the menu holds the actions, with no Move.
|
|
68
|
+
*/
|
|
69
|
+
cardActions?: (card: KanbanCard, move: DropdownMenuSubmenu) => DropdownMenuEntry[];
|
|
52
70
|
className?: string;
|
|
53
71
|
}
|
|
54
|
-
declare function KanbanBoard({ columns: columnsProp, onColumnsChange, onCardMove, readOnly, renderCard, onCardClick, className, }: KanbanBoardProps): react_jsx_runtime.JSX.Element;
|
|
72
|
+
declare function KanbanBoard({ columns: columnsProp, onColumnsChange, onCardMove, readOnly, renderCard, onCardClick, cardActions, className, }: KanbanBoardProps): react_jsx_runtime.JSX.Element;
|
|
55
73
|
|
|
56
|
-
export { KanbanBoard };
|
|
74
|
+
export { KanbanBoard, type KanbanBoardProps, type KanbanCard, type KanbanColumn };
|
package/dist/kanban.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { DropdownMenu } from './chunk-Z4MCD73B.js';
|
|
3
|
+
import { useFillHeight, FillContext, useKeyboardScroll } from './chunk-RHWTMTU5.js';
|
|
4
|
+
import { Badge } from './chunk-NLXQ323J.js';
|
|
5
|
+
import { useFramesItself, Button } from './chunk-Q36JVJX4.js';
|
|
6
|
+
import './chunk-NKUCS67T.js';
|
|
7
|
+
import './chunk-ZHUVLBCC.js';
|
|
5
8
|
import { useRevealFocus } from './chunk-HTXP5BMM.js';
|
|
6
|
-
import './chunk-
|
|
7
|
-
import { useTextOverflow, AutoTooltip } from './chunk-
|
|
9
|
+
import './chunk-OIOII5L6.js';
|
|
10
|
+
import { useTextOverflow, AutoTooltip } from './chunk-GQVBZ4GW.js';
|
|
8
11
|
import './chunk-7Q46JXSK.js';
|
|
9
|
-
import { Icon } from './chunk-
|
|
12
|
+
import { Icon } from './chunk-L6HWACK7.js';
|
|
10
13
|
import './chunk-HP7WUZPK.js';
|
|
11
14
|
import './chunk-WH2FUFAC.js';
|
|
12
15
|
import { cn, FOCUS_INSET, FOCUS_RING } from './chunk-JTU7OBYJ.js';
|
|
@@ -16,11 +19,33 @@ import { sortableKeyboardCoordinates, SortableContext, verticalListSortingStrate
|
|
|
16
19
|
import { CSS } from '@dnd-kit/utilities';
|
|
17
20
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
18
21
|
|
|
22
|
+
var MOVE_ROOM_TITLE = "pr-[var(--ds-control-height-sm)]";
|
|
23
|
+
var MOVE_ROOM_CARD = "pr-[calc(var(--ds-space-3)+var(--ds-control-height-sm))]";
|
|
24
|
+
function cardMenuOf(card, moveEntries, cardActions) {
|
|
25
|
+
if (!cardActions) return moveEntries;
|
|
26
|
+
const move = { label: "Move", items: moveEntries };
|
|
27
|
+
const own = cardActions(card, move);
|
|
28
|
+
const isMove = (e) => e === move || "items" in e && e.items === move.items;
|
|
29
|
+
const divider = { type: "divider" };
|
|
30
|
+
const entries = moveEntries.length === 0 ? own.filter((e) => !isMove(e)) : own.some(isMove) ? own : [...own, divider, move];
|
|
31
|
+
const isDivider = (e) => !!e && "type" in e && e.type === "divider";
|
|
32
|
+
return entries.reduce((acc, e) => isDivider(e) && (acc.length === 0 || isDivider(acc[acc.length - 1])) ? acc : [...acc, e], []).filter((e, i, all) => !(isDivider(e) && i === all.length - 1));
|
|
33
|
+
}
|
|
34
|
+
function menuOf(moves, choose) {
|
|
35
|
+
const entry = (m) => ({ label: m.label, onClick: () => choose(m) });
|
|
36
|
+
const others = moves.filter((m) => m.step === 0).map(entry);
|
|
37
|
+
const steps = moves.filter((m) => m.step !== 0).map(entry);
|
|
38
|
+
return others.length && steps.length ? [...others, { type: "divider" }, ...steps] : [...others, ...steps];
|
|
39
|
+
}
|
|
40
|
+
var MOVE_PLACE = "absolute top-ds-1.5 right-ds-1.5";
|
|
19
41
|
function SortableCard({
|
|
20
42
|
card,
|
|
21
43
|
renderCard,
|
|
22
44
|
onCardClick,
|
|
23
|
-
readOnly
|
|
45
|
+
readOnly,
|
|
46
|
+
moves,
|
|
47
|
+
onMove,
|
|
48
|
+
cardActions
|
|
24
49
|
}) {
|
|
25
50
|
const {
|
|
26
51
|
attributes,
|
|
@@ -30,9 +55,34 @@ function SortableCard({
|
|
|
30
55
|
transition,
|
|
31
56
|
isDragging
|
|
32
57
|
} = useSortable({ id: card.id, disabled: readOnly });
|
|
58
|
+
const entries = cardMenuOf(card, readOnly ? [] : menuOf(moves, (m) => onMove(card.id, m.to, m.step)), cardActions);
|
|
59
|
+
const renderMenu = (style2, faded) => /* @__PURE__ */ jsx(
|
|
60
|
+
DropdownMenu,
|
|
61
|
+
{
|
|
62
|
+
items: entries,
|
|
63
|
+
trigger: (
|
|
64
|
+
// the library's icon button: its name says what its menu is for, and it shows that name as its one tooltip;
|
|
65
|
+
// shown at all times, since a phone has no hover; quiet, as a Card's ⋯ is
|
|
66
|
+
/* @__PURE__ */ jsx(
|
|
67
|
+
Button,
|
|
68
|
+
{
|
|
69
|
+
type: "button",
|
|
70
|
+
variant: "ghost",
|
|
71
|
+
size: "icon-sm",
|
|
72
|
+
"aria-label": cardActions ? `Actions for ${card.title}` : `Move ${card.title}`,
|
|
73
|
+
"data-kanban-move": card.id,
|
|
74
|
+
style: style2,
|
|
75
|
+
className: cn(MOVE_PLACE, "text-[var(--ds-text-muted)] hover:text-[var(--ds-text-primary)]", faded && "opacity-30"),
|
|
76
|
+
children: /* @__PURE__ */ jsx(Icon, { name: "more-horizontal", size: 14 })
|
|
77
|
+
}
|
|
78
|
+
)
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
);
|
|
82
|
+
const menu = entries.length > 0 ? renderMenu : null;
|
|
33
83
|
if (readOnly) {
|
|
34
84
|
const open = onCardClick ? () => onCardClick(card) : void 0;
|
|
35
|
-
|
|
85
|
+
const surface = /* @__PURE__ */ jsx(
|
|
36
86
|
"div",
|
|
37
87
|
{
|
|
38
88
|
ref: setNodeRef,
|
|
@@ -48,47 +98,63 @@ function SortableCard({
|
|
|
48
98
|
className: cn(
|
|
49
99
|
"rounded-[var(--ds-radius-md)] border-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
|
|
50
100
|
"bg-[var(--ds-bg-surface)] p-ds-3 shadow-[var(--ds-shadow-button)]",
|
|
101
|
+
// a card with its actions' ⋯ keeps the button's room clear, as a card that moves does
|
|
102
|
+
menu && renderCard && MOVE_ROOM_CARD,
|
|
51
103
|
open ? "cursor-pointer" : "cursor-default",
|
|
52
104
|
FOCUS_RING
|
|
53
105
|
),
|
|
54
|
-
children: renderCard ? renderCard(card) : /* @__PURE__ */ jsx(DefaultCard, { card })
|
|
106
|
+
children: renderCard ? renderCard(card) : /* @__PURE__ */ jsx(DefaultCard, { card, moveRoom: !!menu })
|
|
55
107
|
}
|
|
56
108
|
);
|
|
109
|
+
return menu ? /* @__PURE__ */ jsxs("div", { className: "relative", "data-kanban-item": card.id, children: [
|
|
110
|
+
surface,
|
|
111
|
+
menu(void 0, false)
|
|
112
|
+
] }) : surface;
|
|
57
113
|
}
|
|
58
114
|
const style = {
|
|
59
115
|
transform: CSS.Transform.toString(transform),
|
|
60
116
|
transition
|
|
61
117
|
};
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
118
|
+
const movable = !!menu;
|
|
119
|
+
return (
|
|
120
|
+
// Sprint 25.8.7 item 3 (WCAG 2.5.7): the card's slot holds the card and, beside it, its Move button, never one
|
|
121
|
+
// inside the other (a control inside a control: the card is the drag's button). The card stays the node the drag
|
|
122
|
+
// measures and moves; the button takes the same transform, so it travels with the card as the list sorts.
|
|
123
|
+
/* @__PURE__ */ jsxs("div", { className: "relative", "data-kanban-item": card.id, children: [
|
|
124
|
+
/* @__PURE__ */ jsx(
|
|
125
|
+
"div",
|
|
126
|
+
{
|
|
127
|
+
ref: setNodeRef,
|
|
128
|
+
style,
|
|
129
|
+
...attributes,
|
|
130
|
+
...listeners,
|
|
131
|
+
onClick: onCardClick ? () => onCardClick(card) : void 0,
|
|
132
|
+
onKeyDown: (e) => {
|
|
133
|
+
if (onCardClick && e.key === "Enter") {
|
|
134
|
+
e.preventDefault();
|
|
135
|
+
onCardClick(card);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
listeners?.onKeyDown?.(e);
|
|
139
|
+
},
|
|
140
|
+
"data-kanban-card": card.id,
|
|
141
|
+
className: cn(
|
|
142
|
+
"rounded-[var(--ds-radius-md)] border-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
|
|
143
|
+
"bg-[var(--ds-bg-surface)] p-ds-3 active:cursor-grabbing",
|
|
144
|
+
movable && renderCard && MOVE_ROOM_CARD,
|
|
145
|
+
onCardClick ? "cursor-pointer" : "cursor-grab",
|
|
146
|
+
"shadow-[var(--ds-shadow-button)]",
|
|
147
|
+
"transition-shadow duration-[var(--ds-transition-speed)]",
|
|
148
|
+
FOCUS_RING,
|
|
149
|
+
// no scale on the card left behind: the drag overlay takes its size from it, and 2% wider made the cards
|
|
150
|
+
// below count as "to the right" of it, so a keyboard drag's arrow keys landed on them (Sprint 25.7, C2)
|
|
151
|
+
isDragging && "opacity-30"
|
|
152
|
+
),
|
|
153
|
+
children: renderCard ? renderCard(card) : /* @__PURE__ */ jsx(DefaultCard, { card, moveRoom: movable })
|
|
75
154
|
}
|
|
76
|
-
listeners?.onKeyDown?.(e);
|
|
77
|
-
},
|
|
78
|
-
"data-kanban-card": card.id,
|
|
79
|
-
className: cn(
|
|
80
|
-
"rounded-[var(--ds-radius-md)] border-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
|
|
81
|
-
"bg-[var(--ds-bg-surface)] p-ds-3 active:cursor-grabbing",
|
|
82
|
-
onCardClick ? "cursor-pointer" : "cursor-grab",
|
|
83
|
-
"shadow-[var(--ds-shadow-button)]",
|
|
84
|
-
"transition-shadow duration-[var(--ds-transition-speed)]",
|
|
85
|
-
FOCUS_RING,
|
|
86
|
-
// no scale on the card left behind: the drag overlay takes its size from it, and 2% wider made the cards
|
|
87
|
-
// below count as "to the right" of it, so a keyboard drag's arrow keys landed on them (Sprint 25.7, C2)
|
|
88
|
-
isDragging && "opacity-30"
|
|
89
155
|
),
|
|
90
|
-
|
|
91
|
-
}
|
|
156
|
+
menu && menu(style, isDragging)
|
|
157
|
+
] })
|
|
92
158
|
);
|
|
93
159
|
}
|
|
94
160
|
function ClampedDescription({ children }) {
|
|
@@ -97,31 +163,36 @@ function ClampedDescription({ children }) {
|
|
|
97
163
|
if (!isTruncated) return el;
|
|
98
164
|
return /* @__PURE__ */ jsx(AutoTooltip, { content: children, children: el });
|
|
99
165
|
}
|
|
100
|
-
function TruncTitle({ children }) {
|
|
166
|
+
function TruncTitle({ children, className }) {
|
|
101
167
|
const { ref, isTruncated } = useTextOverflow({ lines: 1 });
|
|
102
|
-
const el = /* @__PURE__ */ jsx("p", { ref, className: "text-sm font-medium text-[var(--ds-text-primary)] truncate", children });
|
|
168
|
+
const el = /* @__PURE__ */ jsx("p", { ref, className: cn("text-sm font-medium text-[var(--ds-text-primary)] truncate", className), children });
|
|
103
169
|
if (!isTruncated) return el;
|
|
104
170
|
return /* @__PURE__ */ jsx(AutoTooltip, { content: children, children: el });
|
|
105
171
|
}
|
|
106
|
-
function DefaultCard({ card }) {
|
|
172
|
+
function DefaultCard({ card, moveRoom }) {
|
|
107
173
|
return /* @__PURE__ */ jsxs("div", { className: "space-y-ds-2", children: [
|
|
108
|
-
/* @__PURE__ */ jsx(TruncTitle, { children: card.title }),
|
|
174
|
+
/* @__PURE__ */ jsx(TruncTitle, { className: moveRoom ? MOVE_ROOM_TITLE : void 0, children: card.title }),
|
|
109
175
|
card.description && /* @__PURE__ */ jsx(ClampedDescription, { children: card.description }),
|
|
110
176
|
card.tags && card.tags.length > 0 && /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-ds-1", children: card.tags.map((tag) => /* @__PURE__ */ jsx(Badge, { variant: "subtle", size: "sm", children: tag }, tag)) })
|
|
111
177
|
] });
|
|
112
178
|
}
|
|
113
179
|
function OverlayCard({
|
|
114
180
|
card,
|
|
115
|
-
renderCard
|
|
181
|
+
renderCard,
|
|
182
|
+
moveRoom
|
|
116
183
|
}) {
|
|
117
|
-
return /* @__PURE__ */
|
|
184
|
+
return /* @__PURE__ */ jsxs(
|
|
118
185
|
"div",
|
|
119
186
|
{
|
|
120
187
|
className: cn(
|
|
121
|
-
"rounded-[var(--ds-radius-md)] border-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
|
|
122
|
-
"bg-[var(--ds-bg-surface)] p-ds-3 shadow-[var(--ds-shadow-card)]"
|
|
188
|
+
"relative rounded-[var(--ds-radius-md)] border-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
|
|
189
|
+
"bg-[var(--ds-bg-surface)] p-ds-3 shadow-[var(--ds-shadow-card)]",
|
|
190
|
+
moveRoom && renderCard && MOVE_ROOM_CARD
|
|
123
191
|
),
|
|
124
|
-
children:
|
|
192
|
+
children: [
|
|
193
|
+
renderCard ? renderCard(card) : /* @__PURE__ */ jsx(DefaultCard, { card, moveRoom }),
|
|
194
|
+
moveRoom && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: cn(MOVE_PLACE, "inline-flex items-center justify-center h-[var(--ds-control-height-sm)] w-[var(--ds-control-height-sm)] text-[var(--ds-text-muted)]"), children: /* @__PURE__ */ jsx(Icon, { name: "more-horizontal", size: 14 }) })
|
|
195
|
+
]
|
|
125
196
|
}
|
|
126
197
|
);
|
|
127
198
|
}
|
|
@@ -194,6 +265,21 @@ function keptHolds(holds, cols) {
|
|
|
194
265
|
var replayHolds = (cols, holds) => holds.reduce(place, cols);
|
|
195
266
|
var isThenable = (value) => value != null && typeof value.then === "function";
|
|
196
267
|
var placeWords = (at) => `${at.col.title}, position ${at.index + 1} of ${at.col.cards.length}`;
|
|
268
|
+
var overLimit = (col) => col.limit != null && col.cards.length > col.limit;
|
|
269
|
+
var overLimitWords = (col) => overLimit(col) ? ` ${col.title} is over its limit of ${col.limit}.` : "";
|
|
270
|
+
var countWords = (count, limit) => `${count} ${count === 1 ? "card" : "cards"}, ${count > limit ? `over the limit of ${limit}` : `limit ${limit}`}`;
|
|
271
|
+
function ColumnCount({ count, limit }) {
|
|
272
|
+
if (limit == null) return /* @__PURE__ */ jsx("span", { className: "text-xs text-[var(--ds-text-muted)] tabular-nums", children: count });
|
|
273
|
+
return /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-ds-1 text-xs text-[var(--ds-text-muted)] tabular-nums", children: [
|
|
274
|
+
/* @__PURE__ */ jsxs("span", { "aria-hidden": "true", children: [
|
|
275
|
+
count,
|
|
276
|
+
"/",
|
|
277
|
+
limit
|
|
278
|
+
] }),
|
|
279
|
+
count > limit && /* @__PURE__ */ jsx("span", { "aria-hidden": "true", "data-kanban-over-limit": "", className: "inline-flex shrink-0 text-[var(--ds-status-warning)]", children: /* @__PURE__ */ jsx(Icon, { name: "warning", size: 14 }) }),
|
|
280
|
+
/* @__PURE__ */ jsx("span", { className: "sr-only", children: countWords(count, limit) })
|
|
281
|
+
] });
|
|
282
|
+
}
|
|
197
283
|
var MOVE_INSTRUCTIONS = "Press Space to pick up the card. Use the Left and Right arrow keys to move it between columns, and the Up and Down arrow keys to move it within a column. Press Space again to drop it, or Escape to cancel.";
|
|
198
284
|
function BoardColumn({
|
|
199
285
|
col,
|
|
@@ -228,7 +314,7 @@ function BoardColumn({
|
|
|
228
314
|
}
|
|
229
315
|
),
|
|
230
316
|
/* @__PURE__ */ jsx("span", { className: "text-sm font-[var(--ds-weight-heading)] text-[var(--ds-text-primary)]", children: col.title }),
|
|
231
|
-
/* @__PURE__ */ jsx(
|
|
317
|
+
/* @__PURE__ */ jsx(ColumnCount, { count: col.cards.length, limit: col.limit })
|
|
232
318
|
] }),
|
|
233
319
|
/* @__PURE__ */ jsx(Icon, { name: "plus", size: 14, className: "text-[var(--ds-text-muted)]" })
|
|
234
320
|
] }),
|
|
@@ -266,6 +352,7 @@ function KanbanBoard({
|
|
|
266
352
|
readOnly,
|
|
267
353
|
renderCard,
|
|
268
354
|
onCardClick,
|
|
355
|
+
cardActions,
|
|
269
356
|
className
|
|
270
357
|
}) {
|
|
271
358
|
useFramesItself();
|
|
@@ -329,13 +416,13 @@ function KanbanBoard({
|
|
|
329
416
|
const fill = useFillHeight(rootRef);
|
|
330
417
|
const inPane = useContext(FillContext);
|
|
331
418
|
const filled = fill != null || inPane;
|
|
419
|
+
const cardPart = (attr, id) => [...rootRef.current?.querySelectorAll(`[${attr}]`) ?? []].find((el) => el.getAttribute(attr) === id);
|
|
332
420
|
const refocus = useRef(null);
|
|
333
421
|
useLayoutEffect(() => {
|
|
334
|
-
const
|
|
335
|
-
if (
|
|
422
|
+
const target = refocus.current;
|
|
423
|
+
if (target == null) return;
|
|
336
424
|
refocus.current = null;
|
|
337
|
-
|
|
338
|
-
[...cards].find((el) => el.getAttribute("data-kanban-card") === id)?.focus();
|
|
425
|
+
cardPart(target.part === "move" ? "data-kanban-move" : "data-kanban-card", target.id)?.focus();
|
|
339
426
|
});
|
|
340
427
|
const holdKey = useRef(0);
|
|
341
428
|
const sensors = useSensors(
|
|
@@ -389,8 +476,11 @@ function KanbanBoard({
|
|
|
389
476
|
const back = locate(shown, hold.cardId);
|
|
390
477
|
const card = back?.col.cards[back.index];
|
|
391
478
|
const focused = document.activeElement;
|
|
392
|
-
|
|
393
|
-
|
|
479
|
+
const inSlot = !!focused && !!rootRef.current?.contains(focused) && focused.closest?.("[data-kanban-item]")?.getAttribute("data-kanban-item") === hold.cardId;
|
|
480
|
+
const menuOpen = cardPart("data-kanban-move", hold.cardId)?.getAttribute("aria-expanded") === "true";
|
|
481
|
+
if (inSlot) refocus.current = { id: hold.cardId, part: focused?.hasAttribute("data-kanban-move") ? "move" : "card" };
|
|
482
|
+
else if (menuOpen && locate(replayHolds(columnsRef.current, holdsRef.current), hold.cardId)?.col.id !== back?.col.id) {
|
|
483
|
+
refocus.current = { id: hold.cardId, part: "move" };
|
|
394
484
|
}
|
|
395
485
|
setHolds(rest);
|
|
396
486
|
if (previewRef.current && activeRef.current) setPreview(rebase(shown, previewRef.current, activeRef.current));
|
|
@@ -406,32 +496,20 @@ function KanbanBoard({
|
|
|
406
496
|
const next = holdsRef.current.map((h) => h === hold ? { ...h, inFlight, layout: inFlight > 0 ? h.layout : layoutOf(columnsRef.current) } : h);
|
|
407
497
|
setHolds(inFlight > 0 ? next : keptHolds(next, columnsRef.current));
|
|
408
498
|
};
|
|
409
|
-
const
|
|
410
|
-
const
|
|
411
|
-
|
|
412
|
-
const cols = previewRef.current ?? committed;
|
|
413
|
-
activeRef.current = null;
|
|
414
|
-
setActiveCard(null);
|
|
415
|
-
setPreview(null);
|
|
416
|
-
const final = over ? landing(cols, activeId, String(over.id)) : committed;
|
|
417
|
-
const before = locate(committed, activeId);
|
|
418
|
-
const after = locate(final, activeId);
|
|
419
|
-
if (!before || !after || before.col.id === after.col.id && before.index === after.index) {
|
|
420
|
-
if (before) said.current = `Dropped ${before.col.cards[before.index].title} back in ${placeWords(before)}.`;
|
|
421
|
-
return;
|
|
422
|
-
}
|
|
423
|
-
said.current = `Dropped ${after.col.cards[after.index].title} in ${placeWords(after)}.`;
|
|
499
|
+
const commit = (final, cardId) => {
|
|
500
|
+
const after = locate(final, cardId);
|
|
501
|
+
if (!after) return;
|
|
424
502
|
const key = ++holdKey.current;
|
|
425
|
-
const prev = holdsRef.current.find((h) => h.cardId ===
|
|
503
|
+
const prev = holdsRef.current.find((h) => h.cardId === cardId);
|
|
426
504
|
const hold = {
|
|
427
|
-
...placementIn(final,
|
|
505
|
+
...placementIn(final, cardId),
|
|
428
506
|
key,
|
|
429
507
|
inFlight: prev?.inFlight ?? 0,
|
|
430
|
-
origin: prev?.origin ?? placementIn(columnsProp,
|
|
508
|
+
origin: prev?.origin ?? placementIn(columnsProp, cardId) ?? placementIn(committed, cardId),
|
|
431
509
|
earlier: prev ? [...prev.earlier, { cardId: prev.cardId, toColumnId: prev.toColumnId, beforeId: prev.beforeId, index: prev.index }] : [],
|
|
432
510
|
layout: layoutOf(columnsProp)
|
|
433
511
|
};
|
|
434
|
-
setHolds([...holdsRef.current.filter((h) => h.cardId !==
|
|
512
|
+
setHolds([...holdsRef.current.filter((h) => h.cardId !== cardId), hold]);
|
|
435
513
|
const saves = [];
|
|
436
514
|
try {
|
|
437
515
|
for (const result of [onColumnsChange?.(final), onCardMove?.(after.col.cards[after.index], after.col.id, after.index)]) {
|
|
@@ -443,8 +521,51 @@ function KanbanBoard({
|
|
|
443
521
|
}
|
|
444
522
|
if (!saves.length) return;
|
|
445
523
|
setHolds(holdsRef.current.map((h) => h.key === key ? { ...h, inFlight: h.inFlight + 1 } : h));
|
|
446
|
-
Promise.allSettled(saves).then((results) => settle(
|
|
524
|
+
Promise.allSettled(saves).then((results) => settle(cardId, key, results.every((r) => r.status === "fulfilled")));
|
|
525
|
+
};
|
|
526
|
+
const handleDragEnd = (event) => {
|
|
527
|
+
const { active, over } = event;
|
|
528
|
+
const activeId = String(active.id);
|
|
529
|
+
const cols = previewRef.current ?? committed;
|
|
530
|
+
activeRef.current = null;
|
|
531
|
+
setActiveCard(null);
|
|
532
|
+
setPreview(null);
|
|
533
|
+
const final = over ? landing(cols, activeId, String(over.id)) : committed;
|
|
534
|
+
const before = locate(committed, activeId);
|
|
535
|
+
const after = locate(final, activeId);
|
|
536
|
+
if (!before || !after || before.col.id === after.col.id && before.index === after.index) {
|
|
537
|
+
if (before) said.current = `Dropped ${before.col.cards[before.index].title} back in ${placeWords(before)}.`;
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
said.current = `Dropped ${after.col.cards[after.index].title} in ${placeWords(after)}.${overLimitWords(after.col)}`;
|
|
541
|
+
commit(final, activeId);
|
|
542
|
+
};
|
|
543
|
+
const moveFromMenu = (cardId, toColumnId, step = 0) => {
|
|
544
|
+
const at = locate(committed, cardId);
|
|
545
|
+
const to = committed.find((col) => col.id === toColumnId);
|
|
546
|
+
if (!at || !to) return;
|
|
547
|
+
const index = to.id === at.col.id ? at.index + step : 0;
|
|
548
|
+
const beforeId = to.cards.filter((c) => c.id !== cardId)[index]?.id ?? null;
|
|
549
|
+
const final = place(committed, { cardId, toColumnId, beforeId, index });
|
|
550
|
+
const after = locate(final, cardId);
|
|
551
|
+
if (!after || after.col.id === at.col.id && after.index === at.index) return;
|
|
552
|
+
noticeParts.current = [];
|
|
553
|
+
setNotice(`Moved ${at.col.cards[at.index].title} to ${placeWords(after)}.${overLimitWords(after.col)}`);
|
|
554
|
+
commit(final, cardId);
|
|
555
|
+
refocus.current = { id: cardId, part: "card" };
|
|
556
|
+
};
|
|
557
|
+
const movesFor = (col, index) => {
|
|
558
|
+
if (readOnly) return [];
|
|
559
|
+
const others = columns.filter((c) => c.id !== col.id).map((c) => ({ label: `Move to ${c.title}`, to: c.id, step: 0 }));
|
|
560
|
+
const steps = [
|
|
561
|
+
...index > 0 ? [{ label: "Move up", to: col.id, step: -1 }] : [],
|
|
562
|
+
...index < col.cards.length - 1 ? [{ label: "Move down", to: col.id, step: 1 }] : []
|
|
563
|
+
];
|
|
564
|
+
return [...others, ...steps];
|
|
447
565
|
};
|
|
566
|
+
const activeAt = activeCard ? locate(columns, activeCard.id) : null;
|
|
567
|
+
const activeMovable = !!activeAt && cardMenuOf(activeAt.col.cards[activeAt.index], menuOf(movesFor(activeAt.col, activeAt.index), () => {
|
|
568
|
+
}), cardActions).length > 0;
|
|
448
569
|
const handleDragCancel = (event) => {
|
|
449
570
|
activeRef.current = null;
|
|
450
571
|
setActiveCard(null);
|
|
@@ -464,19 +585,22 @@ function KanbanBoard({
|
|
|
464
585
|
onDragCancel: handleDragCancel,
|
|
465
586
|
children: [
|
|
466
587
|
/* @__PURE__ */ jsxs("div", { ref: setRoot, "data-fill": fill != null ? "" : void 0, className: cn("relative flex gap-ds-4 overflow-x-auto pb-ds-2 scroll-px-[4px] snap-x snap-mandatory sm:snap-none", inPane && "h-full min-h-0", className), style: fill != null ? { height: fill } : void 0, children: [
|
|
467
|
-
columns.map((col) => /* @__PURE__ */ jsx(BoardColumn, { col, readOnly, filled, children: col.cards.map((card) => /* @__PURE__ */ jsx(
|
|
588
|
+
columns.map((col) => /* @__PURE__ */ jsx(BoardColumn, { col, readOnly, filled, children: col.cards.map((card, index) => /* @__PURE__ */ jsx(
|
|
468
589
|
SortableCard,
|
|
469
590
|
{
|
|
470
591
|
card,
|
|
471
592
|
renderCard,
|
|
472
593
|
onCardClick,
|
|
473
|
-
readOnly
|
|
594
|
+
readOnly,
|
|
595
|
+
moves: movesFor(col, index),
|
|
596
|
+
onMove: moveFromMenu,
|
|
597
|
+
cardActions
|
|
474
598
|
},
|
|
475
599
|
card.id
|
|
476
600
|
)) }, col.id)),
|
|
477
601
|
/* @__PURE__ */ jsx("div", { role: "status", className: "sr-only", children: notice })
|
|
478
602
|
] }),
|
|
479
|
-
/* @__PURE__ */ jsx(DragOverlay, { children: activeCard && /* @__PURE__ */ jsx(OverlayCard, { card: activeCard, renderCard }) })
|
|
603
|
+
/* @__PURE__ */ jsx(DragOverlay, { children: activeCard && /* @__PURE__ */ jsx(OverlayCard, { card: activeCard, renderCard, moveRoom: activeMovable }) })
|
|
480
604
|
]
|
|
481
605
|
}
|
|
482
606
|
);
|