@crisp-ui-kit/crisp 0.12.0 → 0.13.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/board.css +7 -0
- package/dist/cjs/components/tabletoolbar/tabletoolbar.js +13 -1
- package/dist/cjs/product/board/board.js +68 -23
- package/dist/cjs/product/board/board.recipe.js +8 -1
- package/dist/esm/components/tabletoolbar/tabletoolbar.js +13 -1
- package/dist/esm/product/board/board.js +68 -23
- package/dist/esm/product/board/board.recipe.js +8 -1
- package/dist/styles.css +7 -0
- package/dist/types/product/board/board.d.ts +9 -1
- package/package.json +1 -1
package/dist/board.css
CHANGED
|
@@ -14,6 +14,13 @@
|
|
|
14
14
|
& .board-card { display: flex; flex-direction: column; gap: var(--space-2); box-sizing: border-box; padding: var(--space-2-5) var(--space-3); border-radius: var(--radius-lg); background: var(--bg-surface); box-shadow: inset 0 0 0 1px var(--border-subtle), 0 1px 2px rgba(0,0,0,0.04); cursor: grab; }
|
|
15
15
|
& .board-card:active { cursor: grabbing; }
|
|
16
16
|
& .board-card[data-dragging] { opacity: 0.4; }
|
|
17
|
+
& .board-card:focus { outline: none; }
|
|
18
|
+
& .board-card:focus-visible { outline: 2px solid var(--bg-brand-solid); outline-offset: 1px; }
|
|
19
|
+
/* announced, never drawn */
|
|
20
|
+
& .board-live {
|
|
21
|
+
position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
|
|
22
|
+
overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
|
|
23
|
+
}
|
|
17
24
|
}
|
|
18
25
|
|
|
19
26
|
@media (prefers-reduced-motion: reduce) {
|
|
@@ -62,6 +62,13 @@ const asText = (v) => {
|
|
|
62
62
|
return "";
|
|
63
63
|
if (Array.isArray(v))
|
|
64
64
|
return v.join(", ");
|
|
65
|
+
// Before the generic object branch, which would look for `.name`, find none
|
|
66
|
+
// and return "" — turning every Date into the empty string (#185). The ISO
|
|
67
|
+
// date is stable and locale-independent, so `contains "2026-06"` works; it
|
|
68
|
+
// will not match "16/06/2026", and a date-aware operator set is Attio's
|
|
69
|
+
// date-picker UI, which has to be measured before it can be built.
|
|
70
|
+
if (v instanceof Date)
|
|
71
|
+
return Number.isNaN(v.getTime()) ? "" : v.toISOString().slice(0, 10);
|
|
65
72
|
if (typeof v === "object") {
|
|
66
73
|
const name = v.name;
|
|
67
74
|
return typeof name === "string" ? name : "";
|
|
@@ -104,7 +111,12 @@ function applySorts(rows, sorts, get = (row, key) => row[key]) {
|
|
|
104
111
|
const av = get(a, s.key);
|
|
105
112
|
const bv = get(b, s.key);
|
|
106
113
|
let d = 0;
|
|
107
|
-
|
|
114
|
+
// Dates compare by instant, the way two numbers do. Falling through to
|
|
115
|
+
// text here is what made a date column sort identically ascending and
|
|
116
|
+
// descending — every comparison was "" against "" (#185).
|
|
117
|
+
if (av instanceof Date && bv instanceof Date)
|
|
118
|
+
d = av.getTime() - bv.getTime();
|
|
119
|
+
else if (typeof av === "number" && typeof bv === "number")
|
|
108
120
|
d = av - bv;
|
|
109
121
|
else
|
|
110
122
|
d = asText(av).localeCompare(asText(bv));
|
|
@@ -43,36 +43,81 @@ const board_recipe_1 = require("./board.recipe");
|
|
|
43
43
|
* (the parent owns the cards, so the move is a controlled state update).
|
|
44
44
|
* Measured from Attio's Deals pipeline.
|
|
45
45
|
*/
|
|
46
|
-
function Board({ columns, cards, onCardsChange, renderCard, onNewCard, newCardLabel = "New", className, ...rest }) {
|
|
46
|
+
function Board({ columns, cards, onCardsChange, renderCard, onNewCard, newCardLabel = "New", getCardLabel, announceMove = (column, card) => card ? `${card} moved to ${column}` : `Moved to ${column}`, className, ...rest }) {
|
|
47
47
|
const [dragId, setDragId] = React.useState(null);
|
|
48
48
|
const [overCol, setOverCol] = React.useState(null);
|
|
49
|
+
const [announcement, setAnnouncement] = React.useState("");
|
|
50
|
+
// the card a keyboard move just sent to another column; React unmounts and
|
|
51
|
+
// remounts it under the new parent, so focus has to be put back by hand
|
|
52
|
+
const [refocusId, setRefocusId] = React.useState(null);
|
|
53
|
+
const rootRef = React.useRef(null);
|
|
49
54
|
const move = (cardId, toColumn) => {
|
|
50
55
|
const card = cards.find((c) => c.id === cardId);
|
|
51
56
|
if (!card || card.columnId === toColumn)
|
|
52
57
|
return;
|
|
53
58
|
onCardsChange?.(cards.map((c) => (c.id === cardId ? { ...c, columnId: toColumn } : c)));
|
|
54
59
|
};
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
return
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
60
|
+
React.useEffect(() => {
|
|
61
|
+
if (!refocusId)
|
|
62
|
+
return;
|
|
63
|
+
rootRef.current
|
|
64
|
+
?.querySelector(`[data-card-id="${CSS.escape(refocusId)}"]`)
|
|
65
|
+
?.focus();
|
|
66
|
+
setRefocusId(null);
|
|
67
|
+
}, [refocusId]);
|
|
68
|
+
/**
|
|
69
|
+
* Native HTML5 drag-and-drop is pointer-only by specification, so the board's
|
|
70
|
+
* primary action needs its own key model or it has none at all.
|
|
71
|
+
*
|
|
72
|
+
* Ctrl/Cmd + Left/Right rather than plain arrows: a screen reader owns the
|
|
73
|
+
* bare arrow keys in browse mode, and a board scrolls horizontally, so plain
|
|
74
|
+
* arrows on a focused card would fight both.
|
|
75
|
+
*/
|
|
76
|
+
const moveByKey = (card, delta) => {
|
|
77
|
+
const from = columns.findIndex((c) => c.id === card.columnId);
|
|
78
|
+
const to = columns[from + delta];
|
|
79
|
+
if (from < 0 || !to)
|
|
80
|
+
return false;
|
|
81
|
+
move(card.id, to.id);
|
|
82
|
+
setAnnouncement(announceMove(to.title, getCardLabel?.(card)));
|
|
83
|
+
setRefocusId(card.id);
|
|
84
|
+
return true;
|
|
85
|
+
};
|
|
86
|
+
return ((0, jsx_runtime_1.jsxs)("div", { ref: rootRef, className: (0, board_recipe_1.boardRecipe)({ className }), ...rest, children: [columns.map((col) => {
|
|
87
|
+
const colCards = cards.filter((c) => c.columnId === col.id);
|
|
88
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: "board-col", "data-over": overCol === col.id ? "" : undefined, onDragOver: (e) => {
|
|
89
|
+
if (!dragId)
|
|
90
|
+
return;
|
|
91
|
+
e.preventDefault();
|
|
92
|
+
setOverCol(col.id);
|
|
93
|
+
}, onDragLeave: (e) => {
|
|
94
|
+
if (!e.currentTarget.contains(e.relatedTarget)) {
|
|
95
|
+
setOverCol((v) => (v === col.id ? null : v));
|
|
96
|
+
}
|
|
97
|
+
}, onDrop: (e) => {
|
|
98
|
+
e.preventDefault();
|
|
99
|
+
if (dragId)
|
|
100
|
+
move(dragId, col.id);
|
|
101
|
+
setDragId(null);
|
|
102
|
+
setOverCol(null);
|
|
103
|
+
}, children: [(0, jsx_runtime_1.jsxs)("div", { className: "board-col-header", children: [col.accent && ((0, jsx_runtime_1.jsx)("span", { className: "board-col-dot", style: { background: col.accent } })), (0, jsx_runtime_1.jsx)("span", { className: "board-col-title", children: col.title }), (0, jsx_runtime_1.jsx)("span", { className: "board-col-count", children: colCards.length }), onNewCard && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: "board-col-add", "aria-label": `Add to ${col.title}`, onClick: () => onNewCard(col.id), children: "+" }))] }), (0, jsx_runtime_1.jsxs)("div", { className: "board-col-body", role: "list", "aria-label": col.title, children: [onNewCard && ((0, jsx_runtime_1.jsxs)("button", { type: "button", className: "board-newcard", onClick: () => onNewCard(col.id), children: ["+ ", newCardLabel] })), colCards.map((card) => ((0, jsx_runtime_1.jsx)("div", { className: "board-card", role: "listitem",
|
|
104
|
+
// biome-ignore lint/a11y/noNoninteractiveTabindex: the rule assumes a listitem cannot be operated. This one carries the board's only keyboard path to move a card, so it has to be reachable by Tab
|
|
105
|
+
tabIndex: 0, "aria-label": getCardLabel?.(card), "data-card-id": card.id, draggable: true, "data-dragging": dragId === card.id ? "" : undefined, onDragStart: () => setDragId(card.id), onDragEnd: () => {
|
|
106
|
+
setDragId(null);
|
|
107
|
+
setOverCol(null);
|
|
108
|
+
}, onKeyDown: (e) => {
|
|
109
|
+
if (!(e.ctrlKey || e.metaKey))
|
|
110
|
+
return;
|
|
111
|
+
const delta = e.key === "ArrowLeft"
|
|
112
|
+
? -1
|
|
113
|
+
: e.key === "ArrowRight"
|
|
114
|
+
? 1
|
|
115
|
+
: 0;
|
|
116
|
+
// only swallow the key when the move actually happens, so
|
|
117
|
+
// Ctrl+Right at the last column still reaches the browser
|
|
118
|
+
if (delta && moveByKey(card, delta))
|
|
119
|
+
e.preventDefault();
|
|
120
|
+
}, children: renderCard(card) }, card.id)))] })] }, col.id));
|
|
121
|
+
}), (0, jsx_runtime_1.jsx)("div", { className: "board-live", role: "status", "aria-live": "polite", children: announcement })] }));
|
|
77
122
|
}
|
|
78
123
|
Board.displayName = "Board";
|
|
@@ -22,7 +22,14 @@ const config = (0, core_1.defineRecipe)({
|
|
|
22
22
|
& .board-newcard:hover { background: color-mix(in srgb, ${tokens_1.contract.fgPrimary} 7%, transparent); color: ${tokens_1.contract.fgPrimary}; }
|
|
23
23
|
& .board-card { display: flex; flex-direction: column; gap: ${tokens_1.contract.space["2"]}; box-sizing: border-box; padding: ${tokens_1.contract.space["2.5"]} ${tokens_1.contract.space["3"]}; border-radius: ${tokens_1.contract.radius.lg}; background: ${tokens_1.contract.bgSurface}; box-shadow: inset 0 0 0 1px ${tokens_1.contract.borderSubtle}, 0 1px 2px rgba(0,0,0,0.04); cursor: grab; }
|
|
24
24
|
& .board-card:active { cursor: grabbing; }
|
|
25
|
-
& .board-card[data-dragging] { opacity: 0.4; }
|
|
25
|
+
& .board-card[data-dragging] { opacity: 0.4; }
|
|
26
|
+
& .board-card:focus { outline: none; }
|
|
27
|
+
& .board-card:focus-visible { outline: 2px solid ${tokens_1.contract.bgBrandSolid}; outline-offset: 1px; }
|
|
28
|
+
/* announced, never drawn */
|
|
29
|
+
& .board-live {
|
|
30
|
+
position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
|
|
31
|
+
overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
|
|
32
|
+
}`,
|
|
26
33
|
variants: {},
|
|
27
34
|
defaultVariants: {},
|
|
28
35
|
compoundVariants: [],
|
|
@@ -24,6 +24,13 @@ const asText = (v) => {
|
|
|
24
24
|
return "";
|
|
25
25
|
if (Array.isArray(v))
|
|
26
26
|
return v.join(", ");
|
|
27
|
+
// Before the generic object branch, which would look for `.name`, find none
|
|
28
|
+
// and return "" — turning every Date into the empty string (#185). The ISO
|
|
29
|
+
// date is stable and locale-independent, so `contains "2026-06"` works; it
|
|
30
|
+
// will not match "16/06/2026", and a date-aware operator set is Attio's
|
|
31
|
+
// date-picker UI, which has to be measured before it can be built.
|
|
32
|
+
if (v instanceof Date)
|
|
33
|
+
return Number.isNaN(v.getTime()) ? "" : v.toISOString().slice(0, 10);
|
|
27
34
|
if (typeof v === "object") {
|
|
28
35
|
const name = v.name;
|
|
29
36
|
return typeof name === "string" ? name : "";
|
|
@@ -66,7 +73,12 @@ export function applySorts(rows, sorts, get = (row, key) => row[key]) {
|
|
|
66
73
|
const av = get(a, s.key);
|
|
67
74
|
const bv = get(b, s.key);
|
|
68
75
|
let d = 0;
|
|
69
|
-
|
|
76
|
+
// Dates compare by instant, the way two numbers do. Falling through to
|
|
77
|
+
// text here is what made a date column sort identically ascending and
|
|
78
|
+
// descending — every comparison was "" against "" (#185).
|
|
79
|
+
if (av instanceof Date && bv instanceof Date)
|
|
80
|
+
d = av.getTime() - bv.getTime();
|
|
81
|
+
else if (typeof av === "number" && typeof bv === "number")
|
|
70
82
|
d = av - bv;
|
|
71
83
|
else
|
|
72
84
|
d = asText(av).localeCompare(asText(bv));
|
|
@@ -7,36 +7,81 @@ import { boardRecipe } from "./board.recipe.js";
|
|
|
7
7
|
* (the parent owns the cards, so the move is a controlled state update).
|
|
8
8
|
* Measured from Attio's Deals pipeline.
|
|
9
9
|
*/
|
|
10
|
-
export function Board({ columns, cards, onCardsChange, renderCard, onNewCard, newCardLabel = "New", className, ...rest }) {
|
|
10
|
+
export function Board({ columns, cards, onCardsChange, renderCard, onNewCard, newCardLabel = "New", getCardLabel, announceMove = (column, card) => card ? `${card} moved to ${column}` : `Moved to ${column}`, className, ...rest }) {
|
|
11
11
|
const [dragId, setDragId] = React.useState(null);
|
|
12
12
|
const [overCol, setOverCol] = React.useState(null);
|
|
13
|
+
const [announcement, setAnnouncement] = React.useState("");
|
|
14
|
+
// the card a keyboard move just sent to another column; React unmounts and
|
|
15
|
+
// remounts it under the new parent, so focus has to be put back by hand
|
|
16
|
+
const [refocusId, setRefocusId] = React.useState(null);
|
|
17
|
+
const rootRef = React.useRef(null);
|
|
13
18
|
const move = (cardId, toColumn) => {
|
|
14
19
|
const card = cards.find((c) => c.id === cardId);
|
|
15
20
|
if (!card || card.columnId === toColumn)
|
|
16
21
|
return;
|
|
17
22
|
onCardsChange?.(cards.map((c) => (c.id === cardId ? { ...c, columnId: toColumn } : c)));
|
|
18
23
|
};
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
return
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
24
|
+
React.useEffect(() => {
|
|
25
|
+
if (!refocusId)
|
|
26
|
+
return;
|
|
27
|
+
rootRef.current
|
|
28
|
+
?.querySelector(`[data-card-id="${CSS.escape(refocusId)}"]`)
|
|
29
|
+
?.focus();
|
|
30
|
+
setRefocusId(null);
|
|
31
|
+
}, [refocusId]);
|
|
32
|
+
/**
|
|
33
|
+
* Native HTML5 drag-and-drop is pointer-only by specification, so the board's
|
|
34
|
+
* primary action needs its own key model or it has none at all.
|
|
35
|
+
*
|
|
36
|
+
* Ctrl/Cmd + Left/Right rather than plain arrows: a screen reader owns the
|
|
37
|
+
* bare arrow keys in browse mode, and a board scrolls horizontally, so plain
|
|
38
|
+
* arrows on a focused card would fight both.
|
|
39
|
+
*/
|
|
40
|
+
const moveByKey = (card, delta) => {
|
|
41
|
+
const from = columns.findIndex((c) => c.id === card.columnId);
|
|
42
|
+
const to = columns[from + delta];
|
|
43
|
+
if (from < 0 || !to)
|
|
44
|
+
return false;
|
|
45
|
+
move(card.id, to.id);
|
|
46
|
+
setAnnouncement(announceMove(to.title, getCardLabel?.(card)));
|
|
47
|
+
setRefocusId(card.id);
|
|
48
|
+
return true;
|
|
49
|
+
};
|
|
50
|
+
return (_jsxs("div", { ref: rootRef, className: boardRecipe({ className }), ...rest, children: [columns.map((col) => {
|
|
51
|
+
const colCards = cards.filter((c) => c.columnId === col.id);
|
|
52
|
+
return (_jsxs("div", { className: "board-col", "data-over": overCol === col.id ? "" : undefined, onDragOver: (e) => {
|
|
53
|
+
if (!dragId)
|
|
54
|
+
return;
|
|
55
|
+
e.preventDefault();
|
|
56
|
+
setOverCol(col.id);
|
|
57
|
+
}, onDragLeave: (e) => {
|
|
58
|
+
if (!e.currentTarget.contains(e.relatedTarget)) {
|
|
59
|
+
setOverCol((v) => (v === col.id ? null : v));
|
|
60
|
+
}
|
|
61
|
+
}, onDrop: (e) => {
|
|
62
|
+
e.preventDefault();
|
|
63
|
+
if (dragId)
|
|
64
|
+
move(dragId, col.id);
|
|
65
|
+
setDragId(null);
|
|
66
|
+
setOverCol(null);
|
|
67
|
+
}, children: [_jsxs("div", { className: "board-col-header", children: [col.accent && (_jsx("span", { className: "board-col-dot", style: { background: col.accent } })), _jsx("span", { className: "board-col-title", children: col.title }), _jsx("span", { className: "board-col-count", children: colCards.length }), onNewCard && (_jsx("button", { type: "button", className: "board-col-add", "aria-label": `Add to ${col.title}`, onClick: () => onNewCard(col.id), children: "+" }))] }), _jsxs("div", { className: "board-col-body", role: "list", "aria-label": col.title, children: [onNewCard && (_jsxs("button", { type: "button", className: "board-newcard", onClick: () => onNewCard(col.id), children: ["+ ", newCardLabel] })), colCards.map((card) => (_jsx("div", { className: "board-card", role: "listitem",
|
|
68
|
+
// biome-ignore lint/a11y/noNoninteractiveTabindex: the rule assumes a listitem cannot be operated. This one carries the board's only keyboard path to move a card, so it has to be reachable by Tab
|
|
69
|
+
tabIndex: 0, "aria-label": getCardLabel?.(card), "data-card-id": card.id, draggable: true, "data-dragging": dragId === card.id ? "" : undefined, onDragStart: () => setDragId(card.id), onDragEnd: () => {
|
|
70
|
+
setDragId(null);
|
|
71
|
+
setOverCol(null);
|
|
72
|
+
}, onKeyDown: (e) => {
|
|
73
|
+
if (!(e.ctrlKey || e.metaKey))
|
|
74
|
+
return;
|
|
75
|
+
const delta = e.key === "ArrowLeft"
|
|
76
|
+
? -1
|
|
77
|
+
: e.key === "ArrowRight"
|
|
78
|
+
? 1
|
|
79
|
+
: 0;
|
|
80
|
+
// only swallow the key when the move actually happens, so
|
|
81
|
+
// Ctrl+Right at the last column still reaches the browser
|
|
82
|
+
if (delta && moveByKey(card, delta))
|
|
83
|
+
e.preventDefault();
|
|
84
|
+
}, children: renderCard(card) }, card.id)))] })] }, col.id));
|
|
85
|
+
}), _jsx("div", { className: "board-live", role: "status", "aria-live": "polite", children: announcement })] }));
|
|
41
86
|
}
|
|
42
87
|
Board.displayName = "Board";
|
|
@@ -19,7 +19,14 @@ const config = defineRecipe({
|
|
|
19
19
|
& .board-newcard:hover { background: color-mix(in srgb, ${c.fgPrimary} 7%, transparent); color: ${c.fgPrimary}; }
|
|
20
20
|
& .board-card { display: flex; flex-direction: column; gap: ${c.space["2"]}; box-sizing: border-box; padding: ${c.space["2.5"]} ${c.space["3"]}; border-radius: ${c.radius.lg}; background: ${c.bgSurface}; box-shadow: inset 0 0 0 1px ${c.borderSubtle}, 0 1px 2px rgba(0,0,0,0.04); cursor: grab; }
|
|
21
21
|
& .board-card:active { cursor: grabbing; }
|
|
22
|
-
& .board-card[data-dragging] { opacity: 0.4; }
|
|
22
|
+
& .board-card[data-dragging] { opacity: 0.4; }
|
|
23
|
+
& .board-card:focus { outline: none; }
|
|
24
|
+
& .board-card:focus-visible { outline: 2px solid ${c.bgBrandSolid}; outline-offset: 1px; }
|
|
25
|
+
/* announced, never drawn */
|
|
26
|
+
& .board-live {
|
|
27
|
+
position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
|
|
28
|
+
overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
|
|
29
|
+
}`,
|
|
23
30
|
variants: {},
|
|
24
31
|
defaultVariants: {},
|
|
25
32
|
compoundVariants: [],
|
package/dist/styles.css
CHANGED
|
@@ -1950,6 +1950,13 @@
|
|
|
1950
1950
|
& .board-card { display: flex; flex-direction: column; gap: var(--space-2); box-sizing: border-box; padding: var(--space-2-5) var(--space-3); border-radius: var(--radius-lg); background: var(--bg-surface); box-shadow: inset 0 0 0 1px var(--border-subtle), 0 1px 2px rgba(0,0,0,0.04); cursor: grab; }
|
|
1951
1951
|
& .board-card:active { cursor: grabbing; }
|
|
1952
1952
|
& .board-card[data-dragging] { opacity: 0.4; }
|
|
1953
|
+
& .board-card:focus { outline: none; }
|
|
1954
|
+
& .board-card:focus-visible { outline: 2px solid var(--bg-brand-solid); outline-offset: 1px; }
|
|
1955
|
+
/* announced, never drawn */
|
|
1956
|
+
& .board-live {
|
|
1957
|
+
position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
|
|
1958
|
+
overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
|
|
1959
|
+
}
|
|
1953
1960
|
}
|
|
1954
1961
|
|
|
1955
1962
|
@media (prefers-reduced-motion: reduce) {
|
|
@@ -18,6 +18,14 @@ export type BoardProps<T extends BoardCard> = Omit<React.ComponentPropsWithoutRe
|
|
|
18
18
|
/** When set, each column shows a "+ {newCardLabel}" button and its header +. */
|
|
19
19
|
onNewCard?: (columnId: string) => void;
|
|
20
20
|
newCardLabel?: string;
|
|
21
|
+
/**
|
|
22
|
+
* Accessible name for a card. Also used in the move announcement. Without it
|
|
23
|
+
* a card is announced by whatever `renderCard` puts in it, which is usually
|
|
24
|
+
* right but is not something the component can guarantee.
|
|
25
|
+
*/
|
|
26
|
+
getCardLabel?: (card: T) => string;
|
|
27
|
+
/** What a screen reader hears after a keyboard move. */
|
|
28
|
+
announceMove?: (columnTitle: string, cardLabel?: string) => string;
|
|
21
29
|
className?: string;
|
|
22
30
|
};
|
|
23
31
|
/**
|
|
@@ -26,7 +34,7 @@ export type BoardProps<T extends BoardCard> = Omit<React.ComponentPropsWithoutRe
|
|
|
26
34
|
* (the parent owns the cards, so the move is a controlled state update).
|
|
27
35
|
* Measured from Attio's Deals pipeline.
|
|
28
36
|
*/
|
|
29
|
-
export declare function Board<T extends BoardCard>({ columns, cards, onCardsChange, renderCard, onNewCard, newCardLabel, className, ...rest }: BoardProps<T>): React.JSX.Element;
|
|
37
|
+
export declare function Board<T extends BoardCard>({ columns, cards, onCardsChange, renderCard, onNewCard, newCardLabel, getCardLabel, announceMove, className, ...rest }: BoardProps<T>): React.JSX.Element;
|
|
30
38
|
export declare namespace Board {
|
|
31
39
|
var displayName: string;
|
|
32
40
|
}
|