@marktiderman/genesis-ui 2.4.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.
@@ -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';
@@ -27,7 +27,7 @@ import '../../chunk-F47U5KNW.js';
27
27
  import { DetailPage } from '../../chunk-AFZ76Q26.js';
28
28
  import '../../chunk-D2LIXA2Y.js';
29
29
  import '../../chunk-AVFZ4P4B.js';
30
- import { titleCase, useResourcePage } from '../../chunk-GDFNAHI4.js';
30
+ import { titleCase, useResourcePage } from '../../chunk-IXGAEECT.js';
31
31
  export { useSavedViews } from '../../chunk-GCQFVMPI.js';
32
32
  import '../../chunk-FTTNGGLA.js';
33
33
  import { useViewSettings } from '../../chunk-OZ7ZAYCS.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';
@@ -61,7 +61,7 @@ function DataPageShell({
61
61
  emptyMessage,
62
62
  emptyAction,
63
63
  hasError = false,
64
- errorMessage,
64
+ errorMessage: errorMessage2,
65
65
  onRetry,
66
66
  count,
67
67
  totalCount,
@@ -148,7 +148,7 @@ function DataPageShell({
148
148
  ) : hasError ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-16 text-center", children: [
149
149
  /* @__PURE__ */ jsx(AlertTriangle, { className: "h-10 w-10 text-destructive mb-3" }),
150
150
  /* @__PURE__ */ jsx("p", { className: "mb-1 text-sm font-medium text-foreground", children: "Something went wrong" }),
151
- /* @__PURE__ */ jsx("p", { className: "mb-4 text-sm text-muted-foreground", children: errorMessage || `Failed to load ${entity}s. Please try again.` }),
151
+ /* @__PURE__ */ jsx("p", { className: "mb-4 text-sm text-muted-foreground", children: errorMessage2 || `Failed to load ${entity}s. Please try again.` }),
152
152
  onRetry && /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: onRetry, children: [
153
153
  /* @__PURE__ */ jsx(RefreshCw, { className: "mr-1 h-4 w-4" }),
154
154
  " Retry"
@@ -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..." })
@@ -639,6 +641,9 @@ function getCellClasses(col) {
639
641
  const hide = col.hideBelow ? col.hideBelow === "sm" ? "hidden sm:table-cell" : col.hideBelow === "md" ? "hidden md:table-cell" : col.hideBelow === "lg" ? "hidden lg:table-cell" : "hidden xl:table-cell" : "";
640
642
  return cn(col.width, hide);
641
643
  }
644
+ function errorMessage(error) {
645
+ return error instanceof Error ? error.message : String(error);
646
+ }
642
647
  function EditableCell({
643
648
  item,
644
649
  column,
@@ -646,13 +651,49 @@ function EditableCell({
646
651
  }) {
647
652
  const [editing, setEditing] = useState(false);
648
653
  const [draft, setDraft] = useState(String(value ?? ""));
654
+ const [saving, setSaving] = useState(false);
655
+ const [error, setError] = useState(null);
656
+ const inFlight = useRef(false);
657
+ const failedDraft = useRef(null);
649
658
  const editable = column.editable;
659
+ const close = useCallback(() => {
660
+ setEditing(false);
661
+ setError(null);
662
+ failedDraft.current = null;
663
+ }, []);
650
664
  const commit = useCallback(
651
665
  (v) => {
652
- editable.onSave(item, v);
653
- setEditing(false);
666
+ if (inFlight.current) return;
667
+ let result;
668
+ try {
669
+ result = editable.onSave(item, v);
670
+ } catch (e) {
671
+ setError(errorMessage(e));
672
+ failedDraft.current = v;
673
+ return;
674
+ }
675
+ if (!result || typeof result.then !== "function") {
676
+ close();
677
+ return;
678
+ }
679
+ inFlight.current = true;
680
+ setSaving(true);
681
+ setError(null);
682
+ result.then(
683
+ () => {
684
+ inFlight.current = false;
685
+ setSaving(false);
686
+ close();
687
+ },
688
+ (e) => {
689
+ inFlight.current = false;
690
+ setSaving(false);
691
+ setError(errorMessage(e));
692
+ failedDraft.current = v;
693
+ }
694
+ );
654
695
  },
655
- [editable, item]
696
+ [editable, item, close]
656
697
  );
657
698
  if (!editing) {
658
699
  return /* @__PURE__ */ jsx(
@@ -662,32 +703,80 @@ function EditableCell({
662
703
  onClick: (e) => {
663
704
  e.stopPropagation();
664
705
  setDraft(String(value ?? ""));
706
+ setError(null);
707
+ failedDraft.current = null;
665
708
  setEditing(true);
666
709
  },
667
710
  children: column.render ? column.render(item, value) : String(value ?? "\u2014")
668
711
  }
669
712
  );
670
713
  }
714
+ const feedback = error ? /* @__PURE__ */ jsx("p", { role: "alert", className: "mt-1 text-[11px] leading-snug text-destructive", children: error }) : null;
671
715
  if (editable.type === "select" && editable.options) {
672
- return /* @__PURE__ */ jsx("div", { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsxs(Select, { defaultValue: draft, onValueChange: (v) => commit(v), children: [
673
- /* @__PURE__ */ jsx(SelectTrigger, { className: "h-7 text-xs w-28", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
674
- /* @__PURE__ */ jsx(SelectContent, { children: editable.options.map((opt) => /* @__PURE__ */ jsx(SelectItem, { value: opt, children: opt }, opt)) })
675
- ] }) });
676
- }
677
- return /* @__PURE__ */ jsx("div", { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx(
678
- Input,
679
- {
680
- autoFocus: true,
681
- className: "h-7 text-xs w-full",
682
- value: draft,
683
- onChange: (e) => setDraft(e.target.value),
684
- onBlur: () => commit(draft),
685
- onKeyDown: (e) => {
686
- if (e.key === "Enter") commit(draft);
687
- if (e.key === "Escape") setEditing(false);
716
+ return /* @__PURE__ */ jsxs(
717
+ "div",
718
+ {
719
+ onClick: (e) => e.stopPropagation(),
720
+ onKeyDown: (e) => {
721
+ if (e.key === "Escape" && !inFlight.current) close();
722
+ },
723
+ "aria-busy": saving || void 0,
724
+ children: [
725
+ /* @__PURE__ */ jsxs(
726
+ Select,
727
+ {
728
+ value: draft,
729
+ disabled: saving,
730
+ onValueChange: (v) => {
731
+ setDraft(v);
732
+ commit(v);
733
+ },
734
+ children: [
735
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "h-7 text-xs w-28", "aria-invalid": error ? true : void 0, children: /* @__PURE__ */ jsx(SelectValue, {}) }),
736
+ /* @__PURE__ */ jsx(SelectContent, { children: editable.options.map((opt) => /* @__PURE__ */ jsx(SelectItem, { value: opt, children: opt }, opt)) })
737
+ ]
738
+ }
739
+ ),
740
+ feedback,
741
+ error && /* @__PURE__ */ jsx(
742
+ "button",
743
+ {
744
+ type: "button",
745
+ className: "mt-0.5 text-[11px] text-muted-foreground underline-offset-2 hover:underline",
746
+ onClick: close,
747
+ children: "Cancel"
748
+ }
749
+ )
750
+ ]
688
751
  }
689
- }
690
- ) });
752
+ );
753
+ }
754
+ return /* @__PURE__ */ jsxs("div", { onClick: (e) => e.stopPropagation(), "aria-busy": saving || void 0, children: [
755
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
756
+ /* @__PURE__ */ jsx(
757
+ Input,
758
+ {
759
+ autoFocus: true,
760
+ className: cn("h-7 text-xs w-full", saving && "pr-7 opacity-70"),
761
+ value: draft,
762
+ readOnly: saving,
763
+ "aria-invalid": error ? true : void 0,
764
+ onChange: (e) => setDraft(e.target.value),
765
+ onBlur: () => {
766
+ if (inFlight.current) return;
767
+ if (failedDraft.current === draft) close();
768
+ else commit(draft);
769
+ },
770
+ onKeyDown: (e) => {
771
+ if (e.key === "Enter") commit(draft);
772
+ if (e.key === "Escape" && !inFlight.current) close();
773
+ }
774
+ }
775
+ ),
776
+ saving && /* @__PURE__ */ jsx(Spinner, { className: "absolute right-2 top-1/2 h-3.5 w-3.5 -translate-y-1/2", "aria-label": "Saving" })
777
+ ] }),
778
+ feedback
779
+ ] });
691
780
  }
692
781
  function ResizeHandle({
693
782
  onResize,
@@ -1327,12 +1416,39 @@ var statusColorMap = {
1327
1416
  destructive: { color: "text-destructive", bgColor: "bg-destructive/15" },
1328
1417
  primary: { color: "text-primary", bgColor: "bg-primary/15" }
1329
1418
  };
1419
+ var DRAG_TYPE = "application/x-genesis-kanban-item";
1330
1420
  function KanbanBoard({
1331
1421
  columns,
1332
1422
  renderCard,
1333
1423
  emptyMessage = "No items",
1334
- className
1424
+ className,
1425
+ getKey,
1426
+ onMoveItem,
1427
+ getItemLabel,
1428
+ renderColumnFooter
1335
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);
1336
1452
  return /* @__PURE__ */ jsx(
1337
1453
  "div",
1338
1454
  {
@@ -1342,20 +1458,80 @@ function KanbanBoard({
1342
1458
  ),
1343
1459
  children: columns.map((column) => {
1344
1460
  const resolved = column.status ? statusColorMap[column.status] : { color: column.color ?? "", bgColor: column.bgColor ?? "" };
1345
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
1346
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1347
- /* @__PURE__ */ jsx("span", { className: cn("text-sm font-semibold", resolved.color), children: column.label }),
1348
- /* @__PURE__ */ jsx(
1349
- Badge,
1350
- {
1351
- variant: "secondary",
1352
- className: cn("text-[10px] px-1.5 py-0", resolved.bgColor),
1353
- children: column.items.length
1354
- }
1355
- )
1356
- ] }),
1357
- /* @__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) => /* @__PURE__ */ jsx("div", { className: "animate-fade-in", style: { animationDelay: `${i * 30}ms`, animationFillMode: "both" }, children: renderCard(item) }, i)) })
1358
- ] }, column.key);
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
+ );
1359
1535
  })
