@marktiderman/genesis-ui 2.5.0 → 2.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/dist/components/data/index.d.ts +38 -2
- package/dist/components/data/index.js +114 -22
- package/dist/components/data/index.js.map +1 -1
- package/dist/components/sheet-grid/sheet-grid.d.ts +108 -0
- package/dist/components/sheet-grid/sheet-grid.js +434 -0
- package/dist/components/sheet-grid/sheet-grid.js.map +1 -0
- package/dist/index.js +4 -4
- package/package.json +14 -3
|
@@ -179,8 +179,12 @@ interface DataFiltersProps {
|
|
|
179
179
|
onClearAll?: () => void;
|
|
180
180
|
/** Expose the search input ref for keyboard nav "/" shortcut */
|
|
181
181
|
searchInputRef?: React.RefObject<HTMLInputElement | null>;
|
|
182
|
+
/** Show the search box. Default `true`; turn off when the data source can't search. */
|
|
183
|
+
showSearch?: boolean;
|
|
184
|
+
/** Show the sort select. Default `true`; turn off when the data source can't sort. */
|
|
185
|
+
showSort?: boolean;
|
|
182
186
|
}
|
|
183
|
-
declare function DataFilters({ search, onSearchChange, searchPlaceholder, sort, onSortChange, sortOptions, filters, statusChips, savedViews, onLoadView, onSaveView, onDeleteView, canSaveViews, onViewSaved, onClearAll, searchInputRef, }: DataFiltersProps): React.JSX.Element;
|
|
187
|
+
declare function DataFilters({ search, onSearchChange, searchPlaceholder, sort, onSortChange, sortOptions, filters, statusChips, savedViews, onLoadView, onSaveView, onDeleteView, canSaveViews, onViewSaved, onClearAll, searchInputRef, showSearch, showSort, }: DataFiltersProps): React.JSX.Element;
|
|
184
188
|
|
|
185
189
|
interface DataGridProps<T> {
|
|
186
190
|
items: T[];
|
|
@@ -309,14 +313,36 @@ interface KanbanColumn<T> {
|
|
|
309
313
|
/** Raw bg color class (used if status not provided) */
|
|
310
314
|
bgColor?: string;
|
|
311
315
|
items: T[];
|
|
316
|
+
/** How many items the column holds in all, when `items` is one page of them. */
|
|
317
|
+
total?: number;
|
|
312
318
|
}
|
|
313
319
|
interface KanbanBoardProps<T> {
|
|
314
320
|
columns: KanbanColumn<T>[];
|
|
315
321
|
renderCard: (item: T) => ReactNode;
|
|
316
322
|
emptyMessage?: string;
|
|
317
323
|
className?: string;
|
|
324
|
+
/** A stable key per item. Without one, cards are keyed by position. */
|
|
325
|
+
getKey?: (item: T) => string;
|
|
326
|
+
/**
|
|
327
|
+
* Move an item to another column. When set, cards can be dragged between
|
|
328
|
+
* columns, and each card gets a "Move to…" menu that does the same from
|
|
329
|
+
* the keyboard. A rejected promise leaves the card where it was.
|
|
330
|
+
*/
|
|
331
|
+
onMoveItem?: (item: T, toColumn: string) => void | Promise<void>;
|
|
332
|
+
/** A name for an item, for the move menu's label ("Move Ana Ruiz"). */
|
|
333
|
+
getItemLabel?: (item: T) => string;
|
|
334
|
+
/** Under a column's cards, e.g. a "Load more" button. */
|
|
335
|
+
renderColumnFooter?: (column: KanbanColumn<T>) => ReactNode;
|
|
318
336
|
}
|
|
319
|
-
|
|
337
|
+
/**
|
|
338
|
+
* Columns of cards, optionally movable between columns.
|
|
339
|
+
*
|
|
340
|
+
* Why the keyboard path isn't Radix (docs/FRAMEWORK.md rule 3): Radix has no
|
|
341
|
+
* drag and drop, and native drag and drop can't be done from a keyboard. The
|
|
342
|
+
* "Move to…" menu (a Radix DropdownMenu) is the equivalent path for keyboard
|
|
343
|
+
* and screen-reader users, so dragging is never the only way to move a card.
|
|
344
|
+
*/
|
|
345
|
+
declare function KanbanBoard<T>({ columns, renderCard, emptyMessage, className, getKey, onMoveItem, getItemLabel, renderColumnFooter, }: KanbanBoardProps<T>): React.JSX.Element;
|
|
320
346
|
|
|
321
347
|
interface DataListProps<T> {
|
|
322
348
|
items: T[];
|
|
@@ -648,6 +674,16 @@ interface ResourcePageProps<T extends Record<string, unknown> = Record<string, u
|
|
|
648
674
|
onDeleteView?: (id: string) => void;
|
|
649
675
|
/** Show the "save current view" affordance. */
|
|
650
676
|
canSaveViews?: boolean;
|
|
677
|
+
/**
|
|
678
|
+
* What the data source can do. A control it can't serve is hidden rather
|
|
679
|
+
* than left to error (DATA-STUDIO.md decision 9): `search: false` hides the
|
|
680
|
+
* search box, `sort: false` the sort select. Pass a provider's
|
|
681
|
+
* `capabilities()` straight in. Omitted, both show, as before.
|
|
682
|
+
*/
|
|
683
|
+
capabilities?: {
|
|
684
|
+
search?: boolean;
|
|
685
|
+
sort?: boolean;
|
|
686
|
+
};
|
|
651
687
|
/** Show the column-visibility dropdown. DataTable's default is `true`. */
|
|
652
688
|
columnVisibility?: boolean;
|
|
653
689
|
/** Persist key for the user's column-visibility picks. */
|
|
@@ -11,7 +11,7 @@ import { Progress } from '../../chunk-W5Y3LG5K.js';
|
|
|
11
11
|
import { Input } from '../../chunk-QSXVL5UB.js';
|
|
12
12
|
import { Checkbox } from '../../chunk-SEBU6JCK.js';
|
|
13
13
|
import { Card, CardContent, CardHeader, CardTitle } from '../../chunk-FN3FZVUR.js';
|
|
14
|
-
import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuCheckboxItem, DropdownMenuSeparator, DropdownMenuItem } from '../../chunk-QJ6P2DW6.js';
|
|
14
|
+
import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuCheckboxItem, DropdownMenuSeparator, DropdownMenuItem, DropdownMenuLabel } from '../../chunk-QJ6P2DW6.js';
|
|
15
15
|
import '../../chunk-B2GIBUQD.js';
|
|
16
16
|
import { Badge } from '../../chunk-NPNADZHM.js';
|
|
17
17
|
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '../../chunk-FK42ZGGV.js';
|
|
@@ -39,7 +39,7 @@ import { DefaultLink, defaultNavigate } from '../../chunk-CDYZRZ6U.js';
|
|
|
39
39
|
export { DefaultLink, defaultNavigate, isPathActive, resolveNavigation } from '../../chunk-CDYZRZ6U.js';
|
|
40
40
|
import { useKeyboardNavigation } from '../../chunk-YJ6MPS6U.js';
|
|
41
41
|
import { useIsMobile } from '../../chunk-VFO3I7PV.js';
|
|
42
|
-
import { AlertTriangle, RefreshCw, ChevronDown, Check, Search, X, ArrowUpDown, Bookmark, Trash2, BookmarkPlus, Loader2, Columns3, ArrowUp, ArrowDown, ChevronLeft, ChevronRight, ExternalLink, Pencil, ChevronsUpDown, ImageIcon, Upload, AlertCircle, FileIcon, Plus } from 'lucide-react';
|
|
42
|
+
import { AlertTriangle, RefreshCw, ChevronDown, Check, Search, X, ArrowUpDown, Bookmark, Trash2, BookmarkPlus, Loader2, Columns3, ArrowUp, ArrowDown, ChevronLeft, ChevronRight, ExternalLink, ArrowRightLeft, Pencil, ChevronsUpDown, ImageIcon, Upload, AlertCircle, FileIcon, Plus } from 'lucide-react';
|
|
43
43
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
44
44
|
import { useState, useRef, useEffect, useCallback, useMemo, Fragment as Fragment$1 } from 'react';
|
|
45
45
|
import { formatDistanceToNow, format } from 'date-fns';
|
|
@@ -279,7 +279,9 @@ function DataFilters({
|
|
|
279
279
|
canSaveViews = false,
|
|
280
280
|
onViewSaved,
|
|
281
281
|
onClearAll,
|
|
282
|
-
searchInputRef
|
|
282
|
+
searchInputRef,
|
|
283
|
+
showSearch = true,
|
|
284
|
+
showSort = true
|
|
283
285
|
}) {
|
|
284
286
|
const internalSearchRef = useRef(null);
|
|
285
287
|
const effectiveSearchRef = searchInputRef || internalSearchRef;
|
|
@@ -335,8 +337,8 @@ function DataFilters({
|
|
|
335
337
|
})
|
|
336
338
|
}
|
|
337
339
|
),
|
|
338
|
-
/* @__PURE__ */ jsxs("div", { className: "flex flex-col sm:flex-row gap-2", children: [
|
|
339
|
-
/* @__PURE__ */ jsxs("div", { className: "relative flex-1", children: [
|
|
340
|
+
(showSearch || showSort) && /* @__PURE__ */ jsxs("div", { className: "flex flex-col sm:flex-row gap-2", children: [
|
|
341
|
+
showSearch && /* @__PURE__ */ jsxs("div", { className: "relative flex-1", children: [
|
|
340
342
|
/* @__PURE__ */ jsx(Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" }),
|
|
341
343
|
/* @__PURE__ */ jsx(
|
|
342
344
|
Input,
|
|
@@ -357,7 +359,7 @@ function DataFilters({
|
|
|
357
359
|
}
|
|
358
360
|
)
|
|
359
361
|
] }),
|
|
360
|
-
/* @__PURE__ */ jsxs(Select, { value: sort, onValueChange: onSortChange, children: [
|
|
362
|
+
showSort && /* @__PURE__ */ jsxs(Select, { value: sort, onValueChange: onSortChange, children: [
|
|
361
363
|
/* @__PURE__ */ jsxs(SelectTrigger, { className: "w-full sm:w-[180px] shrink-0", children: [
|
|
362
364
|
/* @__PURE__ */ jsx(ArrowUpDown, { className: "h-3.5 w-3.5 mr-1.5 text-muted-foreground" }),
|
|
363
365
|
/* @__PURE__ */ jsx(SelectValue, { placeholder: "Sort by..." })
|
|
@@ -1414,12 +1416,39 @@ var statusColorMap = {
|
|
|
1414
1416
|
destructive: { color: "text-destructive", bgColor: "bg-destructive/15" },
|
|
1415
1417
|
primary: { color: "text-primary", bgColor: "bg-primary/15" }
|
|
1416
1418
|
};
|
|
1419
|
+
var DRAG_TYPE = "application/x-genesis-kanban-item";
|
|
1417
1420
|
function KanbanBoard({
|
|
1418
1421
|
columns,
|
|
1419
1422
|
renderCard,
|
|
1420
1423
|
emptyMessage = "No items",
|
|
1421
|
-
className
|
|
1424
|
+
className,
|
|
1425
|
+
getKey,
|
|
1426
|
+
onMoveItem,
|
|
1427
|
+
getItemLabel,
|
|
1428
|
+
renderColumnFooter
|
|
1422
1429
|
}) {
|
|
1430
|
+
const [dragging, setDragging] = useState(null);
|
|
1431
|
+
const [over, setOver] = useState(null);
|
|
1432
|
+
const keyOf = (item, index) => getKey ? getKey(item) : String(index);
|
|
1433
|
+
const move = (item, to) => {
|
|
1434
|
+
void Promise.resolve(onMoveItem?.(item, to)).catch(() => {
|
|
1435
|
+
});
|
|
1436
|
+
};
|
|
1437
|
+
const onDrop = (event, to) => {
|
|
1438
|
+
event.preventDefault();
|
|
1439
|
+
setOver(null);
|
|
1440
|
+
const key = event.dataTransfer.getData(DRAG_TYPE) || dragging?.key;
|
|
1441
|
+
setDragging(null);
|
|
1442
|
+
if (!key || !getKey) return;
|
|
1443
|
+
for (const column of columns) {
|
|
1444
|
+
const item = column.items.find((i) => getKey(i) === key);
|
|
1445
|
+
if (item) {
|
|
1446
|
+
if (column.key !== to) move(item, to);
|
|
1447
|
+
return;
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
};
|
|
1451
|
+
const movable = Boolean(onMoveItem && getKey);
|
|
1423
1452
|
return /* @__PURE__ */ jsx(
|
|
1424
1453
|
"div",
|
|
1425
1454
|
{
|
|
@@ -1429,20 +1458,80 @@ function KanbanBoard({
|
|
|
1429
1458
|
),
|
|
1430
1459
|
children: columns.map((column) => {
|
|
1431
1460
|
const resolved = column.status ? statusColorMap[column.status] : { color: column.color ?? "", bgColor: column.bgColor ?? "" };
|
|
1432
|
-
return /* @__PURE__ */ jsxs(
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1461
|
+
return /* @__PURE__ */ jsxs(
|
|
1462
|
+
"section",
|
|
1463
|
+
{
|
|
1464
|
+
"aria-label": column.label,
|
|
1465
|
+
"data-kanban-column": column.key,
|
|
1466
|
+
className: cn(
|
|
1467
|
+
"flex flex-col gap-3 rounded-lg",
|
|
1468
|
+
movable && over === column.key && dragging?.from !== column.key && "bg-accent/40 ring-2 ring-primary/40"
|
|
1469
|
+
),
|
|
1470
|
+
onDragOver: movable ? (e) => {
|
|
1471
|
+
if (!dragging) return;
|
|
1472
|
+
e.preventDefault();
|
|
1473
|
+
e.dataTransfer.dropEffect = "move";
|
|
1474
|
+
if (over !== column.key) setOver(column.key);
|
|
1475
|
+
} : void 0,
|
|
1476
|
+
onDragLeave: movable ? (e) => {
|
|
1477
|
+
if (!e.currentTarget.contains(e.relatedTarget)) setOver(null);
|
|
1478
|
+
} : void 0,
|
|
1479
|
+
onDrop: movable ? (e) => onDrop(e, column.key) : void 0,
|
|
1480
|
+
children: [
|
|
1481
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1482
|
+
/* @__PURE__ */ jsx("span", { className: cn("text-sm font-semibold", resolved.color), children: column.label }),
|
|
1483
|
+
/* @__PURE__ */ jsx(
|
|
1484
|
+
Badge,
|
|
1485
|
+
{
|
|
1486
|
+
variant: "secondary",
|
|
1487
|
+
className: cn("text-[10px] px-1.5 py-0", resolved.bgColor),
|
|
1488
|
+
children: column.total ?? column.items.length
|
|
1489
|
+
}
|
|
1490
|
+
)
|
|
1491
|
+
] }),
|
|
1492
|
+
/* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2 min-h-[80px]", children: column.items.length === 0 ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center rounded-lg border border-dashed border-border p-6 text-xs text-muted-foreground", children: emptyMessage }) : column.items.map((item, i) => {
|
|
1493
|
+
const key = keyOf(item, i);
|
|
1494
|
+
return /* @__PURE__ */ jsxs(
|
|
1495
|
+
"div",
|
|
1496
|
+
{
|
|
1497
|
+
className: cn("animate-fade-in", movable && "group relative", dragging?.key === key && "opacity-50"),
|
|
1498
|
+
style: { animationDelay: `${i * 30}ms`, animationFillMode: "both" },
|
|
1499
|
+
draggable: movable || void 0,
|
|
1500
|
+
onDragStart: movable ? (e) => {
|
|
1501
|
+
e.dataTransfer.setData(DRAG_TYPE, key);
|
|
1502
|
+
e.dataTransfer.effectAllowed = "move";
|
|
1503
|
+
setDragging({ key, from: column.key });
|
|
1504
|
+
} : void 0,
|
|
1505
|
+
onDragEnd: movable ? () => {
|
|
1506
|
+
setDragging(null);
|
|
1507
|
+
setOver(null);
|
|
1508
|
+
} : void 0,
|
|
1509
|
+
children: [
|
|
1510
|
+
renderCard(item),
|
|
1511
|
+
movable && columns.length > 1 && /* @__PURE__ */ jsxs(DropdownMenu, { children: [
|
|
1512
|
+
/* @__PURE__ */ jsx(
|
|
1513
|
+
DropdownMenuTrigger,
|
|
1514
|
+
{
|
|
1515
|
+
"aria-label": `Move ${getItemLabel?.(item) ?? "card"}`,
|
|
1516
|
+
className: "absolute right-1.5 top-1.5 rounded p-1 text-muted-foreground hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
1517
|
+
children: /* @__PURE__ */ jsx(ArrowRightLeft, { className: "h-3.5 w-3.5", "aria-hidden": true })
|
|
1518
|
+
}
|
|
1519
|
+
),
|
|
1520
|
+
/* @__PURE__ */ jsxs(DropdownMenuContent, { align: "end", children: [
|
|
1521
|
+
/* @__PURE__ */ jsx(DropdownMenuLabel, { children: "Move to" }),
|
|
1522
|
+
columns.filter((c) => c.key !== column.key).map((c) => /* @__PURE__ */ jsx(DropdownMenuItem, { onSelect: () => move(item, c.key), children: c.label }, c.key))
|
|
1523
|
+
] })
|
|
1524
|
+
] })
|
|
1525
|
+
]
|
|
1526
|
+
},
|
|
1527
|
+
key
|
|
1528
|
+
);
|
|
1529
|
+
}) }),
|
|
1530
|
+
renderColumnFooter?.(column)
|
|
1531
|
+
]
|
|
1532
|
+
},
|
|
1533
|
+
column.key
|
|
1534
|
+
);
|
|
1446
1535
|
})
|
|
1447
1536
|
}
|
|
1448
1537
|
);
|
|
@@ -2614,6 +2703,7 @@ function ResourcePage(props) {
|
|
|
2614
2703
|
renderTable: renderTableProp,
|
|
2615
2704
|
gridCols: gridColsProp,
|
|
2616
2705
|
keyboardNavigation = true,
|
|
2706
|
+
capabilities,
|
|
2617
2707
|
toolbar,
|
|
2618
2708
|
emptyState,
|
|
2619
2709
|
resourceOptions,
|
|
@@ -2999,7 +3089,9 @@ function ResourcePage(props) {
|
|
|
2999
3089
|
onSaveView,
|
|
3000
3090
|
onDeleteView,
|
|
3001
3091
|
canSaveViews,
|
|
3002
|
-
onClearAll: page.hasActiveFilters ? handleClearFilters : void 0
|
|
3092
|
+
onClearAll: page.hasActiveFilters ? handleClearFilters : void 0,
|
|
3093
|
+
showSearch: capabilities?.search !== false,
|
|
3094
|
+
showSort: capabilities?.sort !== false
|
|
3003
3095
|
},
|
|
3004
3096
|
slotProps?.filters
|
|
3005
3097
|
),
|