hs-uix 1.5.0 → 1.6.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/README.md +221 -0
- package/common-components.d.ts +127 -0
- package/dist/common-components.js +491 -16
- package/dist/common-components.mjs +465 -10
- package/dist/datatable.js +3 -3
- package/dist/datatable.mjs +5 -4
- package/dist/form.js +18 -1
- package/dist/form.mjs +18 -1
- package/dist/index.js +1745 -17
- package/dist/index.mjs +1760 -20
- package/dist/kanban.js +1410 -0
- package/dist/kanban.mjs +1408 -0
- package/dist/utils.js +60 -0
- package/dist/utils.mjs +58 -0
- package/form.d.ts +1 -0
- package/index.d.ts +65 -0
- package/kanban.d.ts +1 -0
- package/package.json +9 -1
package/dist/kanban.js
ADDED
|
@@ -0,0 +1,1410 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
+
mod
|
|
26
|
+
));
|
|
27
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
|
+
|
|
29
|
+
// src/kanban.js
|
|
30
|
+
var kanban_exports = {};
|
|
31
|
+
__export(kanban_exports, {
|
|
32
|
+
Kanban: () => Kanban,
|
|
33
|
+
KanbanCardActions: () => KanbanCardActions
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(kanban_exports);
|
|
36
|
+
|
|
37
|
+
// packages/kanban/src/Kanban.jsx
|
|
38
|
+
var import_react2 = __toESM(require("react"));
|
|
39
|
+
var import_fuse = __toESM(require("fuse.js"));
|
|
40
|
+
|
|
41
|
+
// src/common-components/StyledText.js
|
|
42
|
+
var import_react = __toESM(require("react"));
|
|
43
|
+
var import_ui_extensions = require("@hubspot/ui-extensions");
|
|
44
|
+
|
|
45
|
+
// src/common-components/svgDefaults.js
|
|
46
|
+
var HS_FONT_FAMILY = '"Lexend Deca", Helvetica, Arial, sans-serif';
|
|
47
|
+
var HS_TEXT_COLOR = "#33475b";
|
|
48
|
+
var HS_SUBTLE_BG = "#F5F8FA";
|
|
49
|
+
var HS_TAG_SUBTLE_BORDER = "#7C98B6";
|
|
50
|
+
var HS_TAG_TEXT_COLOR = HS_TEXT_COLOR;
|
|
51
|
+
var HS_TAG_FONT_SIZE = 12;
|
|
52
|
+
var HS_TAG_LINE_HEIGHT = 22;
|
|
53
|
+
var HS_TAG_PADDING_X = 8;
|
|
54
|
+
var HS_TAG_PADDING_Y = 0;
|
|
55
|
+
var HS_TAG_BORDER_RADIUS = 0;
|
|
56
|
+
var HS_TAG_BORDER_WIDTH = 1;
|
|
57
|
+
|
|
58
|
+
// src/common-components/StyledText.js
|
|
59
|
+
var VARIANT_PRESETS = {
|
|
60
|
+
bodytext: { fontSize: 14, lineHeight: 24, fontWeight: 400 },
|
|
61
|
+
microcopy: { fontSize: 12, lineHeight: 18, fontWeight: 400 }
|
|
62
|
+
};
|
|
63
|
+
var WEIGHT_ALIASES = {
|
|
64
|
+
bold: 700,
|
|
65
|
+
demibold: 600,
|
|
66
|
+
regular: 400
|
|
67
|
+
};
|
|
68
|
+
var LINE_DECORATION = {
|
|
69
|
+
strikethrough: "line-through",
|
|
70
|
+
underline: "underline"
|
|
71
|
+
};
|
|
72
|
+
var ORIENTATION_ROTATION = {
|
|
73
|
+
horizontal: 0,
|
|
74
|
+
"vertical-up": -90,
|
|
75
|
+
"vertical-down": 90
|
|
76
|
+
};
|
|
77
|
+
var BACKGROUND_PRESETS = {
|
|
78
|
+
tag: {
|
|
79
|
+
color: HS_SUBTLE_BG,
|
|
80
|
+
borderColor: HS_TAG_SUBTLE_BORDER,
|
|
81
|
+
borderWidth: HS_TAG_BORDER_WIDTH,
|
|
82
|
+
radius: HS_TAG_BORDER_RADIUS,
|
|
83
|
+
paddingX: HS_TAG_PADDING_X,
|
|
84
|
+
paddingY: HS_TAG_PADDING_Y,
|
|
85
|
+
height: HS_TAG_LINE_HEIGHT,
|
|
86
|
+
textColor: HS_TAG_TEXT_COLOR,
|
|
87
|
+
fontSize: HS_TAG_FONT_SIZE,
|
|
88
|
+
canvasPaddingX: 0,
|
|
89
|
+
canvasPaddingY: 0
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
var TAG_VARIANTS = {
|
|
93
|
+
default: {
|
|
94
|
+
color: HS_SUBTLE_BG,
|
|
95
|
+
borderColor: HS_TAG_SUBTLE_BORDER,
|
|
96
|
+
textColor: HS_TAG_TEXT_COLOR
|
|
97
|
+
},
|
|
98
|
+
success: {
|
|
99
|
+
color: "#E5F8F6",
|
|
100
|
+
borderColor: "#00BDA5",
|
|
101
|
+
textColor: "#00BDA5"
|
|
102
|
+
},
|
|
103
|
+
warning: {
|
|
104
|
+
color: "#FEF8F0",
|
|
105
|
+
borderColor: "#F5C26B",
|
|
106
|
+
textColor: "#D39913"
|
|
107
|
+
},
|
|
108
|
+
error: {
|
|
109
|
+
color: "#FDEDEE",
|
|
110
|
+
borderColor: "#F2545B",
|
|
111
|
+
textColor: "#F2545B"
|
|
112
|
+
},
|
|
113
|
+
danger: {
|
|
114
|
+
color: "#FDEDEE",
|
|
115
|
+
borderColor: "#F2545B",
|
|
116
|
+
textColor: "#F2545B"
|
|
117
|
+
},
|
|
118
|
+
info: {
|
|
119
|
+
color: "#E5F5F8",
|
|
120
|
+
borderColor: "#00A4BD",
|
|
121
|
+
textColor: "#00A4BD"
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
var escapeSvgText = (s) => String(s).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
125
|
+
var applyTextTransform = (text, transform) => {
|
|
126
|
+
if (!transform || transform === "none") return String(text);
|
|
127
|
+
const s = String(text);
|
|
128
|
+
switch (transform) {
|
|
129
|
+
case "uppercase":
|
|
130
|
+
return s.toUpperCase();
|
|
131
|
+
case "lowercase":
|
|
132
|
+
return s.toLowerCase();
|
|
133
|
+
case "capitalize":
|
|
134
|
+
return s.replace(/\b\w/g, (c) => c.toUpperCase());
|
|
135
|
+
case "sentenceCase":
|
|
136
|
+
return s.charAt(0).toUpperCase() + s.slice(1).toLowerCase();
|
|
137
|
+
default:
|
|
138
|
+
return s;
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
var estimateTextWidth = (text, fontSize) => Math.max(fontSize, Math.round(String(text).length * fontSize * 0.58));
|
|
142
|
+
var resolveBackground = (background) => {
|
|
143
|
+
if (!background) return null;
|
|
144
|
+
const preset = background.preset ? BACKGROUND_PRESETS[background.preset] : null;
|
|
145
|
+
const variant = background.preset === "tag" && background.variant ? TAG_VARIANTS[background.variant] || null : null;
|
|
146
|
+
return {
|
|
147
|
+
...preset || {},
|
|
148
|
+
...variant || {},
|
|
149
|
+
...background
|
|
150
|
+
};
|
|
151
|
+
};
|
|
152
|
+
var buildBackgroundRect = ({ background, x, y, width, height }) => {
|
|
153
|
+
const radius = (background == null ? void 0 : background.radius) ?? 3;
|
|
154
|
+
const fill = (background == null ? void 0 : background.color) ?? "transparent";
|
|
155
|
+
const borderWidth = (background == null ? void 0 : background.borderWidth) ?? 0;
|
|
156
|
+
const borderColor = background == null ? void 0 : background.borderColor;
|
|
157
|
+
if (!borderColor || borderWidth <= 0) {
|
|
158
|
+
return `<rect x="${x}" y="${y}" width="${width}" height="${height}" rx="${radius}" fill="${fill}" />`;
|
|
159
|
+
}
|
|
160
|
+
const isTagPreset = (background == null ? void 0 : background.preset) === "tag";
|
|
161
|
+
const fillRect = `<rect x="${x}" y="${y}" width="${width}" height="${height}" rx="${radius}" fill="${fill}" />`;
|
|
162
|
+
const strokeInset = borderWidth / 2;
|
|
163
|
+
const strokeX = x + strokeInset;
|
|
164
|
+
const strokeY = y + strokeInset;
|
|
165
|
+
const strokeW = Math.max(0, width - borderWidth);
|
|
166
|
+
const strokeH = Math.max(0, height - borderWidth);
|
|
167
|
+
return fillRect + `<rect x="${strokeX}" y="${strokeY}" width="${strokeW}" height="${strokeH}" rx="${Math.max(
|
|
168
|
+
0,
|
|
169
|
+
radius - strokeInset
|
|
170
|
+
)}" fill="none" stroke="${borderColor}" stroke-width="${borderWidth}"${isTagPreset ? ` shape-rendering="crispEdges"` : ""} />`;
|
|
171
|
+
};
|
|
172
|
+
var makeStyledTextDataUri = (text, opts = {}) => {
|
|
173
|
+
const {
|
|
174
|
+
variant = "bodytext",
|
|
175
|
+
format = {},
|
|
176
|
+
orientation = "horizontal",
|
|
177
|
+
color: colorProp = HS_TEXT_COLOR,
|
|
178
|
+
fontFamily = HS_FONT_FAMILY,
|
|
179
|
+
background: backgroundProp = null,
|
|
180
|
+
paddingX: paddingXProp = 4,
|
|
181
|
+
paddingY: paddingYProp = 2,
|
|
182
|
+
width: widthOverride,
|
|
183
|
+
height: heightOverride,
|
|
184
|
+
fontSize: fontSizeOverride
|
|
185
|
+
} = opts;
|
|
186
|
+
const preset = VARIANT_PRESETS[variant] || VARIANT_PRESETS.bodytext;
|
|
187
|
+
const background = resolveBackground(backgroundProp);
|
|
188
|
+
const fontSize = fontSizeOverride ?? (background == null ? void 0 : background.fontSize) ?? preset.fontSize;
|
|
189
|
+
const rawWeight = format.fontWeight;
|
|
190
|
+
const fontWeight = rawWeight ? WEIGHT_ALIASES[rawWeight] ?? rawWeight : preset.fontWeight;
|
|
191
|
+
const fontStyle = format.italic ? "italic" : "normal";
|
|
192
|
+
const textDecoration = LINE_DECORATION[format.lineDecoration] || "none";
|
|
193
|
+
const transformed = applyTextTransform(text, format.textTransform);
|
|
194
|
+
const lineHeight = (background == null ? void 0 : background.height) ?? preset.lineHeight ?? fontSize;
|
|
195
|
+
const color = (background == null ? void 0 : background.textColor) ?? colorProp;
|
|
196
|
+
const paddingX = (background == null ? void 0 : background.canvasPaddingX) ?? paddingXProp;
|
|
197
|
+
const paddingY = (background == null ? void 0 : background.canvasPaddingY) ?? paddingYProp;
|
|
198
|
+
const rotate = typeof orientation === "number" ? orientation : ORIENTATION_ROTATION[orientation] ?? 0;
|
|
199
|
+
const textW = estimateTextWidth(transformed, fontSize);
|
|
200
|
+
let pillW = 0;
|
|
201
|
+
let pillH = 0;
|
|
202
|
+
if (background) {
|
|
203
|
+
const bgPadX = background.paddingX ?? 6;
|
|
204
|
+
const bgPadY = background.paddingY ?? 3;
|
|
205
|
+
pillW = textW + bgPadX * 2;
|
|
206
|
+
pillH = background.height ?? Math.max(lineHeight, fontSize + bgPadY * 2);
|
|
207
|
+
}
|
|
208
|
+
const intrinsicW = (background ? pillW : textW) + paddingX * 2;
|
|
209
|
+
const intrinsicH = (background ? pillH : lineHeight) + paddingY * 2;
|
|
210
|
+
const isOrthoRotation = rotate === 90 || rotate === -90 || rotate === 270;
|
|
211
|
+
const canvasW = widthOverride ?? (isOrthoRotation ? intrinsicH : intrinsicW);
|
|
212
|
+
const canvasH = heightOverride ?? (isOrthoRotation ? intrinsicW : intrinsicH);
|
|
213
|
+
const cx = canvasW / 2;
|
|
214
|
+
const cy = canvasH / 2;
|
|
215
|
+
const rectX = cx - pillW / 2;
|
|
216
|
+
const rectY = cy - pillH / 2;
|
|
217
|
+
const group = (background ? buildBackgroundRect({
|
|
218
|
+
background,
|
|
219
|
+
x: rectX,
|
|
220
|
+
y: rectY,
|
|
221
|
+
width: pillW,
|
|
222
|
+
height: pillH
|
|
223
|
+
}) : "") + `<text x="${cx}" y="${cy}" text-anchor="middle" dominant-baseline="central" font-family="${fontFamily.replace(/"/g, """)}" font-size="${fontSize}" font-weight="${fontWeight}" font-style="${fontStyle}" text-decoration="${textDecoration}" fill="${color}">${escapeSvgText(transformed)}</text>`;
|
|
224
|
+
const wrapped = rotate ? `<g transform="rotate(${rotate} ${cx} ${cy})">${group}</g>` : group;
|
|
225
|
+
const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${canvasW}" height="${canvasH}">` + wrapped + `</svg>`;
|
|
226
|
+
return {
|
|
227
|
+
src: `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`,
|
|
228
|
+
width: canvasW,
|
|
229
|
+
height: canvasH
|
|
230
|
+
};
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
// packages/kanban/src/Kanban.jsx
|
|
234
|
+
var import_ui_extensions2 = require("@hubspot/ui-extensions");
|
|
235
|
+
var DEFAULT_DENSITY = "compact";
|
|
236
|
+
var DEFAULT_MAX_CARDS = 10;
|
|
237
|
+
var DEFAULT_MAX_EXPANDED = 50;
|
|
238
|
+
var DEFAULT_COLUMN_WIDTH = 350;
|
|
239
|
+
var MIN_COLUMN_WIDTH = 350;
|
|
240
|
+
var DEFAULT_FILTER_INLINE_LIMIT = 4;
|
|
241
|
+
var DEFAULT_SEARCH_DEBOUNCE = 250;
|
|
242
|
+
var DEFAULT_TITLE_TRUNCATE = 60;
|
|
243
|
+
var applyTruncate = (value, truncate, fallback) => {
|
|
244
|
+
if (truncate === false) return value;
|
|
245
|
+
if (typeof value !== "string") return value;
|
|
246
|
+
const limit = typeof truncate === "number" ? truncate : truncate === true ? fallback || DEFAULT_TITLE_TRUNCATE : fallback || DEFAULT_TITLE_TRUNCATE;
|
|
247
|
+
if (value.length <= limit) return value;
|
|
248
|
+
return value.slice(0, limit).trimEnd() + "\u2026";
|
|
249
|
+
};
|
|
250
|
+
var DEFAULT_LABELS = {
|
|
251
|
+
search: "Search cards...",
|
|
252
|
+
// Only the total is surfaced — callers asked for a single headline number
|
|
253
|
+
// rather than a "loaded / total" fraction. Fall back to the bare label when
|
|
254
|
+
// no total is known.
|
|
255
|
+
showMore: (_shown, total) => total ? `Show more (${total})` : "Show more",
|
|
256
|
+
showLess: "Show less",
|
|
257
|
+
loadMore: (_loaded, total) => total ? `Load more (${total})` : "Load more",
|
|
258
|
+
loadingMore: "Loading...",
|
|
259
|
+
retryLoadMore: "Retry",
|
|
260
|
+
emptyColumn: "\u2014",
|
|
261
|
+
emptyTitle: "No cards",
|
|
262
|
+
emptyMessage: "Nothing matches the current filters.",
|
|
263
|
+
loading: "Loading board...",
|
|
264
|
+
errorTitle: "Something went wrong.",
|
|
265
|
+
errorMessage: "An error occurred while loading data.",
|
|
266
|
+
cardCount: (n) => String(n),
|
|
267
|
+
moveTo: "Move",
|
|
268
|
+
clearAll: "Clear all",
|
|
269
|
+
selectAll: (count, label) => `Select all ${count} ${label}`,
|
|
270
|
+
deselectAll: "Deselect all",
|
|
271
|
+
selected: (count, label) => `${count}\xA0${label}\xA0selected`,
|
|
272
|
+
filtersButton: "Filters",
|
|
273
|
+
dateFrom: "From",
|
|
274
|
+
dateTo: "To",
|
|
275
|
+
sortButton: "Sort",
|
|
276
|
+
sortAscending: "Ascending",
|
|
277
|
+
sortDescending: "Descending",
|
|
278
|
+
metricsButton: "Metrics"
|
|
279
|
+
};
|
|
280
|
+
var makeRotatedTagDataUri = (label) => makeStyledTextDataUri(label, {
|
|
281
|
+
variant: "microcopy",
|
|
282
|
+
format: { fontWeight: "demibold" },
|
|
283
|
+
orientation: "vertical-down",
|
|
284
|
+
background: { preset: "tag" }
|
|
285
|
+
});
|
|
286
|
+
var makeRotatedLabelDataUri = (label) => makeStyledTextDataUri(label, {
|
|
287
|
+
variant: "bodytext",
|
|
288
|
+
format: { fontWeight: "demibold" },
|
|
289
|
+
orientation: "vertical-down"
|
|
290
|
+
});
|
|
291
|
+
var getEmptyFilterValue = (filter) => {
|
|
292
|
+
const type = filter.type || "select";
|
|
293
|
+
if (type === "multiselect") return [];
|
|
294
|
+
if (type === "dateRange") return { from: null, to: null };
|
|
295
|
+
return "";
|
|
296
|
+
};
|
|
297
|
+
var isFilterActive = (filter, value) => {
|
|
298
|
+
const type = filter.type || "select";
|
|
299
|
+
if (type === "multiselect") return Array.isArray(value) && value.length > 0;
|
|
300
|
+
if (type === "dateRange") return value && (value.from || value.to);
|
|
301
|
+
return !!value;
|
|
302
|
+
};
|
|
303
|
+
var formatDateChip = (dateObj) => {
|
|
304
|
+
if (!dateObj) return "";
|
|
305
|
+
const { year, month, date } = dateObj;
|
|
306
|
+
return new Intl.DateTimeFormat("en-US", {
|
|
307
|
+
month: "short",
|
|
308
|
+
day: "numeric",
|
|
309
|
+
year: "numeric"
|
|
310
|
+
}).format(new Date(year, month, date));
|
|
311
|
+
};
|
|
312
|
+
var dateToTimestamp = (dateObj) => {
|
|
313
|
+
if (!dateObj) return null;
|
|
314
|
+
return new Date(dateObj.year, dateObj.month, dateObj.date).getTime();
|
|
315
|
+
};
|
|
316
|
+
var toStableKey = (value) => {
|
|
317
|
+
try {
|
|
318
|
+
return JSON.stringify(value);
|
|
319
|
+
} catch (_error) {
|
|
320
|
+
return String(value);
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
var canStageReceiveRow = (stage, row, canMove) => {
|
|
324
|
+
if (!stage) return false;
|
|
325
|
+
if (typeof canMove === "function" && !canMove(row, stage.value)) return false;
|
|
326
|
+
if (typeof stage.canEnter === "function" && !stage.canEnter(row)) return false;
|
|
327
|
+
return true;
|
|
328
|
+
};
|
|
329
|
+
var isFieldDirectionSortOption = (option) => !!(option && option.field && (option.direction === "asc" || option.direction === "desc"));
|
|
330
|
+
var resolveDividers = (cardDividers, density) => {
|
|
331
|
+
if (cardDividers === true) {
|
|
332
|
+
return { afterTitle: true, afterSubtitle: true, afterBody: true, afterFooter: true };
|
|
333
|
+
}
|
|
334
|
+
if (cardDividers === false) {
|
|
335
|
+
return { afterTitle: false, afterSubtitle: false, afterBody: false, afterFooter: false };
|
|
336
|
+
}
|
|
337
|
+
if (cardDividers && typeof cardDividers === "object") {
|
|
338
|
+
return {
|
|
339
|
+
afterTitle: cardDividers.afterTitle ?? false,
|
|
340
|
+
afterSubtitle: cardDividers.afterSubtitle ?? false,
|
|
341
|
+
afterBody: cardDividers.afterBody ?? false,
|
|
342
|
+
afterFooter: cardDividers.afterFooter ?? false
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
if (density === "comfortable") {
|
|
346
|
+
return { afterTitle: true, afterSubtitle: true, afterBody: true, afterFooter: true };
|
|
347
|
+
}
|
|
348
|
+
return { afterTitle: false, afterSubtitle: false, afterBody: true, afterFooter: false };
|
|
349
|
+
};
|
|
350
|
+
var partitionFields = (cardFields) => {
|
|
351
|
+
const buckets = { title: null, subtitle: null, meta: [], body: [], footer: [] };
|
|
352
|
+
for (const field of cardFields || []) {
|
|
353
|
+
const placement = field.placement || "body";
|
|
354
|
+
if (placement === "title" && !buckets.title) buckets.title = field;
|
|
355
|
+
else if (placement === "subtitle" && !buckets.subtitle) buckets.subtitle = field;
|
|
356
|
+
else if (placement === "meta") buckets.meta.push(field);
|
|
357
|
+
else if (placement === "footer") buckets.footer.push(field);
|
|
358
|
+
else buckets.body.push(field);
|
|
359
|
+
}
|
|
360
|
+
return buckets;
|
|
361
|
+
};
|
|
362
|
+
var resolveFieldValue = (field, row) => {
|
|
363
|
+
if (!field) return void 0;
|
|
364
|
+
if (field.field && row && Object.prototype.hasOwnProperty.call(row, field.field)) {
|
|
365
|
+
return row[field.field];
|
|
366
|
+
}
|
|
367
|
+
return void 0;
|
|
368
|
+
};
|
|
369
|
+
var resolveHref = (href, row) => {
|
|
370
|
+
if (!href) return null;
|
|
371
|
+
if (typeof href === "function") return href(row);
|
|
372
|
+
return href;
|
|
373
|
+
};
|
|
374
|
+
var KanbanCard = ({
|
|
375
|
+
row,
|
|
376
|
+
rowId,
|
|
377
|
+
stage,
|
|
378
|
+
stages,
|
|
379
|
+
fields,
|
|
380
|
+
density,
|
|
381
|
+
dividers,
|
|
382
|
+
bodyAs,
|
|
383
|
+
maxBodyLines,
|
|
384
|
+
stageControl,
|
|
385
|
+
stageControlPlacement,
|
|
386
|
+
canMove,
|
|
387
|
+
onStageChangeRequest,
|
|
388
|
+
isChanging,
|
|
389
|
+
selectable,
|
|
390
|
+
selected,
|
|
391
|
+
onToggleSelect,
|
|
392
|
+
labels
|
|
393
|
+
}) => {
|
|
394
|
+
const titleHref = fields.title ? resolveHref(fields.title.href, row) : null;
|
|
395
|
+
const rawTitleValue = fields.title ? fields.title.render ? fields.title.render(resolveFieldValue(fields.title, row), row) : resolveFieldValue(fields.title, row) : null;
|
|
396
|
+
const titleValue = fields.title && typeof rawTitleValue === "string" ? applyTruncate(rawTitleValue, fields.title.truncate, DEFAULT_TITLE_TRUNCATE) : rawTitleValue;
|
|
397
|
+
const titleNode = titleHref ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Link, { href: titleHref }, titleValue) : /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { format: { fontWeight: "demibold" } }, titleValue);
|
|
398
|
+
const metaNodes = fields.meta.filter((f) => !f.visible || f.visible(row)).map((f) => {
|
|
399
|
+
const val = resolveFieldValue(f, row);
|
|
400
|
+
return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { key: f.field || f.label, variant: "microcopy" }, f.render ? f.render(val, row) : val);
|
|
401
|
+
});
|
|
402
|
+
const showSubtitle = density === "comfortable" && fields.subtitle;
|
|
403
|
+
const subtitleNode = showSubtitle ? fields.subtitle.render ? fields.subtitle.render(resolveFieldValue(fields.subtitle, row), row) : resolveFieldValue(fields.subtitle, row) : null;
|
|
404
|
+
const bodyFields = fields.body.filter((f) => !f.visible || f.visible(row)).slice(0, maxBodyLines);
|
|
405
|
+
const footerFields = fields.footer.filter((f) => !f.visible || f.visible(row));
|
|
406
|
+
const footerAlerts = footerFields.slice(0, -1);
|
|
407
|
+
const footerActionsField = footerFields.length > 0 ? footerFields[footerFields.length - 1] : null;
|
|
408
|
+
const renderFooterField = (f, idx) => {
|
|
409
|
+
const val = resolveFieldValue(f, row);
|
|
410
|
+
const rendered = f.render ? f.render(val, row) : val;
|
|
411
|
+
const key = f.key || f.field || f.label || `footer-${idx}`;
|
|
412
|
+
return /* @__PURE__ */ import_react2.default.createElement(import_react2.default.Fragment, { key }, rendered);
|
|
413
|
+
};
|
|
414
|
+
const stageControlNode = stageControl === "none" ? null : /* @__PURE__ */ import_react2.default.createElement(
|
|
415
|
+
StageControl,
|
|
416
|
+
{
|
|
417
|
+
row,
|
|
418
|
+
rowId,
|
|
419
|
+
currentStage: stage,
|
|
420
|
+
stages,
|
|
421
|
+
canMove,
|
|
422
|
+
isChanging,
|
|
423
|
+
mode: stageControl,
|
|
424
|
+
onStageChangeRequest,
|
|
425
|
+
labels
|
|
426
|
+
}
|
|
427
|
+
);
|
|
428
|
+
const titleRow = /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", justify: "between", align: "center", gap: "sm" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Box, { flex: 1 }, titleNode), selectable ? /* @__PURE__ */ import_react2.default.createElement(
|
|
429
|
+
import_ui_extensions2.Checkbox,
|
|
430
|
+
{
|
|
431
|
+
name: `kanban-select-${rowId}`,
|
|
432
|
+
checked: selected,
|
|
433
|
+
onChange: () => onToggleSelect(rowId)
|
|
434
|
+
}
|
|
435
|
+
) : null);
|
|
436
|
+
const metaRow = metaNodes.length === 0 ? null : /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", justify: "end", align: "center", gap: "xs" }, metaNodes);
|
|
437
|
+
const bodyRow = bodyFields.length === 0 ? null : bodyAs === "descriptionList" ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.DescriptionList, { direction: "row" }, bodyFields.map((f, idx) => {
|
|
438
|
+
const val = resolveFieldValue(f, row);
|
|
439
|
+
const rendered = f.render ? f.render(val, row) : val ?? "\u2014";
|
|
440
|
+
const key = f.key || f.field || f.label || `body-${idx}`;
|
|
441
|
+
return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.DescriptionListItem, { key, label: f.label || "" }, rendered);
|
|
442
|
+
})) : /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", gap: "flush" }, bodyFields.map((f, idx) => {
|
|
443
|
+
const val = resolveFieldValue(f, row);
|
|
444
|
+
const rendered = f.render ? f.render(val, row) : val ?? "\u2014";
|
|
445
|
+
const key = f.key || f.field || f.label || `body-${idx}`;
|
|
446
|
+
return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { key, variant: "microcopy" }, f.label ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { inline: true, variant: "microcopy" }, `${f.label}: `) : null, rendered);
|
|
447
|
+
}));
|
|
448
|
+
const footerAlertsNode = footerAlerts.length === 0 ? null : /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", gap: "xs" }, footerAlerts.map((f, idx) => renderFooterField(f, idx)));
|
|
449
|
+
const footerActionsNode = footerActionsField ? renderFooterField(footerActionsField, footerFields.length - 1) : null;
|
|
450
|
+
const inlineStageControl = stageControlPlacement === "inline" ? stageControlNode : null;
|
|
451
|
+
const separateRowStageControl = stageControlPlacement === "separateRow" ? stageControlNode : null;
|
|
452
|
+
const footerMainRow = inlineStageControl || footerActionsNode ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", justify: "between", align: "start", gap: "sm" }, inlineStageControl ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Box, { alignSelf: "center" }, inlineStageControl) : /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Box, null), footerActionsNode ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Box, { flex: 1 }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", justify: "end", align: "start" }, footerActionsNode)) : null) : null;
|
|
453
|
+
const footerRow = !footerAlertsNode && !footerMainRow ? null : /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", gap: "xs" }, footerAlertsNode, footerMainRow);
|
|
454
|
+
return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Tile, { compact: density === "compact" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", gap: density === "compact" ? "xs" : "sm" }, titleRow, dividers.afterTitle && (metaRow || bodyRow || footerRow || separateRowStageControl) ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Divider, null) : null, subtitleNode ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { variant: "microcopy" }, subtitleNode) : null, dividers.afterSubtitle && subtitleNode && (metaRow || bodyRow || footerRow || separateRowStageControl) ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Divider, null) : null, metaRow, bodyRow, dividers.afterBody && bodyRow && (footerRow || separateRowStageControl) ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Divider, null) : null, footerRow, dividers.afterFooter && footerRow && separateRowStageControl ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Divider, null) : null, separateRowStageControl));
|
|
455
|
+
};
|
|
456
|
+
var StageControl = ({
|
|
457
|
+
row,
|
|
458
|
+
rowId,
|
|
459
|
+
currentStage,
|
|
460
|
+
stages,
|
|
461
|
+
canMove,
|
|
462
|
+
isChanging,
|
|
463
|
+
mode,
|
|
464
|
+
onStageChangeRequest,
|
|
465
|
+
labels
|
|
466
|
+
}) => {
|
|
467
|
+
if (isChanging) {
|
|
468
|
+
return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.LoadingSpinner, { size: "xs" });
|
|
469
|
+
}
|
|
470
|
+
const availableStages = (stages || []).filter(
|
|
471
|
+
(stage) => stage.value === currentStage.value || canStageReceiveRow(stage, row, canMove)
|
|
472
|
+
);
|
|
473
|
+
if (mode === "menu") {
|
|
474
|
+
const targetStages = availableStages.filter((stage) => stage.value !== currentStage.value);
|
|
475
|
+
if (targetStages.length === 0) {
|
|
476
|
+
return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Button, { variant: "transparent", size: "extra-small", disabled: true }, labels.moveTo);
|
|
477
|
+
}
|
|
478
|
+
return /* @__PURE__ */ import_react2.default.createElement(
|
|
479
|
+
import_ui_extensions2.Dropdown,
|
|
480
|
+
{
|
|
481
|
+
variant: "transparent",
|
|
482
|
+
buttonText: labels.moveTo,
|
|
483
|
+
buttonSize: "xs"
|
|
484
|
+
},
|
|
485
|
+
targetStages.map((stage) => /* @__PURE__ */ import_react2.default.createElement(
|
|
486
|
+
import_ui_extensions2.Dropdown.ButtonItem,
|
|
487
|
+
{
|
|
488
|
+
key: stage.value,
|
|
489
|
+
onClick: () => onStageChangeRequest(row, stage.value, currentStage.value)
|
|
490
|
+
},
|
|
491
|
+
stage.shortLabel || stage.label
|
|
492
|
+
))
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
return /* @__PURE__ */ import_react2.default.createElement(
|
|
496
|
+
import_ui_extensions2.Select,
|
|
497
|
+
{
|
|
498
|
+
name: `stage-${rowId}`,
|
|
499
|
+
label: "",
|
|
500
|
+
value: currentStage.value,
|
|
501
|
+
onChange: (val) => {
|
|
502
|
+
if (val !== currentStage.value) onStageChangeRequest(row, val, currentStage.value);
|
|
503
|
+
},
|
|
504
|
+
options: availableStages.map((stage) => ({
|
|
505
|
+
label: stage.shortLabel || stage.label,
|
|
506
|
+
value: stage.value
|
|
507
|
+
}))
|
|
508
|
+
}
|
|
509
|
+
);
|
|
510
|
+
};
|
|
511
|
+
var KanbanColumn = ({
|
|
512
|
+
stage,
|
|
513
|
+
rows,
|
|
514
|
+
bucketCount,
|
|
515
|
+
totalCount,
|
|
516
|
+
hasMore,
|
|
517
|
+
loading,
|
|
518
|
+
error,
|
|
519
|
+
onLoadMore,
|
|
520
|
+
expanded,
|
|
521
|
+
onToggleExpanded,
|
|
522
|
+
collapsed,
|
|
523
|
+
onToggleCollapsed,
|
|
524
|
+
columnFooter,
|
|
525
|
+
countDisplay,
|
|
526
|
+
labels,
|
|
527
|
+
children
|
|
528
|
+
}) => {
|
|
529
|
+
const countLabel = labels.cardCount(totalCount != null ? totalCount : bucketCount);
|
|
530
|
+
const countNode = countDisplay === "text" ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { format: { fontWeight: "demibold" } }, countLabel) : countDisplay === "none" ? null : /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Tag, { variant: "subtle" }, countLabel);
|
|
531
|
+
if (collapsed) {
|
|
532
|
+
const rotated = makeRotatedLabelDataUri(stage.label);
|
|
533
|
+
const rotatedCount = countDisplay === "none" ? null : makeRotatedTagDataUri(countLabel);
|
|
534
|
+
const stageIdentifier = stage.icon ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Icon, { name: stage.icon, size: "sm", screenReaderText: stage.label }) : /* @__PURE__ */ import_react2.default.createElement(
|
|
535
|
+
import_ui_extensions2.Image,
|
|
536
|
+
{
|
|
537
|
+
src: rotated.src,
|
|
538
|
+
width: rotated.width,
|
|
539
|
+
height: rotated.height,
|
|
540
|
+
alt: stage.label
|
|
541
|
+
}
|
|
542
|
+
);
|
|
543
|
+
return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Tile, { compact: true }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", gap: "xs", align: "center" }, /* @__PURE__ */ import_react2.default.createElement(
|
|
544
|
+
import_ui_extensions2.Button,
|
|
545
|
+
{
|
|
546
|
+
variant: "transparent",
|
|
547
|
+
size: "sm",
|
|
548
|
+
onClick: onToggleCollapsed,
|
|
549
|
+
tooltip: `Expand ${stage.label}`
|
|
550
|
+
},
|
|
551
|
+
/* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Icon, { name: "right", size: "sm", screenReaderText: `Expand ${stage.label}` })
|
|
552
|
+
), stageIdentifier, rotatedCount ? /* @__PURE__ */ import_react2.default.createElement(
|
|
553
|
+
import_ui_extensions2.Image,
|
|
554
|
+
{
|
|
555
|
+
src: rotatedCount.src,
|
|
556
|
+
width: rotatedCount.width,
|
|
557
|
+
height: rotatedCount.height,
|
|
558
|
+
alt: `${bucketCount} items`
|
|
559
|
+
}
|
|
560
|
+
) : null));
|
|
561
|
+
}
|
|
562
|
+
const footerContent = stage.footer ? stage.footer(rows) : columnFooter ? columnFooter(rows, stage) : null;
|
|
563
|
+
return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Tile, { compact: true }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", gap: "xs" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", align: "center", justify: "between", gap: "xs" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", align: "center", gap: "xs" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { format: { fontWeight: "demibold" } }, stage.shortLabel || stage.label), countNode, loading ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.LoadingSpinner, { size: "xs" }) : null), /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Button, { variant: "transparent", size: "sm", onClick: onToggleCollapsed, tooltip: "Collapse" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Icon, { name: "left", size: "sm", screenReaderText: `Collapse ${stage.label}` }))), footerContent ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { variant: "microcopy" }, footerContent) : null, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Divider, null), children, error ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Alert, { variant: "danger", title: labels.errorTitle }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", gap: "xs", align: "center" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { variant: "microcopy" }, error), onLoadMore ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Button, { variant: "transparent", size: "xs", onClick: () => onLoadMore(stage.value) }, labels.retryLoadMore) : null)) : null, !error && hasMore && onLoadMore && !loading && bucketCount > 0 ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", justify: "center" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Link, { onClick: () => onLoadMore(stage.value) }, labels.loadMore(bucketCount, totalCount))) : null, !error && loading && hasMore ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.LoadingSpinner, { size: "sm", layout: "centered", label: labels.loadingMore }) : null, !error && !hasMore && bucketCount > rows.length ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", justify: "center" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Link, { onClick: onToggleExpanded }, expanded ? labels.showLess : labels.showMore(rows.length, bucketCount))) : null, rows.length === 0 && bucketCount === 0 && !loading ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { variant: "microcopy", format: { italic: true } }, labels.emptyColumn) : null));
|
|
564
|
+
};
|
|
565
|
+
var SortModalBody = ({ sortOptions, sortValue, onSortChange, labels }) => {
|
|
566
|
+
const hasFieldDirection = Array.isArray(sortOptions) && sortOptions.length > 0 && sortOptions.every(isFieldDirectionSortOption);
|
|
567
|
+
if (!hasFieldDirection) {
|
|
568
|
+
return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", gap: "xs" }, sortOptions.map((opt) => /* @__PURE__ */ import_react2.default.createElement(
|
|
569
|
+
import_ui_extensions2.Button,
|
|
570
|
+
{
|
|
571
|
+
key: opt.value,
|
|
572
|
+
variant: sortValue === opt.value ? "primary" : "secondary",
|
|
573
|
+
onClick: () => onSortChange(opt.value)
|
|
574
|
+
},
|
|
575
|
+
opt.label
|
|
576
|
+
)));
|
|
577
|
+
}
|
|
578
|
+
const currentOption = sortOptions.find((o) => o.value === sortValue) || sortOptions[0];
|
|
579
|
+
const currentField = currentOption.field;
|
|
580
|
+
const currentDirection = currentOption.direction;
|
|
581
|
+
const uniqueFields = [];
|
|
582
|
+
const seen = /* @__PURE__ */ new Set();
|
|
583
|
+
for (const opt of sortOptions) {
|
|
584
|
+
if (!seen.has(opt.field)) {
|
|
585
|
+
seen.add(opt.field);
|
|
586
|
+
uniqueFields.push({ value: opt.field, label: opt.fieldLabel || opt.field });
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
const fieldOpts = sortOptions.filter((o) => o.field === currentField);
|
|
590
|
+
const ascOption = fieldOpts.find((o) => o.direction === "asc");
|
|
591
|
+
const descOption = fieldOpts.find((o) => o.direction === "desc");
|
|
592
|
+
const handleFieldChange = (newField) => {
|
|
593
|
+
const next = sortOptions.find((o) => o.field === newField && o.direction === currentDirection) || sortOptions.find((o) => o.field === newField && o.direction === "desc") || sortOptions.find((o) => o.field === newField);
|
|
594
|
+
if (next) onSortChange(next.value);
|
|
595
|
+
};
|
|
596
|
+
return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Inline, { align: "center", gap: "small" }, /* @__PURE__ */ import_react2.default.createElement(
|
|
597
|
+
import_ui_extensions2.Select,
|
|
598
|
+
{
|
|
599
|
+
name: "kanban-sort-field",
|
|
600
|
+
label: "",
|
|
601
|
+
value: currentField,
|
|
602
|
+
onChange: handleFieldChange,
|
|
603
|
+
options: uniqueFields
|
|
604
|
+
}
|
|
605
|
+
), /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Inline, { align: "center", gap: "flush" }, descOption ? /* @__PURE__ */ import_react2.default.createElement(
|
|
606
|
+
import_ui_extensions2.Button,
|
|
607
|
+
{
|
|
608
|
+
variant: currentDirection === "desc" ? "primary" : "secondary",
|
|
609
|
+
onClick: () => onSortChange(descOption.value)
|
|
610
|
+
},
|
|
611
|
+
labels.sortDescending
|
|
612
|
+
) : null, ascOption ? /* @__PURE__ */ import_react2.default.createElement(
|
|
613
|
+
import_ui_extensions2.Button,
|
|
614
|
+
{
|
|
615
|
+
variant: currentDirection === "asc" ? "primary" : "secondary",
|
|
616
|
+
onClick: () => onSortChange(ascOption.value)
|
|
617
|
+
},
|
|
618
|
+
labels.sortAscending
|
|
619
|
+
) : null));
|
|
620
|
+
};
|
|
621
|
+
var renderFilterControl = ({ filter, value, onChange, labels }) => {
|
|
622
|
+
const type = filter.type || "select";
|
|
623
|
+
if (type === "multiselect") {
|
|
624
|
+
return /* @__PURE__ */ import_react2.default.createElement(
|
|
625
|
+
import_ui_extensions2.MultiSelect,
|
|
626
|
+
{
|
|
627
|
+
key: filter.name,
|
|
628
|
+
name: `kanban-filter-${filter.name}`,
|
|
629
|
+
label: "",
|
|
630
|
+
placeholder: filter.placeholder || "All",
|
|
631
|
+
value: value || [],
|
|
632
|
+
onChange: (val) => onChange(filter.name, val),
|
|
633
|
+
options: filter.options
|
|
634
|
+
}
|
|
635
|
+
);
|
|
636
|
+
}
|
|
637
|
+
if (type === "dateRange") {
|
|
638
|
+
const rangeVal = value || { from: null, to: null };
|
|
639
|
+
return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { key: filter.name, direction: "row", align: "center", gap: "xs" }, /* @__PURE__ */ import_react2.default.createElement(
|
|
640
|
+
import_ui_extensions2.DateInput,
|
|
641
|
+
{
|
|
642
|
+
size: "sm",
|
|
643
|
+
name: `kanban-filter-${filter.name}-from`,
|
|
644
|
+
label: "",
|
|
645
|
+
placeholder: labels.dateFrom,
|
|
646
|
+
format: "medium",
|
|
647
|
+
value: rangeVal.from,
|
|
648
|
+
onChange: (val) => onChange(filter.name, { ...rangeVal, from: val })
|
|
649
|
+
}
|
|
650
|
+
), /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Icon, { name: "dataSync", size: "sm" }), /* @__PURE__ */ import_react2.default.createElement(
|
|
651
|
+
import_ui_extensions2.DateInput,
|
|
652
|
+
{
|
|
653
|
+
size: "sm",
|
|
654
|
+
name: `kanban-filter-${filter.name}-to`,
|
|
655
|
+
label: "",
|
|
656
|
+
placeholder: labels.dateTo,
|
|
657
|
+
format: "medium",
|
|
658
|
+
value: rangeVal.to,
|
|
659
|
+
onChange: (val) => onChange(filter.name, { ...rangeVal, to: val })
|
|
660
|
+
}
|
|
661
|
+
));
|
|
662
|
+
}
|
|
663
|
+
return /* @__PURE__ */ import_react2.default.createElement(
|
|
664
|
+
import_ui_extensions2.Select,
|
|
665
|
+
{
|
|
666
|
+
key: filter.name,
|
|
667
|
+
name: `kanban-filter-${filter.name}`,
|
|
668
|
+
variant: "transparent",
|
|
669
|
+
placeholder: filter.placeholder || "All",
|
|
670
|
+
value,
|
|
671
|
+
onChange: (val) => onChange(filter.name, val),
|
|
672
|
+
options: [
|
|
673
|
+
{ label: filter.placeholder || "All", value: "" },
|
|
674
|
+
...filter.options
|
|
675
|
+
]
|
|
676
|
+
}
|
|
677
|
+
);
|
|
678
|
+
};
|
|
679
|
+
var renderMetricsPanel = (metrics) => {
|
|
680
|
+
if (!metrics) return null;
|
|
681
|
+
if (!Array.isArray(metrics)) return metrics;
|
|
682
|
+
if (metrics.length === 0) return null;
|
|
683
|
+
return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Statistics, null, metrics.map((m, i) => /* @__PURE__ */ import_react2.default.createElement(
|
|
684
|
+
import_ui_extensions2.StatisticsItem,
|
|
685
|
+
{
|
|
686
|
+
key: m.id || m.label || i,
|
|
687
|
+
label: m.label,
|
|
688
|
+
number: m.number != null ? String(m.number) : ""
|
|
689
|
+
},
|
|
690
|
+
m.trend ? /* @__PURE__ */ import_react2.default.createElement(
|
|
691
|
+
import_ui_extensions2.StatisticsTrend,
|
|
692
|
+
{
|
|
693
|
+
direction: m.trend.direction || "increase",
|
|
694
|
+
value: m.trend.value,
|
|
695
|
+
color: m.trend.color
|
|
696
|
+
}
|
|
697
|
+
) : null
|
|
698
|
+
)));
|
|
699
|
+
};
|
|
700
|
+
var KanbanToolbar = ({
|
|
701
|
+
showSearch,
|
|
702
|
+
searchValue,
|
|
703
|
+
searchPlaceholder,
|
|
704
|
+
onSearchChange,
|
|
705
|
+
filters,
|
|
706
|
+
filterValues,
|
|
707
|
+
onFilterChange,
|
|
708
|
+
filterInlineLimit,
|
|
709
|
+
showFilterBadges,
|
|
710
|
+
showClearFiltersButton,
|
|
711
|
+
activeChips,
|
|
712
|
+
onFilterRemove,
|
|
713
|
+
sortOptions,
|
|
714
|
+
sortValue,
|
|
715
|
+
onSortChange,
|
|
716
|
+
metrics,
|
|
717
|
+
showMetrics,
|
|
718
|
+
onToggleMetrics,
|
|
719
|
+
labels
|
|
720
|
+
}) => {
|
|
721
|
+
const [showMoreFilters, setShowMoreFilters] = (0, import_react2.useState)(false);
|
|
722
|
+
const inlineFilters = (filters || []).slice(0, filterInlineLimit);
|
|
723
|
+
const overflowFilters = (filters || []).slice(filterInlineLimit);
|
|
724
|
+
return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", gap: "xs" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", gap: "sm" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Box, { flex: 3 }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", gap: "sm" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", align: "center", gap: "sm", wrap: "wrap" }, showSearch ? /* @__PURE__ */ import_react2.default.createElement(
|
|
725
|
+
import_ui_extensions2.SearchInput,
|
|
726
|
+
{
|
|
727
|
+
name: "kanban-search",
|
|
728
|
+
placeholder: searchPlaceholder,
|
|
729
|
+
value: searchValue,
|
|
730
|
+
onChange: onSearchChange
|
|
731
|
+
}
|
|
732
|
+
) : null, inlineFilters.map(
|
|
733
|
+
(filter) => renderFilterControl({
|
|
734
|
+
filter,
|
|
735
|
+
value: filterValues[filter.name],
|
|
736
|
+
onChange: onFilterChange,
|
|
737
|
+
labels
|
|
738
|
+
})
|
|
739
|
+
), overflowFilters.length > 0 ? /* @__PURE__ */ import_react2.default.createElement(
|
|
740
|
+
import_ui_extensions2.Button,
|
|
741
|
+
{
|
|
742
|
+
variant: "transparent",
|
|
743
|
+
size: "small",
|
|
744
|
+
onClick: () => setShowMoreFilters((prev) => !prev)
|
|
745
|
+
},
|
|
746
|
+
/* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Icon, { name: "filter", size: "sm" }),
|
|
747
|
+
" ",
|
|
748
|
+
labels.filtersButton
|
|
749
|
+
) : null), showMoreFilters && overflowFilters.length > 0 ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", align: "center", gap: "sm", wrap: "wrap" }, overflowFilters.map(
|
|
750
|
+
(filter) => renderFilterControl({
|
|
751
|
+
filter,
|
|
752
|
+
value: filterValues[filter.name],
|
|
753
|
+
onChange: onFilterChange,
|
|
754
|
+
labels
|
|
755
|
+
})
|
|
756
|
+
)) : null, activeChips.length > 0 && (showFilterBadges || showClearFiltersButton) ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", align: "center", gap: "sm", wrap: "wrap" }, showFilterBadges ? activeChips.map((chip) => /* @__PURE__ */ import_react2.default.createElement(
|
|
757
|
+
import_ui_extensions2.Tag,
|
|
758
|
+
{
|
|
759
|
+
key: chip.key,
|
|
760
|
+
variant: "default",
|
|
761
|
+
onDelete: () => onFilterRemove(chip.key)
|
|
762
|
+
},
|
|
763
|
+
chip.label
|
|
764
|
+
)) : null, showClearFiltersButton ? /* @__PURE__ */ import_react2.default.createElement(
|
|
765
|
+
import_ui_extensions2.Button,
|
|
766
|
+
{
|
|
767
|
+
variant: "transparent",
|
|
768
|
+
size: "extra-small",
|
|
769
|
+
onClick: () => onFilterRemove("all")
|
|
770
|
+
},
|
|
771
|
+
labels.clearAll
|
|
772
|
+
) : null) : null)), (sortOptions == null ? void 0 : sortOptions.length) > 0 || metrics ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Box, { flex: 1, alignSelf: "start" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", align: "center", gap: "sm", justify: "end" }, sortOptions && sortOptions.length > 0 ? /* @__PURE__ */ import_react2.default.createElement(
|
|
773
|
+
import_ui_extensions2.Button,
|
|
774
|
+
{
|
|
775
|
+
variant: "secondary",
|
|
776
|
+
size: "small",
|
|
777
|
+
overlay: /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Modal, { id: "kanban-sort-modal", title: labels.sortButton }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.ModalBody, null, /* @__PURE__ */ import_react2.default.createElement(
|
|
778
|
+
SortModalBody,
|
|
779
|
+
{
|
|
780
|
+
sortOptions,
|
|
781
|
+
sortValue,
|
|
782
|
+
onSortChange,
|
|
783
|
+
labels
|
|
784
|
+
}
|
|
785
|
+
)))
|
|
786
|
+
},
|
|
787
|
+
/* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Icon, { name: "sortAmtDesc", size: "sm" }),
|
|
788
|
+
" ",
|
|
789
|
+
labels.sortButton
|
|
790
|
+
) : null, metrics ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Button, { variant: "secondary", size: "small", onClick: onToggleMetrics }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Icon, { name: "reports", size: "sm" }), " ", labels.metricsButton) : null)) : null), showMetrics && metrics ? renderMetricsPanel(metrics) : null);
|
|
791
|
+
};
|
|
792
|
+
var DefaultSelectionBar = ({
|
|
793
|
+
selectedIds,
|
|
794
|
+
selectedCount,
|
|
795
|
+
displayCount,
|
|
796
|
+
countLabel,
|
|
797
|
+
allSelected,
|
|
798
|
+
onSelectAll,
|
|
799
|
+
onDeselectAll,
|
|
800
|
+
selectionActions,
|
|
801
|
+
labels
|
|
802
|
+
}) => {
|
|
803
|
+
const pluralForCount = (n) => countLabel(n);
|
|
804
|
+
return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Tile, { compact: true }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Inline, { align: "center", justify: "between", gap: "small" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Inline, { align: "center", gap: "small" }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, { inline: true, format: { fontWeight: "demibold" } }, typeof labels.selected === "function" ? labels.selected(selectedCount, pluralForCount(selectedCount)) : `${selectedCount} selected`), !allSelected ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Button, { variant: "transparent", size: "extra-small", onClick: onSelectAll }, typeof labels.selectAll === "function" ? labels.selectAll(displayCount, pluralForCount(displayCount)) : labels.selectAll) : null, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Button, { variant: "transparent", size: "extra-small", onClick: onDeselectAll }, labels.deselectAll)), (selectionActions || []).length > 0 ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Inline, { align: "center", gap: "extra-small" }, selectionActions.map((action, i) => /* @__PURE__ */ import_react2.default.createElement(
|
|
805
|
+
import_ui_extensions2.Button,
|
|
806
|
+
{
|
|
807
|
+
key: action.key || action.label || i,
|
|
808
|
+
variant: action.variant || "transparent",
|
|
809
|
+
size: "extra-small",
|
|
810
|
+
onClick: () => action.onClick([...selectedIds])
|
|
811
|
+
},
|
|
812
|
+
action.icon ? /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Icon, { name: action.icon, size: "sm" }) : null,
|
|
813
|
+
" ",
|
|
814
|
+
action.label
|
|
815
|
+
))) : null));
|
|
816
|
+
};
|
|
817
|
+
var Kanban = ({
|
|
818
|
+
// --- Data ---
|
|
819
|
+
data = [],
|
|
820
|
+
stages = [],
|
|
821
|
+
groupBy = "status",
|
|
822
|
+
rowIdField = "id",
|
|
823
|
+
// --- Card rendering ---
|
|
824
|
+
renderCard,
|
|
825
|
+
cardFields,
|
|
826
|
+
cardDensity = DEFAULT_DENSITY,
|
|
827
|
+
cardDividers,
|
|
828
|
+
cardBodyAs = "descriptionList",
|
|
829
|
+
maxBodyLines,
|
|
830
|
+
maxCardsPerColumn = DEFAULT_MAX_CARDS,
|
|
831
|
+
maxCardsExpanded = DEFAULT_MAX_EXPANDED,
|
|
832
|
+
expandedStages,
|
|
833
|
+
onExpandedStagesChange,
|
|
834
|
+
// --- Per-stage pagination ---
|
|
835
|
+
stageMeta,
|
|
836
|
+
onLoadMore,
|
|
837
|
+
// --- Selection ---
|
|
838
|
+
selectable = false,
|
|
839
|
+
selectedIds,
|
|
840
|
+
onSelectionChange,
|
|
841
|
+
selectionActions,
|
|
842
|
+
recordLabel,
|
|
843
|
+
selectionResetKey,
|
|
844
|
+
resetSelectionOnQueryChange = true,
|
|
845
|
+
showSelectionBar = true,
|
|
846
|
+
renderSelectionBar,
|
|
847
|
+
// --- Stage transitions ---
|
|
848
|
+
stageControl,
|
|
849
|
+
stageControlPlacement,
|
|
850
|
+
onStageChange,
|
|
851
|
+
isStageChanging,
|
|
852
|
+
canMove,
|
|
853
|
+
// --- Toolbar ---
|
|
854
|
+
showSearch = true,
|
|
855
|
+
searchFields,
|
|
856
|
+
searchPlaceholder,
|
|
857
|
+
searchDebounce = DEFAULT_SEARCH_DEBOUNCE,
|
|
858
|
+
fuzzySearch = false,
|
|
859
|
+
fuzzyOptions,
|
|
860
|
+
filters,
|
|
861
|
+
filterInlineLimit = DEFAULT_FILTER_INLINE_LIMIT,
|
|
862
|
+
showFilterBadges = true,
|
|
863
|
+
showClearFiltersButton = true,
|
|
864
|
+
sortOptions,
|
|
865
|
+
defaultSort,
|
|
866
|
+
sort,
|
|
867
|
+
onSortChange,
|
|
868
|
+
// --- Column level ---
|
|
869
|
+
columnFooter,
|
|
870
|
+
columnWidth = DEFAULT_COLUMN_WIDTH,
|
|
871
|
+
countDisplay = "tag",
|
|
872
|
+
collapsedStages,
|
|
873
|
+
onCollapsedStagesChange,
|
|
874
|
+
// --- Metrics panel ---
|
|
875
|
+
metrics,
|
|
876
|
+
// Array of stat items or a ReactNode for full override
|
|
877
|
+
showMetrics: controlledShowMetrics,
|
|
878
|
+
onMetricsToggle,
|
|
879
|
+
// --- State (controlled) ---
|
|
880
|
+
searchValue,
|
|
881
|
+
onSearchChange,
|
|
882
|
+
filterValues,
|
|
883
|
+
onFilterChange,
|
|
884
|
+
onParamsChange,
|
|
885
|
+
loading = false,
|
|
886
|
+
error,
|
|
887
|
+
// --- Labels ---
|
|
888
|
+
labels: labelsProp,
|
|
889
|
+
renderEmptyState,
|
|
890
|
+
renderLoadingState,
|
|
891
|
+
renderErrorState
|
|
892
|
+
}) => {
|
|
893
|
+
var _a;
|
|
894
|
+
const labels = (0, import_react2.useMemo)(() => ({ ...DEFAULT_LABELS, ...labelsProp || {} }), [labelsProp]);
|
|
895
|
+
const [internalSearch, setInternalSearch] = (0, import_react2.useState)(searchValue != null ? searchValue : "");
|
|
896
|
+
const [internalFilters, setInternalFilters] = (0, import_react2.useState)(() => {
|
|
897
|
+
const init = {};
|
|
898
|
+
(filters || []).forEach((f) => {
|
|
899
|
+
init[f.name] = getEmptyFilterValue(f);
|
|
900
|
+
});
|
|
901
|
+
return init;
|
|
902
|
+
});
|
|
903
|
+
const [internalSort, setInternalSort] = (0, import_react2.useState)(defaultSort || (((_a = sortOptions == null ? void 0 : sortOptions[0]) == null ? void 0 : _a.value) ?? ""));
|
|
904
|
+
const [internalCollapsed, setInternalCollapsed] = (0, import_react2.useState)([]);
|
|
905
|
+
const [internalExpanded, setInternalExpanded] = (0, import_react2.useState)([]);
|
|
906
|
+
const [internalSelection, setInternalSelection] = (0, import_react2.useState)([]);
|
|
907
|
+
const [internalShowMetrics, setInternalShowMetrics] = (0, import_react2.useState)(false);
|
|
908
|
+
const [transitionPrompts, setTransitionPrompts] = (0, import_react2.useState)({});
|
|
909
|
+
const selectionResetRef = (0, import_react2.useRef)("");
|
|
910
|
+
const resolvedShowMetrics = controlledShowMetrics != null ? controlledShowMetrics : internalShowMetrics;
|
|
911
|
+
const toggleMetrics = (0, import_react2.useCallback)(() => {
|
|
912
|
+
const next = !resolvedShowMetrics;
|
|
913
|
+
if (onMetricsToggle) onMetricsToggle(next);
|
|
914
|
+
if (controlledShowMetrics == null) setInternalShowMetrics(next);
|
|
915
|
+
}, [resolvedShowMetrics, onMetricsToggle, controlledShowMetrics]);
|
|
916
|
+
const effectiveColumnWidth = Math.max(MIN_COLUMN_WIDTH, columnWidth || DEFAULT_COLUMN_WIDTH);
|
|
917
|
+
const resolvedSearch = searchValue != null ? searchValue : internalSearch;
|
|
918
|
+
const resolvedFilters = filterValues != null ? filterValues : internalFilters;
|
|
919
|
+
const resolvedSort = sort != null ? sort : internalSort;
|
|
920
|
+
const resolvedCollapsed = collapsedStages != null ? collapsedStages : internalCollapsed;
|
|
921
|
+
const resolvedExpanded = expandedStages != null ? expandedStages : internalExpanded;
|
|
922
|
+
const resolvedSelection = selectedIds != null ? selectedIds : internalSelection;
|
|
923
|
+
const searchEnabled = showSearch && Array.isArray(searchFields) && searchFields.length > 0;
|
|
924
|
+
const stagesByValue = (0, import_react2.useMemo)(() => {
|
|
925
|
+
const map = {};
|
|
926
|
+
for (const stage of stages || []) {
|
|
927
|
+
map[stage.value] = stage;
|
|
928
|
+
}
|
|
929
|
+
return map;
|
|
930
|
+
}, [stages]);
|
|
931
|
+
const fireParamsChange = (0, import_react2.useCallback)((overrides = {}) => {
|
|
932
|
+
if (!onParamsChange) return;
|
|
933
|
+
onParamsChange({
|
|
934
|
+
search: overrides.search != null ? overrides.search : resolvedSearch,
|
|
935
|
+
filters: overrides.filters != null ? overrides.filters : resolvedFilters,
|
|
936
|
+
sort: overrides.sort != null ? overrides.sort : resolvedSort || null,
|
|
937
|
+
collapsedStages: overrides.collapsedStages != null ? overrides.collapsedStages : resolvedCollapsed
|
|
938
|
+
});
|
|
939
|
+
}, [onParamsChange, resolvedCollapsed, resolvedFilters, resolvedSearch, resolvedSort]);
|
|
940
|
+
const debounceRef = (0, import_react2.useRef)(null);
|
|
941
|
+
(0, import_react2.useEffect)(() => () => {
|
|
942
|
+
if (debounceRef.current) clearTimeout(debounceRef.current);
|
|
943
|
+
}, []);
|
|
944
|
+
const handleSearch = (0, import_react2.useCallback)(
|
|
945
|
+
(val) => {
|
|
946
|
+
if (searchValue == null) setInternalSearch(val);
|
|
947
|
+
const dispatch = () => {
|
|
948
|
+
if (onSearchChange) onSearchChange(val);
|
|
949
|
+
fireParamsChange({ search: val });
|
|
950
|
+
};
|
|
951
|
+
if (searchDebounce > 0) {
|
|
952
|
+
if (debounceRef.current) clearTimeout(debounceRef.current);
|
|
953
|
+
debounceRef.current = setTimeout(dispatch, searchDebounce);
|
|
954
|
+
} else {
|
|
955
|
+
dispatch();
|
|
956
|
+
}
|
|
957
|
+
},
|
|
958
|
+
[fireParamsChange, onSearchChange, searchValue, searchDebounce]
|
|
959
|
+
);
|
|
960
|
+
const handleFilter = (0, import_react2.useCallback)(
|
|
961
|
+
(name, val) => {
|
|
962
|
+
const next = { ...resolvedFilters, [name]: val };
|
|
963
|
+
if (filterValues == null) setInternalFilters(next);
|
|
964
|
+
if (onFilterChange) onFilterChange(next);
|
|
965
|
+
fireParamsChange({ filters: next });
|
|
966
|
+
},
|
|
967
|
+
[fireParamsChange, onFilterChange, filterValues, resolvedFilters]
|
|
968
|
+
);
|
|
969
|
+
const handleFilterRemove = (0, import_react2.useCallback)(
|
|
970
|
+
(key) => {
|
|
971
|
+
if (key === "all") {
|
|
972
|
+
const cleared = {};
|
|
973
|
+
(filters || []).forEach((f) => {
|
|
974
|
+
cleared[f.name] = getEmptyFilterValue(f);
|
|
975
|
+
});
|
|
976
|
+
if (filterValues == null) setInternalFilters(cleared);
|
|
977
|
+
if (onFilterChange) onFilterChange(cleared);
|
|
978
|
+
fireParamsChange({ filters: cleared });
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
981
|
+
const filter = (filters || []).find((f) => f.name === key);
|
|
982
|
+
const emptyVal = filter ? getEmptyFilterValue(filter) : "";
|
|
983
|
+
const next = { ...resolvedFilters, [key]: emptyVal };
|
|
984
|
+
if (filterValues == null) setInternalFilters(next);
|
|
985
|
+
if (onFilterChange) onFilterChange(next);
|
|
986
|
+
fireParamsChange({ filters: next });
|
|
987
|
+
},
|
|
988
|
+
[filters, filterValues, fireParamsChange, onFilterChange, resolvedFilters]
|
|
989
|
+
);
|
|
990
|
+
const handleSort = (0, import_react2.useCallback)(
|
|
991
|
+
(val) => {
|
|
992
|
+
if (onSortChange) onSortChange(val);
|
|
993
|
+
if (sort == null) setInternalSort(val);
|
|
994
|
+
fireParamsChange({ sort: val });
|
|
995
|
+
},
|
|
996
|
+
[fireParamsChange, onSortChange, sort]
|
|
997
|
+
);
|
|
998
|
+
const handleCollapsed = (0, import_react2.useCallback)(
|
|
999
|
+
(stageValue) => {
|
|
1000
|
+
const next = resolvedCollapsed.includes(stageValue) ? resolvedCollapsed.filter((v) => v !== stageValue) : [...resolvedCollapsed, stageValue];
|
|
1001
|
+
if (onCollapsedStagesChange) onCollapsedStagesChange(next);
|
|
1002
|
+
if (collapsedStages == null) setInternalCollapsed(next);
|
|
1003
|
+
fireParamsChange({ collapsedStages: next });
|
|
1004
|
+
},
|
|
1005
|
+
[fireParamsChange, resolvedCollapsed, collapsedStages, onCollapsedStagesChange]
|
|
1006
|
+
);
|
|
1007
|
+
const handleExpanded = (0, import_react2.useCallback)(
|
|
1008
|
+
(stageValue) => {
|
|
1009
|
+
const next = resolvedExpanded.includes(stageValue) ? resolvedExpanded.filter((v) => v !== stageValue) : [...resolvedExpanded, stageValue];
|
|
1010
|
+
if (onExpandedStagesChange) onExpandedStagesChange(next);
|
|
1011
|
+
if (expandedStages == null) setInternalExpanded(next);
|
|
1012
|
+
},
|
|
1013
|
+
[resolvedExpanded, expandedStages, onExpandedStagesChange]
|
|
1014
|
+
);
|
|
1015
|
+
const handleToggleSelect = (0, import_react2.useCallback)(
|
|
1016
|
+
(rowId) => {
|
|
1017
|
+
const next = resolvedSelection.includes(rowId) ? resolvedSelection.filter((id) => id !== rowId) : [...resolvedSelection, rowId];
|
|
1018
|
+
if (onSelectionChange) onSelectionChange(next);
|
|
1019
|
+
if (selectedIds == null) setInternalSelection(next);
|
|
1020
|
+
},
|
|
1021
|
+
[resolvedSelection, selectedIds, onSelectionChange]
|
|
1022
|
+
);
|
|
1023
|
+
const clearTransitionPrompt = (0, import_react2.useCallback)((rowId) => {
|
|
1024
|
+
setTransitionPrompts((prev) => {
|
|
1025
|
+
if (!Object.prototype.hasOwnProperty.call(prev, rowId)) return prev;
|
|
1026
|
+
const next = { ...prev };
|
|
1027
|
+
delete next[rowId];
|
|
1028
|
+
return next;
|
|
1029
|
+
});
|
|
1030
|
+
}, []);
|
|
1031
|
+
const commitStageChange = (0, import_react2.useCallback)(
|
|
1032
|
+
(row, newStage, oldStage, result) => {
|
|
1033
|
+
clearTransitionPrompt(row[rowIdField]);
|
|
1034
|
+
if (onStageChange) onStageChange(row, newStage, oldStage, result);
|
|
1035
|
+
},
|
|
1036
|
+
[clearTransitionPrompt, onStageChange, rowIdField]
|
|
1037
|
+
);
|
|
1038
|
+
const selectionQueryKey = (0, import_react2.useMemo)(() => {
|
|
1039
|
+
if (!resetSelectionOnQueryChange) return "";
|
|
1040
|
+
return toStableKey({
|
|
1041
|
+
search: resolvedSearch,
|
|
1042
|
+
filters: resolvedFilters,
|
|
1043
|
+
sort: resolvedSort || null
|
|
1044
|
+
});
|
|
1045
|
+
}, [resetSelectionOnQueryChange, resolvedFilters, resolvedSearch, resolvedSort]);
|
|
1046
|
+
const combinedSelectionResetKey = (0, import_react2.useMemo)(
|
|
1047
|
+
() => `${selectionQueryKey}::${selectionResetKey == null ? "" : toStableKey(selectionResetKey)}`,
|
|
1048
|
+
[selectionQueryKey, selectionResetKey]
|
|
1049
|
+
);
|
|
1050
|
+
(0, import_react2.useEffect)(() => {
|
|
1051
|
+
if (!selectable || selectedIds != null) {
|
|
1052
|
+
selectionResetRef.current = combinedSelectionResetKey;
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
1055
|
+
if (selectionResetRef.current && selectionResetRef.current !== combinedSelectionResetKey) {
|
|
1056
|
+
setInternalSelection([]);
|
|
1057
|
+
}
|
|
1058
|
+
selectionResetRef.current = combinedSelectionResetKey;
|
|
1059
|
+
}, [combinedSelectionResetKey, selectable, selectedIds]);
|
|
1060
|
+
const getStageFor = (0, import_react2.useCallback)(
|
|
1061
|
+
(row) => {
|
|
1062
|
+
if (typeof groupBy === "function") return groupBy(row);
|
|
1063
|
+
return row[groupBy];
|
|
1064
|
+
},
|
|
1065
|
+
[groupBy]
|
|
1066
|
+
);
|
|
1067
|
+
const filteredData = (0, import_react2.useMemo)(() => {
|
|
1068
|
+
let result = data;
|
|
1069
|
+
for (const filter of filters || []) {
|
|
1070
|
+
const val = resolvedFilters[filter.name];
|
|
1071
|
+
if (!isFilterActive(filter, val)) continue;
|
|
1072
|
+
const type = filter.type || "select";
|
|
1073
|
+
if (filter.filterFn) {
|
|
1074
|
+
result = result.filter((row) => filter.filterFn(row, val));
|
|
1075
|
+
} else if (type === "multiselect") {
|
|
1076
|
+
result = result.filter((row) => val.includes(row[filter.name]));
|
|
1077
|
+
} else if (type === "dateRange") {
|
|
1078
|
+
const fromTs = dateToTimestamp(val.from);
|
|
1079
|
+
const toTs = val.to ? dateToTimestamp(val.to) + 864e5 - 1 : null;
|
|
1080
|
+
result = result.filter((row) => {
|
|
1081
|
+
const rowTs = new Date(row[filter.name]).getTime();
|
|
1082
|
+
if (Number.isNaN(rowTs)) return false;
|
|
1083
|
+
if (fromTs && rowTs < fromTs) return false;
|
|
1084
|
+
if (toTs && rowTs > toTs) return false;
|
|
1085
|
+
return true;
|
|
1086
|
+
});
|
|
1087
|
+
} else {
|
|
1088
|
+
result = result.filter((row) => row[filter.name] === val);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
const searchLower = (resolvedSearch || "").toLowerCase().trim();
|
|
1092
|
+
if (searchEnabled && searchLower) {
|
|
1093
|
+
if (fuzzySearch) {
|
|
1094
|
+
const fuse = new import_fuse.default(result, {
|
|
1095
|
+
keys: searchFields,
|
|
1096
|
+
threshold: 0.4,
|
|
1097
|
+
distance: 100,
|
|
1098
|
+
ignoreLocation: true,
|
|
1099
|
+
...fuzzyOptions
|
|
1100
|
+
});
|
|
1101
|
+
result = fuse.search(searchLower).map((r) => r.item);
|
|
1102
|
+
} else {
|
|
1103
|
+
result = result.filter(
|
|
1104
|
+
(row) => searchFields.some(
|
|
1105
|
+
(f) => String(row[f] || "").toLowerCase().includes(searchLower)
|
|
1106
|
+
)
|
|
1107
|
+
);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
return result;
|
|
1111
|
+
}, [data, resolvedSearch, resolvedFilters, filters, searchEnabled, searchFields, fuzzySearch, fuzzyOptions]);
|
|
1112
|
+
const buckets = (0, import_react2.useMemo)(() => {
|
|
1113
|
+
const map = {};
|
|
1114
|
+
for (const stage of stages) map[stage.value] = [];
|
|
1115
|
+
for (const row of filteredData) {
|
|
1116
|
+
const key = getStageFor(row);
|
|
1117
|
+
if (map[key]) {
|
|
1118
|
+
map[key].push(row);
|
|
1119
|
+
} else if (stages.length > 0) {
|
|
1120
|
+
if (!map.__unknown) map.__unknown = [];
|
|
1121
|
+
map.__unknown.push(row);
|
|
1122
|
+
}
|
|
1123
|
+
}
|
|
1124
|
+
return map;
|
|
1125
|
+
}, [filteredData, stages, getStageFor]);
|
|
1126
|
+
const sortComparator = (0, import_react2.useMemo)(() => {
|
|
1127
|
+
if (!sortOptions || !resolvedSort) return null;
|
|
1128
|
+
const opt = sortOptions.find((s) => s.value === resolvedSort);
|
|
1129
|
+
return (opt == null ? void 0 : opt.comparator) || null;
|
|
1130
|
+
}, [sortOptions, resolvedSort]);
|
|
1131
|
+
const sortedBuckets = (0, import_react2.useMemo)(() => {
|
|
1132
|
+
if (!sortComparator) return buckets;
|
|
1133
|
+
const out = {};
|
|
1134
|
+
for (const key of Object.keys(buckets)) {
|
|
1135
|
+
out[key] = [...buckets[key]].sort(sortComparator);
|
|
1136
|
+
}
|
|
1137
|
+
return out;
|
|
1138
|
+
}, [buckets, sortComparator]);
|
|
1139
|
+
const activeChips = (0, import_react2.useMemo)(() => {
|
|
1140
|
+
const chips = [];
|
|
1141
|
+
for (const filter of filters || []) {
|
|
1142
|
+
const val = resolvedFilters[filter.name];
|
|
1143
|
+
if (!isFilterActive(filter, val)) continue;
|
|
1144
|
+
const type = filter.type || "select";
|
|
1145
|
+
const prefix = filter.chipLabel || filter.placeholder || filter.name;
|
|
1146
|
+
if (type === "multiselect") {
|
|
1147
|
+
const labelList = val.map((v) => {
|
|
1148
|
+
var _a2;
|
|
1149
|
+
return ((_a2 = filter.options.find((o) => o.value === v)) == null ? void 0 : _a2.label) || v;
|
|
1150
|
+
}).join(", ");
|
|
1151
|
+
chips.push({ key: filter.name, label: `${prefix}: ${labelList}` });
|
|
1152
|
+
} else if (type === "dateRange") {
|
|
1153
|
+
const parts = [];
|
|
1154
|
+
if (val.from) parts.push(`from ${formatDateChip(val.from)}`);
|
|
1155
|
+
if (val.to) parts.push(`to ${formatDateChip(val.to)}`);
|
|
1156
|
+
chips.push({ key: filter.name, label: `${prefix}: ${parts.join(" ")}` });
|
|
1157
|
+
} else {
|
|
1158
|
+
const opt = filter.options.find((o) => o.value === val);
|
|
1159
|
+
chips.push({ key: filter.name, label: `${prefix}: ${(opt == null ? void 0 : opt.label) || val}` });
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
return chips;
|
|
1163
|
+
}, [filters, resolvedFilters]);
|
|
1164
|
+
const partitioned = (0, import_react2.useMemo)(() => partitionFields(cardFields || []), [cardFields]);
|
|
1165
|
+
const dividers = (0, import_react2.useMemo)(() => resolveDividers(cardDividers, cardDensity), [cardDividers, cardDensity]);
|
|
1166
|
+
const resolvedMaxBody = maxBodyLines || (cardDensity === "comfortable" ? 5 : 3);
|
|
1167
|
+
const resolvedStageControl = stageControl || (cardDensity === "comfortable" ? "select" : "menu");
|
|
1168
|
+
const resolvedStageControlPlacement = stageControlPlacement || (resolvedStageControl === "menu" ? "inline" : "separateRow");
|
|
1169
|
+
const handleStageChangeRequest = (0, import_react2.useCallback)(
|
|
1170
|
+
(row, newStage, oldStage) => {
|
|
1171
|
+
var _a2;
|
|
1172
|
+
if (!newStage || newStage === oldStage) return;
|
|
1173
|
+
const targetStage = stagesByValue[newStage];
|
|
1174
|
+
if (!targetStage || !canStageReceiveRow(targetStage, row, canMove)) return;
|
|
1175
|
+
const rowId = row[rowIdField];
|
|
1176
|
+
if ((_a2 = targetStage.onEnterRequired) == null ? void 0 : _a2.render) {
|
|
1177
|
+
setTransitionPrompts((prev) => ({
|
|
1178
|
+
...prev,
|
|
1179
|
+
[rowId]: {
|
|
1180
|
+
row,
|
|
1181
|
+
fromStage: oldStage,
|
|
1182
|
+
toStage: newStage
|
|
1183
|
+
}
|
|
1184
|
+
}));
|
|
1185
|
+
return;
|
|
1186
|
+
}
|
|
1187
|
+
commitStageChange(row, newStage, oldStage);
|
|
1188
|
+
},
|
|
1189
|
+
[canMove, commitStageChange, rowIdField, stagesByValue]
|
|
1190
|
+
);
|
|
1191
|
+
const renderCardNode = (0, import_react2.useCallback)(
|
|
1192
|
+
(row, stage) => {
|
|
1193
|
+
var _a2;
|
|
1194
|
+
const rowId = row[rowIdField];
|
|
1195
|
+
const activePrompt = transitionPrompts[rowId];
|
|
1196
|
+
const promptStage = activePrompt ? stagesByValue[activePrompt.toStage] : null;
|
|
1197
|
+
if ((_a2 = promptStage == null ? void 0 : promptStage.onEnterRequired) == null ? void 0 : _a2.render) {
|
|
1198
|
+
return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Tile, { key: rowId, compact: cardDensity === "compact" }, promptStage.onEnterRequired.render({
|
|
1199
|
+
row: activePrompt.row,
|
|
1200
|
+
fromStage: activePrompt.fromStage,
|
|
1201
|
+
toStage: activePrompt.toStage,
|
|
1202
|
+
onConfirm: (result) => commitStageChange(activePrompt.row, activePrompt.toStage, activePrompt.fromStage, result),
|
|
1203
|
+
onCancel: () => clearTransitionPrompt(rowId)
|
|
1204
|
+
}));
|
|
1205
|
+
}
|
|
1206
|
+
if (renderCard) {
|
|
1207
|
+
return renderCard(row, {
|
|
1208
|
+
stage,
|
|
1209
|
+
isChanging: isStageChanging ? isStageChanging(row) : false,
|
|
1210
|
+
density: cardDensity,
|
|
1211
|
+
onStageChange: (newStage) => handleStageChangeRequest(row, newStage, stage.value)
|
|
1212
|
+
});
|
|
1213
|
+
}
|
|
1214
|
+
return /* @__PURE__ */ import_react2.default.createElement(
|
|
1215
|
+
KanbanCard,
|
|
1216
|
+
{
|
|
1217
|
+
key: rowId,
|
|
1218
|
+
row,
|
|
1219
|
+
rowId,
|
|
1220
|
+
stage,
|
|
1221
|
+
stages,
|
|
1222
|
+
fields: partitioned,
|
|
1223
|
+
density: cardDensity,
|
|
1224
|
+
dividers,
|
|
1225
|
+
bodyAs: cardBodyAs,
|
|
1226
|
+
maxBodyLines: resolvedMaxBody,
|
|
1227
|
+
stageControl: resolvedStageControl,
|
|
1228
|
+
stageControlPlacement: resolvedStageControlPlacement,
|
|
1229
|
+
canMove,
|
|
1230
|
+
onStageChangeRequest: handleStageChangeRequest,
|
|
1231
|
+
isChanging: isStageChanging ? isStageChanging(row) : false,
|
|
1232
|
+
selectable,
|
|
1233
|
+
selected: resolvedSelection.includes(rowId),
|
|
1234
|
+
onToggleSelect: handleToggleSelect,
|
|
1235
|
+
labels
|
|
1236
|
+
}
|
|
1237
|
+
);
|
|
1238
|
+
},
|
|
1239
|
+
[
|
|
1240
|
+
clearTransitionPrompt,
|
|
1241
|
+
commitStageChange,
|
|
1242
|
+
renderCard,
|
|
1243
|
+
rowIdField,
|
|
1244
|
+
partitioned,
|
|
1245
|
+
cardDensity,
|
|
1246
|
+
dividers,
|
|
1247
|
+
resolvedMaxBody,
|
|
1248
|
+
resolvedStageControl,
|
|
1249
|
+
resolvedStageControlPlacement,
|
|
1250
|
+
canMove,
|
|
1251
|
+
isStageChanging,
|
|
1252
|
+
selectable,
|
|
1253
|
+
resolvedSelection,
|
|
1254
|
+
handleStageChangeRequest,
|
|
1255
|
+
handleToggleSelect,
|
|
1256
|
+
labels,
|
|
1257
|
+
stages,
|
|
1258
|
+
stagesByValue,
|
|
1259
|
+
transitionPrompts
|
|
1260
|
+
]
|
|
1261
|
+
);
|
|
1262
|
+
const totalMatching = filteredData.length;
|
|
1263
|
+
const selectedCount = resolvedSelection.length;
|
|
1264
|
+
const singular = ((recordLabel == null ? void 0 : recordLabel.singular) || "card").toLowerCase();
|
|
1265
|
+
const plural = ((recordLabel == null ? void 0 : recordLabel.plural) || "cards").toLowerCase();
|
|
1266
|
+
const countLabel = (n) => n === 1 ? singular : plural;
|
|
1267
|
+
const resolvedSearchPlaceholder = searchPlaceholder ?? ((recordLabel == null ? void 0 : recordLabel.plural) ? `Search ${plural}...` : labels.search);
|
|
1268
|
+
const selectionBarProps = {
|
|
1269
|
+
selectedIds: resolvedSelection,
|
|
1270
|
+
selectedCount,
|
|
1271
|
+
displayCount: totalMatching,
|
|
1272
|
+
countLabel,
|
|
1273
|
+
allSelected: selectedCount >= totalMatching && totalMatching > 0,
|
|
1274
|
+
onSelectAll: () => {
|
|
1275
|
+
const allIds = filteredData.map((r) => r[rowIdField]);
|
|
1276
|
+
if (onSelectionChange) onSelectionChange(allIds);
|
|
1277
|
+
if (selectedIds == null) setInternalSelection(allIds);
|
|
1278
|
+
},
|
|
1279
|
+
onDeselectAll: () => {
|
|
1280
|
+
if (onSelectionChange) onSelectionChange([]);
|
|
1281
|
+
if (selectedIds == null) setInternalSelection([]);
|
|
1282
|
+
},
|
|
1283
|
+
selectionActions: selectionActions || [],
|
|
1284
|
+
labels
|
|
1285
|
+
};
|
|
1286
|
+
const mainContent = error ? renderErrorState ? renderErrorState({
|
|
1287
|
+
error,
|
|
1288
|
+
title: labels.errorTitle,
|
|
1289
|
+
message: typeof error === "string" ? error : labels.errorMessage
|
|
1290
|
+
}) : /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Alert, { variant: "danger", title: labels.errorTitle }, typeof error === "string" ? error : labels.errorMessage) : loading && data.length === 0 ? renderLoadingState ? renderLoadingState({ label: labels.loading }) : /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.LoadingSpinner, { size: "md", layout: "centered", label: labels.loading }) : filteredData.length === 0 ? renderEmptyState ? renderEmptyState({
|
|
1291
|
+
title: labels.emptyTitle,
|
|
1292
|
+
message: labels.emptyMessage
|
|
1293
|
+
}) : /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Tile, null, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.EmptyState, { title: labels.emptyTitle, layout: "vertical", reverseOrder: true }, /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Text, null, labels.emptyMessage))) : /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "row", gap: "sm", wrap: "nowrap" }, stages.map((stage) => {
|
|
1294
|
+
const stageRows = sortedBuckets[stage.value] || [];
|
|
1295
|
+
const meta = stageMeta == null ? void 0 : stageMeta[stage.value];
|
|
1296
|
+
const isExpanded = resolvedExpanded.includes(stage.value);
|
|
1297
|
+
const clamp = isExpanded ? maxCardsExpanded : maxCardsPerColumn;
|
|
1298
|
+
const visibleRows = stageRows.slice(0, clamp);
|
|
1299
|
+
const isCollapsed = resolvedCollapsed.includes(stage.value);
|
|
1300
|
+
return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.AutoGrid, { key: stage.value, columnWidth: isCollapsed ? 72 : effectiveColumnWidth }, /* @__PURE__ */ import_react2.default.createElement(
|
|
1301
|
+
KanbanColumn,
|
|
1302
|
+
{
|
|
1303
|
+
stage,
|
|
1304
|
+
rows: visibleRows,
|
|
1305
|
+
bucketCount: stageRows.length,
|
|
1306
|
+
totalCount: meta == null ? void 0 : meta.totalCount,
|
|
1307
|
+
hasMore: meta == null ? void 0 : meta.hasMore,
|
|
1308
|
+
loading: meta == null ? void 0 : meta.loading,
|
|
1309
|
+
error: meta == null ? void 0 : meta.error,
|
|
1310
|
+
onLoadMore,
|
|
1311
|
+
expanded: isExpanded,
|
|
1312
|
+
onToggleExpanded: () => handleExpanded(stage.value),
|
|
1313
|
+
collapsed: isCollapsed,
|
|
1314
|
+
onToggleCollapsed: () => handleCollapsed(stage.value),
|
|
1315
|
+
columnFooter,
|
|
1316
|
+
countDisplay,
|
|
1317
|
+
labels
|
|
1318
|
+
},
|
|
1319
|
+
visibleRows.map((row) => renderCardNode(row, stage))
|
|
1320
|
+
));
|
|
1321
|
+
}));
|
|
1322
|
+
return /* @__PURE__ */ import_react2.default.createElement(import_ui_extensions2.Flex, { direction: "column", gap: "sm" }, /* @__PURE__ */ import_react2.default.createElement(
|
|
1323
|
+
KanbanToolbar,
|
|
1324
|
+
{
|
|
1325
|
+
showSearch: searchEnabled,
|
|
1326
|
+
searchValue: resolvedSearch,
|
|
1327
|
+
searchPlaceholder: resolvedSearchPlaceholder,
|
|
1328
|
+
onSearchChange: handleSearch,
|
|
1329
|
+
filters,
|
|
1330
|
+
filterValues: resolvedFilters,
|
|
1331
|
+
onFilterChange: handleFilter,
|
|
1332
|
+
filterInlineLimit,
|
|
1333
|
+
showFilterBadges,
|
|
1334
|
+
showClearFiltersButton,
|
|
1335
|
+
activeChips,
|
|
1336
|
+
onFilterRemove: handleFilterRemove,
|
|
1337
|
+
sortOptions,
|
|
1338
|
+
sortValue: resolvedSort,
|
|
1339
|
+
onSortChange: handleSort,
|
|
1340
|
+
metrics,
|
|
1341
|
+
showMetrics: resolvedShowMetrics,
|
|
1342
|
+
onToggleMetrics: toggleMetrics,
|
|
1343
|
+
labels
|
|
1344
|
+
}
|
|
1345
|
+
), showSelectionBar && selectable && selectedCount > 0 ? renderSelectionBar ? renderSelectionBar(selectionBarProps) : /* @__PURE__ */ import_react2.default.createElement(DefaultSelectionBar, { ...selectionBarProps }) : null, mainContent);
|
|
1346
|
+
};
|
|
1347
|
+
|
|
1348
|
+
// packages/kanban/src/KanbanCardActions.jsx
|
|
1349
|
+
var import_react3 = __toESM(require("react"));
|
|
1350
|
+
var import_ui_extensions3 = require("@hubspot/ui-extensions");
|
|
1351
|
+
var renderButton = (action, display, size) => {
|
|
1352
|
+
const { key, label, icon, tooltip, variant = "transparent", disabled, onClick, href } = action;
|
|
1353
|
+
const buttonProps = {
|
|
1354
|
+
key: key || label,
|
|
1355
|
+
variant,
|
|
1356
|
+
size,
|
|
1357
|
+
disabled,
|
|
1358
|
+
tooltip: tooltip || label
|
|
1359
|
+
};
|
|
1360
|
+
if (href) buttonProps.href = typeof href === "string" ? href : href;
|
|
1361
|
+
if (onClick) buttonProps.onClick = onClick;
|
|
1362
|
+
if (display === "icon") {
|
|
1363
|
+
return /* @__PURE__ */ import_react3.default.createElement(import_ui_extensions3.Button, { ...buttonProps }, icon ? /* @__PURE__ */ import_react3.default.createElement(import_ui_extensions3.Icon, { name: icon, size: "sm", screenReaderText: label }) : label);
|
|
1364
|
+
}
|
|
1365
|
+
if (display === "label") {
|
|
1366
|
+
return /* @__PURE__ */ import_react3.default.createElement(import_ui_extensions3.Button, { ...buttonProps }, label);
|
|
1367
|
+
}
|
|
1368
|
+
return /* @__PURE__ */ import_react3.default.createElement(import_ui_extensions3.Button, { ...buttonProps }, /* @__PURE__ */ import_react3.default.createElement(import_ui_extensions3.Flex, { direction: "row", align: "center", gap: "xs" }, icon ? /* @__PURE__ */ import_react3.default.createElement(import_ui_extensions3.Icon, { name: icon, size: "sm", screenReaderText: label }) : null, /* @__PURE__ */ import_react3.default.createElement(import_ui_extensions3.Text, { variant: "microcopy" }, label)));
|
|
1369
|
+
};
|
|
1370
|
+
var KanbanCardActions = ({
|
|
1371
|
+
actions = [],
|
|
1372
|
+
display = "icon",
|
|
1373
|
+
// Default to xs — the Button's own padding at sm visibly pushes icons away
|
|
1374
|
+
// from Tile edges. Icon glyph inside stays pinned to size="sm" in renderButton
|
|
1375
|
+
// so the icon itself doesn't shrink, only the hit-target/button padding does.
|
|
1376
|
+
size = "xs",
|
|
1377
|
+
align = "end",
|
|
1378
|
+
gap = "flush",
|
|
1379
|
+
separator = "none",
|
|
1380
|
+
overflowAfter,
|
|
1381
|
+
overflowLabel = "More"
|
|
1382
|
+
}) => {
|
|
1383
|
+
const visible = actions.filter((a) => a.visible !== false);
|
|
1384
|
+
if (visible.length === 0) return null;
|
|
1385
|
+
const cutoff = typeof overflowAfter === "number" ? Math.max(0, overflowAfter) : visible.length;
|
|
1386
|
+
const primary = visible.slice(0, cutoff);
|
|
1387
|
+
const overflow = visible.slice(cutoff);
|
|
1388
|
+
const renderedPrimary = primary.map((action, idx) => {
|
|
1389
|
+
const button = renderButton(action, display, size);
|
|
1390
|
+
if (separator === "pipe" && idx > 0) {
|
|
1391
|
+
return /* @__PURE__ */ import_react3.default.createElement(import_react3.default.Fragment, { key: `${action.key || action.label}-sep` }, /* @__PURE__ */ import_react3.default.createElement(import_ui_extensions3.Text, { variant: "microcopy" }, "|"), button);
|
|
1392
|
+
}
|
|
1393
|
+
return button;
|
|
1394
|
+
});
|
|
1395
|
+
const renderedOverflow = overflow.length > 0 ? /* @__PURE__ */ import_react3.default.createElement(import_ui_extensions3.Dropdown, { variant: "transparent", buttonText: overflowLabel, buttonSize: size, key: "overflow" }, overflow.map((action) => /* @__PURE__ */ import_react3.default.createElement(
|
|
1396
|
+
import_ui_extensions3.Dropdown.ButtonItem,
|
|
1397
|
+
{
|
|
1398
|
+
key: action.key || action.label,
|
|
1399
|
+
onClick: action.onClick
|
|
1400
|
+
},
|
|
1401
|
+
action.label
|
|
1402
|
+
))) : null;
|
|
1403
|
+
const justify = align === "end" ? "end" : align === "between" ? "between" : "start";
|
|
1404
|
+
return /* @__PURE__ */ import_react3.default.createElement(import_ui_extensions3.Flex, { direction: "row", align: "end", justify, gap }, renderedPrimary, renderedOverflow);
|
|
1405
|
+
};
|
|
1406
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1407
|
+
0 && (module.exports = {
|
|
1408
|
+
Kanban,
|
|
1409
|
+
KanbanCardActions
|
|
1410
|
+
});
|