1360
1536
  }
1361
1537
  );
@@ -2036,15 +2212,15 @@ function ResourceFormFieldRenderer({
2036
2212
  name: field.key,
2037
2213
  control,
2038
2214
  render: ({ field: f, fieldState }) => {
2039
- const errorMessage = fieldState.error?.message;
2215
+ const errorMessage2 = fieldState.error?.message;
2040
2216
  const ariaProps = {
2041
- "aria-invalid": !!errorMessage || void 0,
2042
- "aria-describedby": errorMessage ? `${field.key}-error` : field.description ? `${field.key}-desc` : void 0,
2217
+ "aria-invalid": !!errorMessage2 || void 0,
2218
+ "aria-describedby": errorMessage2 ? `${field.key}-error` : field.description ? `${field.key}-desc` : void 0,
2043
2219
  "aria-required": field.required || void 0
2044
2220
  };
2045
2221
  if (fieldType === "switch") {
2046
2222
  return /* @__PURE__ */ jsxs(FormField, { className: "flex flex-row items-center justify-between rounded-lg border p-4", children: [
2047
- /* @__PURE__ */ jsx("div", { className: "space-y-0.5", children: /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage, children: label }) }),
2223
+ /* @__PURE__ */ jsx("div", { className: "space-y-0.5", children: /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage2, children: label }) }),
2048
2224
  /* @__PURE__ */ jsx(
2049
2225
  Switch,
2050
2226
  {
@@ -2068,14 +2244,14 @@ function ResourceFormFieldRenderer({
2068
2244
  }
2069
2245
  ),
2070
2246
  /* @__PURE__ */ jsxs("div", { className: "space-y-1 leading-none", children: [
2071
- /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage, children: label }),
2072
- /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage })
2247
+ /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage2, children: label }),
2248
+ /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage2 })
2073
2249
  ] })
2074
2250
  ] });
2075
2251
  }
2076
2252
  if (fieldType === "select" && field.options) {
2077
2253
  return /* @__PURE__ */ jsxs(FormField, { children: [
2078
- /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage, required: field.required, children: label }),
2254
+ /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage2, required: field.required, children: label }),
2079
2255
  /* @__PURE__ */ jsxs(
2080
2256
  Select,
2081
2257
  {
@@ -2095,12 +2271,12 @@ function ResourceFormFieldRenderer({
2095
2271
  }
2096
2272
  ),
2097
2273
  field.description && /* @__PURE__ */ jsx(FormDescription, { id: `${field.key}-desc`, children: field.description }),
2098
- /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage })
2274
+ /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage2 })
2099
2275
  ] });
2100
2276
  }
2101
2277
  if (fieldType === "relation" && field.relation) {
2102
2278
  return /* @__PURE__ */ jsxs(FormField, { children: [
2103
- /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage, required: field.required, children: label }),
2279
+ /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage2, required: field.required, children: label }),
2104
2280
  /* @__PURE__ */ jsx(
2105
2281
  RelationField,
2106
2282
  {
@@ -2111,12 +2287,12 @@ function ResourceFormFieldRenderer({
2111
2287
  }
2112
2288
  ),
2113
2289
  field.description && /* @__PURE__ */ jsx(FormDescription, { id: `${field.key}-desc`, children: field.description }),
2114
- /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage })
2290
+ /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage2 })
2115
2291
  ] });
2116
2292
  }
2117
2293
  if (fieldType === "file" || fieldType === "image") {
2118
2294
  return /* @__PURE__ */ jsxs(FormField, { children: [
2119
- /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage, required: field.required, children: label }),
2295
+ /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage2, required: field.required, children: label }),
2120
2296
  /* @__PURE__ */ jsx(
2121
2297
  FileUploadField,
2122
2298
  {
@@ -2127,12 +2303,12 @@ function ResourceFormFieldRenderer({
2127
2303
  }
2128
2304
  ),
2129
2305
  field.description && /* @__PURE__ */ jsx(FormDescription, { id: `${field.key}-desc`, children: field.description }),
2130
- /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage })
2306
+ /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage2 })
2131
2307
  ] });
2132
2308
  }
2133
2309
  if (fieldType === "textarea") {
2134
2310
  return /* @__PURE__ */ jsxs(FormField, { children: [
2135
- /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage, required: field.required, children: label }),
2311
+ /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage2, required: field.required, children: label }),
2136
2312
  /* @__PURE__ */ jsx(
2137
2313
  Textarea,
2138
2314
  {
@@ -2146,12 +2322,12 @@ function ResourceFormFieldRenderer({
2146
2322
  }
2147
2323
  ),
2148
2324
  field.description && /* @__PURE__ */ jsx(FormDescription, { id: `${field.key}-desc`, children: field.description }),
2149
- /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage })
2325
+ /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage2 })
2150
2326
  ] });
2151
2327
  }
2152
2328
  if (fieldType === "number") {
2153
2329
  return /* @__PURE__ */ jsxs(FormField, { children: [
2154
- /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage, required: field.required, children: label }),
2330
+ /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage2, required: field.required, children: label }),
2155
2331
  /* @__PURE__ */ jsx(
2156
2332
  Input,
2157
2333
  {
@@ -2170,11 +2346,11 @@ function ResourceFormFieldRenderer({
2170
2346
  }
2171
2347
  ),
2172
2348
  field.description && /* @__PURE__ */ jsx(FormDescription, { id: `${field.key}-desc`, children: field.description }),
2173
- /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage })
2349
+ /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage2 })
2174
2350
  ] });
2175
2351
  }
2176
2352
  return /* @__PURE__ */ jsxs(FormField, { children: [
2177
- /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage, required: field.required, children: label }),
2353
+ /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage2, required: field.required, children: label }),
2178
2354
  /* @__PURE__ */ jsx(
2179
2355
  Input,
2180
2356
  {
@@ -2188,7 +2364,7 @@ function ResourceFormFieldRenderer({
2188
2364
  }
2189
2365
  ),
2190
2366
  field.description && /* @__PURE__ */ jsx(FormDescription, { id: `${field.key}-desc`, children: field.description }),
2191
- /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage })
2367
+ /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage2 })
2192
2368
  ] });
2193
2369
  }
2194
2370
  }
@@ -2475,6 +2651,7 @@ function ResourcePage(props) {
2475
2651
  subtitle,
2476
2652
  icon,
2477
2653
  columns: columnsProp,
2654
+ getKey: getKeyProp,
2478
2655
  detail = "panel",
2479
2656
  detailWidth = "md",
2480
2657
  titleField: titleFieldProp,
@@ -2495,7 +2672,7 @@ function ResourcePage(props) {
2495
2672
  onSearchChange,
2496
2673
  loading: loadingProp,
2497
2674
  error: errorProp,
2498
- errorMessage,
2675
+ errorMessage: errorMessage2,
2499
2676
  onRetry: onRetryProp,
2500
2677
  savedViews,
2501
2678
  onLoadView,
@@ -2526,6 +2703,7 @@ function ResourcePage(props) {
2526
2703
  renderTable: renderTableProp,
2527
2704
  gridCols: gridColsProp,
2528
2705
  keyboardNavigation = true,
2706
+ capabilities,
2529
2707
  toolbar,
2530
2708
  emptyState,
2531
2709
  resourceOptions,
@@ -2592,6 +2770,10 @@ function ResourcePage(props) {
2592
2770
  resourceHook.remove,
2593
2771
  resourceHook.list
2594
2772
  ]);
2773
+ const keyOf = useCallback(
2774
+ (item) => getKeyProp ? getKeyProp(item) : String(item.id ?? ""),
2775
+ [getKeyProp]
2776
+ );
2595
2777
  const [selectedIds, setSelectedIds] = useState(/* @__PURE__ */ new Set());
2596
2778
  const handleToggleSelect = useCallback((id) => {
2597
2779
  setSelectedIds((prev) => {
@@ -2604,13 +2786,9 @@ function ResourcePage(props) {
2604
2786
  const handleToggleAll = useCallback(() => {
2605
2787
  setSelectedIds((prev) => {
2606
2788
  if (prev.size === page.data.length) return /* @__PURE__ */ new Set();
2607
- return new Set(
2608
- page.data.map(
2609
- (item) => String(item.id ?? "")
2610
- )
2611
- );
2789
+ return new Set(page.data.map(keyOf));
2612
2790
  });
2613
- }, [page.data]);
2791
+ }, [page.data, keyOf]);
2614
2792
  const handleClearSelection = useCallback(() => {
2615
2793
  setSelectedIds(/* @__PURE__ */ new Set());
2616
2794
  }, []);
@@ -2646,6 +2824,8 @@ function ResourcePage(props) {
2646
2824
  header: col.header ?? col.key,
2647
2825
  sortable: col.sortable ?? true,
2648
2826
  hideBelow: col.hideBelow,
2827
+ editable: col.editable,
2828
+ accessor: col.accessor,
2649
2829
  render: col.render ? (item, value) => col.render(item, value, actions) : void 0
2650
2830
  })) : page.tableColumns;
2651
2831
  if (!isServerControlled) return base;
@@ -2765,9 +2945,7 @@ function ResourcePage(props) {
2765
2945
  onToggleSelect: hasBulkSelect ? (index) => {
2766
2946
  const item = page.data[index];
2767
2947
  if (item) {
2768
- handleToggleSelect(
2769
- String(item.id ?? "")
2770
- );
2948
+ handleToggleSelect(keyOf(item));
2771
2949
  }
2772
2950
  } : void 0,
2773
2951
  // Supplied only while there is an open detail to close — the hook
@@ -2881,7 +3059,7 @@ function ResourcePage(props) {
2881
3059
  isLoading,
2882
3060
  hasError,
2883
3061
  isEmpty,
2884
- errorMessage,
3062
+ errorMessage: errorMessage2,
2885
3063
  onRetry: isClientMode ? () => resourceHook.list.refetch() : onRetryProp,
2886
3064
  viewMode: page.viewMode,
2887
3065
  onViewModeChange: page.setViewMode,
@@ -2911,7 +3089,9 @@ function ResourcePage(props) {
2911
3089
  onSaveView,
2912
3090
  onDeleteView,
2913
3091
  canSaveViews,
2914
- onClearAll: page.hasActiveFilters ? handleClearFilters : void 0
3092
+ onClearAll: page.hasActiveFilters ? handleClearFilters : void 0,
3093
+ showSearch: capabilities?.search !== false,
3094
+ showSort: capabilities?.sort !== false
2915
3095
  },
2916
3096
  slotProps?.filters
2917
3097
  ),
@@ -2946,7 +3126,7 @@ function ResourcePage(props) {
2946
3126
  items: page.data,
2947
3127
  viewMode: page.viewMode,
2948
3128
  gridCols: gridColsProp,
2949
- getKey: (item) => String(item.id ?? ""),
3129
+ getKey: keyOf,
2950
3130
  focusedIndex: keyboardNav.focusedIndex,
2951
3131
  keyboardContainerRef: keyboardNav.containerRef,
2952
3132
  renderTable: (items) => renderTableProp ? renderTableProp(items) : renderSlot(
@@ -2955,7 +3135,7 @@ function ResourcePage(props) {
2955
3135
  {
2956
3136
  items,
2957
3137
  columns: tableColumns,
2958
- getKey: (item) => String(item.id ?? ""),
3138
+ getKey: keyOf,
2959
3139
  onRowClick: detail !== "none" ? handleRowClick : void 0,
2960
3140
  selectable: !!bulkActions && bulkActions.length > 0,
2961
3141
  selected: selectedIds,