@marktiderman/genesis-ui 2.3.1 → 2.5.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.
Files changed (39) hide show
  1. package/SKILL.md +137 -0
  2. package/dist/{AppShell-D_E_Hk4g.d.ts → AppShell-CX0M-dfc.d.ts} +24 -6
  3. package/dist/chunk-CDYZRZ6U.js +141 -0
  4. package/dist/chunk-CDYZRZ6U.js.map +1 -0
  5. package/dist/{chunk-UBCYXOXT.js → chunk-DWM7ELFI.js} +28 -7
  6. package/dist/chunk-DWM7ELFI.js.map +1 -0
  7. package/dist/{chunk-GDFNAHI4.js → chunk-IXGAEECT.js} +4 -2
  8. package/dist/chunk-IXGAEECT.js.map +1 -0
  9. package/dist/{chunk-D4JTQYP5.js → chunk-JA3ZKULS.js} +3 -3
  10. package/dist/{chunk-D4JTQYP5.js.map → chunk-JA3ZKULS.js.map} +1 -1
  11. package/dist/chunk-VFO3I7PV.js +24 -0
  12. package/dist/chunk-VFO3I7PV.js.map +1 -0
  13. package/dist/components/data/index.d.ts +42 -9
  14. package/dist/components/data/index.js +208 -76
  15. package/dist/components/data/index.js.map +1 -1
  16. package/dist/components/layout/index.d.ts +2 -2
  17. package/dist/components/layout/index.js +6 -6
  18. package/dist/components/patterns/index.js +3 -3
  19. package/dist/hooks/index.d.ts +2 -2
  20. package/dist/hooks/index.js +4 -4
  21. package/dist/hooks/use-data-filters.d.ts +1 -1
  22. package/dist/hooks/use-data-filters.js +2 -2
  23. package/dist/hooks/use-mobile.d.ts +1 -0
  24. package/dist/hooks/use-mobile.js +1 -1
  25. package/dist/hooks/use-resource-page.d.ts +1 -1
  26. package/dist/hooks/use-resource-page.js +1 -1
  27. package/dist/index.d.ts +1 -1
  28. package/dist/index.js +12 -12
  29. package/dist/navigation-DBwcZ6vb.d.ts +163 -0
  30. package/dist/styles.css +11 -0
  31. package/dist/{use-resource-page-DTQS011Q.d.ts → use-resource-page-Dd2VUv30.d.ts} +15 -1
  32. package/package.json +8 -6
  33. package/dist/chunk-6NPEMLBA.js +0 -51
  34. package/dist/chunk-6NPEMLBA.js.map +0 -1
  35. package/dist/chunk-GDFNAHI4.js.map +0 -1
  36. package/dist/chunk-M7AU5DDM.js +0 -26
  37. package/dist/chunk-M7AU5DDM.js.map +0 -1
  38. package/dist/chunk-UBCYXOXT.js.map +0 -1
  39. package/dist/navigation-CpOEyUSb.d.ts +0 -94
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { AppShell, PageHeader } from '../../chunk-UBCYXOXT.js';
2
+ import { AppShell, PageHeader } from '../../chunk-DWM7ELFI.js';
3
3
  export { ViewSettings, ViewToggle } from '../../chunk-46TZGMFW.js';
4
4
  import { renderSlot } from '../../chunk-7S7LKQBD.js';
5
5
  import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from '../../chunk-N2ZFNOOT.js';
@@ -16,18 +16,18 @@ import '../../chunk-B2GIBUQD.js';
16
16
  import { Badge } from '../../chunk-NPNADZHM.js';
17
17
  import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '../../chunk-FK42ZGGV.js';
18
18
  import { Popover, PopoverTrigger, PopoverContent } from '../../chunk-22XB3Q6H.js';
19
- import { PageLoading } from '../../chunk-YRYQXU3G.js';
20
- import { Spinner } from '../../chunk-OVU3Z2MT.js';
21
19
  import { Switch } from '../../chunk-27ELYTGE.js';
22
- import { DetailPage } from '../../chunk-AFZ76Q26.js';
23
- import '../../chunk-D2LIXA2Y.js';
24
- import '../../chunk-AVFZ4P4B.js';
25
20
  import { EmptyState } from '../../chunk-65SPT7EA.js';
26
21
  export { EmptyState } from '../../chunk-65SPT7EA.js';
27
22
  import { Button } from '../../chunk-NWSCYLPT.js';
23
+ import { PageLoading } from '../../chunk-YRYQXU3G.js';
24
+ import { Spinner } from '../../chunk-OVU3Z2MT.js';
28
25
  import { FormField, FormLabel, FormError, FormDescription } from '../../chunk-R7QFP6EL.js';
29
26
  import '../../chunk-F47U5KNW.js';
30
- import { titleCase, useResourcePage } from '../../chunk-GDFNAHI4.js';
27
+ import { DetailPage } from '../../chunk-AFZ76Q26.js';
28
+ import '../../chunk-D2LIXA2Y.js';
29
+ import '../../chunk-AVFZ4P4B.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';
@@ -35,10 +35,10 @@ export { CommandPalette, useCommandPaletteHotkey } from '../../chunk-CNTWQSUT.js
35
35
  import { Command, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem, CommandSeparator } from '../../chunk-SEFK4NZV.js';
36
36
  import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription } from '../../chunk-7XMZVISM.js';
37
37
  import { cn } from '../../chunk-NZ5WBA4R.js';
38
- import { DefaultLink, defaultNavigate } from '../../chunk-6NPEMLBA.js';
39
- export { DefaultLink, defaultNavigate, isPathActive } from '../../chunk-6NPEMLBA.js';
38
+ import { DefaultLink, defaultNavigate } from '../../chunk-CDYZRZ6U.js';
39
+ export { DefaultLink, defaultNavigate, isPathActive, resolveNavigation } from '../../chunk-CDYZRZ6U.js';
40
40
  import { useKeyboardNavigation } from '../../chunk-YJ6MPS6U.js';
41
- import { useIsMobile } from '../../chunk-M7AU5DDM.js';
41
+ import { useIsMobile } from '../../chunk-VFO3I7PV.js';
42
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';
43
43
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
44
44
  import { useState, useRef, useEffect, useCallback, useMemo, Fragment as Fragment$1 } from 'react';
@@ -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,
@@ -71,6 +71,7 @@ function DataPageShell({
71
71
  onPageSizeChange,
72
72
  density,
73
73
  onDensityChange,
74
+ stats,
74
75
  filters,
75
76
  hasActiveFilters = false,
76
77
  onClearFilters,
@@ -110,6 +111,7 @@ function DataPageShell({
110
111
  onDensityChange,
111
112
  createAction,
112
113
  secondaryActions,
114
+ stats,
113
115
  optionsStorageKey: headerOptionsStorageKey
114
116
  };
115
117
  const Header = header === null ? (() => null) : header ?? PageHeader;
@@ -146,7 +148,7 @@ function DataPageShell({
146
148
  ) : hasError ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center py-16 text-center", children: [
147
149
  /* @__PURE__ */ jsx(AlertTriangle, { className: "h-10 w-10 text-destructive mb-3" }),
148
150
  /* @__PURE__ */ jsx("p", { className: "mb-1 text-sm font-medium text-foreground", children: "Something went wrong" }),
149
- /* @__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.` }),
150
152
  onRetry && /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: onRetry, children: [
151
153
  /* @__PURE__ */ jsx(RefreshCw, { className: "mr-1 h-4 w-4" }),
152
154
  " Retry"
@@ -285,6 +287,10 @@ function DataFilters({
285
287
  const [saveDialogOpen, setSaveDialogOpen] = useState(false);
286
288
  const [newViewName, setNewViewName] = useState("");
287
289
  const hasFilters = filters.some((f) => f.selected.length > 0) || (statusChips?.selected.length ?? 0) > 0;
290
+ const statusOptions = (statusChips?.options ?? []).map(
291
+ (o) => typeof o === "string" ? { value: o, label: o } : { value: o.value, label: o.label ?? o.value, count: o.count }
292
+ );
293
+ const statusVariant = statusChips?.variant ?? "chips";
288
294
  const toggleStatus = (status) => {
289
295
  if (!statusChips) return;
290
296
  const { selected, onChange } = statusChips;
@@ -300,6 +306,35 @@ function DataFilters({
300
306
  onViewSaved?.(newViewName.trim());
301
307
  };
302
308
  return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
309
+ statusChips && statusVariant === "tabs" && /* @__PURE__ */ jsx(
310
+ "div",
311
+ {
312
+ className: "flex items-center border rounded-lg overflow-x-auto w-fit max-w-full",
313
+ role: "group",
314
+ "aria-label": "Status",
315
+ "data-testid": "status-tabs",
316
+ children: statusOptions.map((o) => {
317
+ const pressed = statusChips.selected.includes(o.value);
318
+ return /* @__PURE__ */ jsxs(
319
+ Button,
320
+ {
321
+ type: "button",
322
+ variant: pressed ? "default" : "ghost",
323
+ size: "sm",
324
+ className: "h-8 rounded-none gap-1.5",
325
+ onClick: () => toggleStatus(o.value),
326
+ "aria-pressed": pressed,
327
+ testID: `status-tab-${o.value}`,
328
+ children: [
329
+ o.label,
330
+ o.count !== void 0 && /* @__PURE__ */ jsx("span", { className: "text-[10px] tabular-nums opacity-70", children: o.count })
331
+ ]
332
+ },
333
+ o.value
334
+ );
335
+ })
336
+ }
337
+ ),
303
338
  /* @__PURE__ */ jsxs("div", { className: "flex flex-col sm:flex-row gap-2", children: [
304
339
  /* @__PURE__ */ jsxs("div", { className: "relative flex-1", children: [
305
340
  /* @__PURE__ */ jsx(Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" }),
@@ -399,18 +434,27 @@ function DataFilters({
399
434
  }
400
435
  )
401
436
  ] }),
402
- statusChips && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1 mr-1", children: statusChips.options.map((s) => /* @__PURE__ */ jsx(
403
- "button",
404
- {
405
- onClick: () => toggleStatus(s),
406
- className: cn(
407
- "text-xs px-2.5 py-1 rounded-full border transition-all font-medium",
408
- statusChips.selected.includes(s) ? "bg-primary text-primary-foreground border-primary" : "bg-background text-muted-foreground border-border hover:border-primary/40"
409
- ),
410
- children: s
411
- },
412
- s
413
- )) }),
437
+ statusChips && statusVariant === "chips" && /* @__PURE__ */ jsx("div", { className: "flex items-center gap-1 mr-1", children: statusOptions.map((o) => {
438
+ const pressed = statusChips.selected.includes(o.value);
439
+ return /* @__PURE__ */ jsxs(
440
+ "button",
441
+ {
442
+ type: "button",
443
+ onClick: () => toggleStatus(o.value),
444
+ "aria-pressed": pressed,
445
+ "data-testid": `status-chip-${o.value}`,
446
+ className: cn(
447
+ "text-xs px-2.5 py-1 rounded-full border transition-all font-medium",
448
+ pressed ? "bg-primary text-primary-foreground border-primary" : "bg-background text-muted-foreground border-border hover:border-primary/40"
449
+ ),
450
+ children: [
451
+ o.label,
452
+ o.count !== void 0 && /* @__PURE__ */ jsx("span", { className: "ml-1 text-[10px] tabular-nums opacity-70", children: o.count })
453
+ ]
454
+ },
455
+ o.value
456
+ );
457
+ }) }),
414
458
  filters.map(
415
459
  (f) => f.options.length > 0 ? /* @__PURE__ */ jsx(
416
460
  FilterCombobox,
@@ -595,6 +639,9 @@ function getCellClasses(col) {
595
639
  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" : "";
596
640
  return cn(col.width, hide);
597
641
  }
642
+ function errorMessage(error) {
643
+ return error instanceof Error ? error.message : String(error);
644
+ }
598
645
  function EditableCell({
599
646
  item,
600
647
  column,
@@ -602,13 +649,49 @@ function EditableCell({
602
649
  }) {
603
650
  const [editing, setEditing] = useState(false);
604
651
  const [draft, setDraft] = useState(String(value ?? ""));
652
+ const [saving, setSaving] = useState(false);
653
+ const [error, setError] = useState(null);
654
+ const inFlight = useRef(false);
655
+ const failedDraft = useRef(null);
605
656
  const editable = column.editable;
657
+ const close = useCallback(() => {
658
+ setEditing(false);
659
+ setError(null);
660
+ failedDraft.current = null;
661
+ }, []);
606
662
  const commit = useCallback(
607
663
  (v) => {
608
- editable.onSave(item, v);
609
- setEditing(false);
664
+ if (inFlight.current) return;
665
+ let result;
666
+ try {
667
+ result = editable.onSave(item, v);
668
+ } catch (e) {
669
+ setError(errorMessage(e));
670
+ failedDraft.current = v;
671
+ return;
672
+ }
673
+ if (!result || typeof result.then !== "function") {
674
+ close();
675
+ return;
676
+ }
677
+ inFlight.current = true;
678
+ setSaving(true);
679
+ setError(null);
680
+ result.then(
681
+ () => {
682
+ inFlight.current = false;
683
+ setSaving(false);
684
+ close();
685
+ },
686
+ (e) => {
687
+ inFlight.current = false;
688
+ setSaving(false);
689
+ setError(errorMessage(e));
690
+ failedDraft.current = v;
691
+ }
692
+ );
610
693
  },
611
- [editable, item]
694
+ [editable, item, close]
612
695
  );
613
696
  if (!editing) {
614
697
  return /* @__PURE__ */ jsx(
@@ -618,32 +701,80 @@ function EditableCell({
618
701
  onClick: (e) => {
619
702
  e.stopPropagation();
620
703
  setDraft(String(value ?? ""));
704
+ setError(null);
705
+ failedDraft.current = null;
621
706
  setEditing(true);
622
707
  },
623
708
  children: column.render ? column.render(item, value) : String(value ?? "\u2014")
624
709
  }
625
710
  );
626
711
  }
712
+ const feedback = error ? /* @__PURE__ */ jsx("p", { role: "alert", className: "mt-1 text-[11px] leading-snug text-destructive", children: error }) : null;
627
713
  if (editable.type === "select" && editable.options) {
628
- return /* @__PURE__ */ jsx("div", { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsxs(Select, { defaultValue: draft, onValueChange: (v) => commit(v), children: [
629
- /* @__PURE__ */ jsx(SelectTrigger, { className: "h-7 text-xs w-28", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
630
- /* @__PURE__ */ jsx(SelectContent, { children: editable.options.map((opt) => /* @__PURE__ */ jsx(SelectItem, { value: opt, children: opt }, opt)) })
631
- ] }) });
632
- }
633
- return /* @__PURE__ */ jsx("div", { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx(
634
- Input,
635
- {
636
- autoFocus: true,
637
- className: "h-7 text-xs w-full",
638
- value: draft,
639
- onChange: (e) => setDraft(e.target.value),
640
- onBlur: () => commit(draft),
641
- onKeyDown: (e) => {
642
- if (e.key === "Enter") commit(draft);
643
- if (e.key === "Escape") setEditing(false);
714
+ return /* @__PURE__ */ jsxs(
715
+ "div",
716
+ {
717
+ onClick: (e) => e.stopPropagation(),
718
+ onKeyDown: (e) => {
719
+ if (e.key === "Escape" && !inFlight.current) close();
720
+ },
721
+ "aria-busy": saving || void 0,
722
+ children: [
723
+ /* @__PURE__ */ jsxs(
724
+ Select,
725
+ {
726
+ value: draft,
727
+ disabled: saving,
728
+ onValueChange: (v) => {
729
+ setDraft(v);
730
+ commit(v);
731
+ },
732
+ children: [
733
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "h-7 text-xs w-28", "aria-invalid": error ? true : void 0, children: /* @__PURE__ */ jsx(SelectValue, {}) }),
734
+ /* @__PURE__ */ jsx(SelectContent, { children: editable.options.map((opt) => /* @__PURE__ */ jsx(SelectItem, { value: opt, children: opt }, opt)) })
735
+ ]
736
+ }
737
+ ),
738
+ feedback,
739
+ error && /* @__PURE__ */ jsx(
740
+ "button",
741
+ {
742
+ type: "button",
743
+ className: "mt-0.5 text-[11px] text-muted-foreground underline-offset-2 hover:underline",
744
+ onClick: close,
745
+ children: "Cancel"
746
+ }
747
+ )
748
+ ]
644
749
  }
645
- }
646
- ) });
750
+ );
751
+ }
752
+ return /* @__PURE__ */ jsxs("div", { onClick: (e) => e.stopPropagation(), "aria-busy": saving || void 0, children: [
753
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
754
+ /* @__PURE__ */ jsx(
755
+ Input,
756
+ {
757
+ autoFocus: true,
758
+ className: cn("h-7 text-xs w-full", saving && "pr-7 opacity-70"),
759
+ value: draft,
760
+ readOnly: saving,
761
+ "aria-invalid": error ? true : void 0,
762
+ onChange: (e) => setDraft(e.target.value),
763
+ onBlur: () => {
764
+ if (inFlight.current) return;
765
+ if (failedDraft.current === draft) close();
766
+ else commit(draft);
767
+ },
768
+ onKeyDown: (e) => {
769
+ if (e.key === "Enter") commit(draft);
770
+ if (e.key === "Escape" && !inFlight.current) close();
771
+ }
772
+ }
773
+ ),
774
+ saving && /* @__PURE__ */ jsx(Spinner, { className: "absolute right-2 top-1/2 h-3.5 w-3.5 -translate-y-1/2", "aria-label": "Saving" })
775
+ ] }),
776
+ feedback
777
+ ] });
647
778
  }
648
779
  function ResizeHandle({
649
780
  onResize,
@@ -1992,15 +2123,15 @@ function ResourceFormFieldRenderer({
1992
2123
  name: field.key,
1993
2124
  control,
1994
2125
  render: ({ field: f, fieldState }) => {
1995
- const errorMessage = fieldState.error?.message;
2126
+ const errorMessage2 = fieldState.error?.message;
1996
2127
  const ariaProps = {
1997
- "aria-invalid": !!errorMessage || void 0,
1998
- "aria-describedby": errorMessage ? `${field.key}-error` : field.description ? `${field.key}-desc` : void 0,
2128
+ "aria-invalid": !!errorMessage2 || void 0,
2129
+ "aria-describedby": errorMessage2 ? `${field.key}-error` : field.description ? `${field.key}-desc` : void 0,
1999
2130
  "aria-required": field.required || void 0
2000
2131
  };
2001
2132
  if (fieldType === "switch") {
2002
2133
  return /* @__PURE__ */ jsxs(FormField, { className: "flex flex-row items-center justify-between rounded-lg border p-4", children: [
2003
- /* @__PURE__ */ jsx("div", { className: "space-y-0.5", children: /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage, children: label }) }),
2134
+ /* @__PURE__ */ jsx("div", { className: "space-y-0.5", children: /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage2, children: label }) }),
2004
2135
  /* @__PURE__ */ jsx(
2005
2136
  Switch,
2006
2137
  {
@@ -2024,14 +2155,14 @@ function ResourceFormFieldRenderer({
2024
2155
  }
2025
2156
  ),
2026
2157
  /* @__PURE__ */ jsxs("div", { className: "space-y-1 leading-none", children: [
2027
- /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage, children: label }),
2028
- /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage })
2158
+ /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage2, children: label }),
2159
+ /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage2 })
2029
2160
  ] })
2030
2161
  ] });
2031
2162
  }
2032
2163
  if (fieldType === "select" && field.options) {
2033
2164
  return /* @__PURE__ */ jsxs(FormField, { children: [
2034
- /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage, required: field.required, children: label }),
2165
+ /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage2, required: field.required, children: label }),
2035
2166
  /* @__PURE__ */ jsxs(
2036
2167
  Select,
2037
2168
  {
@@ -2051,12 +2182,12 @@ function ResourceFormFieldRenderer({
2051
2182
  }
2052
2183
  ),
2053
2184
  field.description && /* @__PURE__ */ jsx(FormDescription, { id: `${field.key}-desc`, children: field.description }),
2054
- /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage })
2185
+ /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage2 })
2055
2186
  ] });
2056
2187
  }
2057
2188
  if (fieldType === "relation" && field.relation) {
2058
2189
  return /* @__PURE__ */ jsxs(FormField, { children: [
2059
- /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage, required: field.required, children: label }),
2190
+ /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage2, required: field.required, children: label }),
2060
2191
  /* @__PURE__ */ jsx(
2061
2192
  RelationField,
2062
2193
  {
@@ -2067,12 +2198,12 @@ function ResourceFormFieldRenderer({
2067
2198
  }
2068
2199
  ),
2069
2200
  field.description && /* @__PURE__ */ jsx(FormDescription, { id: `${field.key}-desc`, children: field.description }),
2070
- /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage })
2201
+ /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage2 })
2071
2202
  ] });
2072
2203
  }
2073
2204
  if (fieldType === "file" || fieldType === "image") {
2074
2205
  return /* @__PURE__ */ jsxs(FormField, { children: [
2075
- /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage, required: field.required, children: label }),
2206
+ /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage2, required: field.required, children: label }),
2076
2207
  /* @__PURE__ */ jsx(
2077
2208
  FileUploadField,
2078
2209
  {
@@ -2083,12 +2214,12 @@ function ResourceFormFieldRenderer({
2083
2214
  }
2084
2215
  ),
2085
2216
  field.description && /* @__PURE__ */ jsx(FormDescription, { id: `${field.key}-desc`, children: field.description }),
2086
- /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage })
2217
+ /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage2 })
2087
2218
  ] });
2088
2219
  }
2089
2220
  if (fieldType === "textarea") {
2090
2221
  return /* @__PURE__ */ jsxs(FormField, { children: [
2091
- /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage, required: field.required, children: label }),
2222
+ /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage2, required: field.required, children: label }),
2092
2223
  /* @__PURE__ */ jsx(
2093
2224
  Textarea,
2094
2225
  {
@@ -2102,12 +2233,12 @@ function ResourceFormFieldRenderer({
2102
2233
  }
2103
2234
  ),
2104
2235
  field.description && /* @__PURE__ */ jsx(FormDescription, { id: `${field.key}-desc`, children: field.description }),
2105
- /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage })
2236
+ /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage2 })
2106
2237
  ] });
2107
2238
  }
2108
2239
  if (fieldType === "number") {
2109
2240
  return /* @__PURE__ */ jsxs(FormField, { children: [
2110
- /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage, required: field.required, children: label }),
2241
+ /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage2, required: field.required, children: label }),
2111
2242
  /* @__PURE__ */ jsx(
2112
2243
  Input,
2113
2244
  {
@@ -2126,11 +2257,11 @@ function ResourceFormFieldRenderer({
2126
2257
  }
2127
2258
  ),
2128
2259
  field.description && /* @__PURE__ */ jsx(FormDescription, { id: `${field.key}-desc`, children: field.description }),
2129
- /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage })
2260
+ /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage2 })
2130
2261
  ] });
2131
2262
  }
2132
2263
  return /* @__PURE__ */ jsxs(FormField, { children: [
2133
- /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage, required: field.required, children: label }),
2264
+ /* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage2, required: field.required, children: label }),
2134
2265
  /* @__PURE__ */ jsx(
2135
2266
  Input,
2136
2267
  {
@@ -2144,7 +2275,7 @@ function ResourceFormFieldRenderer({
2144
2275
  }
2145
2276
  ),
2146
2277
  field.description && /* @__PURE__ */ jsx(FormDescription, { id: `${field.key}-desc`, children: field.description }),
2147
- /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage })
2278
+ /* @__PURE__ */ jsx(FormError, { id: `${field.key}-error`, children: errorMessage2 })
2148
2279
  ] });
2149
2280
  }
2150
2281
  }
@@ -2431,6 +2562,7 @@ function ResourcePage(props) {
2431
2562
  subtitle,
2432
2563
  icon,
2433
2564
  columns: columnsProp,
2565
+ getKey: getKeyProp,
2434
2566
  detail = "panel",
2435
2567
  detailWidth = "md",
2436
2568
  titleField: titleFieldProp,
@@ -2451,7 +2583,7 @@ function ResourcePage(props) {
2451
2583
  onSearchChange,
2452
2584
  loading: loadingProp,
2453
2585
  error: errorProp,
2454
- errorMessage,
2586
+ errorMessage: errorMessage2,
2455
2587
  onRetry: onRetryProp,
2456
2588
  savedViews,
2457
2589
  onLoadView,
@@ -2548,6 +2680,10 @@ function ResourcePage(props) {
2548
2680
  resourceHook.remove,
2549
2681
  resourceHook.list
2550
2682
  ]);
2683
+ const keyOf = useCallback(
2684
+ (item) => getKeyProp ? getKeyProp(item) : String(item.id ?? ""),
2685
+ [getKeyProp]
2686
+ );
2551
2687
  const [selectedIds, setSelectedIds] = useState(/* @__PURE__ */ new Set());
2552
2688
  const handleToggleSelect = useCallback((id) => {
2553
2689
  setSelectedIds((prev) => {
@@ -2560,13 +2696,9 @@ function ResourcePage(props) {
2560
2696
  const handleToggleAll = useCallback(() => {
2561
2697
  setSelectedIds((prev) => {
2562
2698
  if (prev.size === page.data.length) return /* @__PURE__ */ new Set();
2563
- return new Set(
2564
- page.data.map(
2565
- (item) => String(item.id ?? "")
2566
- )
2567
- );
2699
+ return new Set(page.data.map(keyOf));
2568
2700
  });
2569
- }, [page.data]);
2701
+ }, [page.data, keyOf]);
2570
2702
  const handleClearSelection = useCallback(() => {
2571
2703
  setSelectedIds(/* @__PURE__ */ new Set());
2572
2704
  }, []);
@@ -2602,6 +2734,8 @@ function ResourcePage(props) {
2602
2734
  header: col.header ?? col.key,
2603
2735
  sortable: col.sortable ?? true,
2604
2736
  hideBelow: col.hideBelow,
2737
+ editable: col.editable,
2738
+ accessor: col.accessor,
2605
2739
  render: col.render ? (item, value) => col.render(item, value, actions) : void 0
2606
2740
  })) : page.tableColumns;
2607
2741
  if (!isServerControlled) return base;
@@ -2721,9 +2855,7 @@ function ResourcePage(props) {
2721
2855
  onToggleSelect: hasBulkSelect ? (index) => {
2722
2856
  const item = page.data[index];
2723
2857
  if (item) {
2724
- handleToggleSelect(
2725
- String(item.id ?? "")
2726
- );
2858
+ handleToggleSelect(keyOf(item));
2727
2859
  }
2728
2860
  } : void 0,
2729
2861
  // Supplied only while there is an open detail to close — the hook
@@ -2837,7 +2969,7 @@ function ResourcePage(props) {
2837
2969
  isLoading,
2838
2970
  hasError,
2839
2971
  isEmpty,
2840
- errorMessage,
2972
+ errorMessage: errorMessage2,
2841
2973
  onRetry: isClientMode ? () => resourceHook.list.refetch() : onRetryProp,
2842
2974
  viewMode: page.viewMode,
2843
2975
  onViewModeChange: page.setViewMode,
@@ -2902,7 +3034,7 @@ function ResourcePage(props) {
2902
3034
  items: page.data,
2903
3035
  viewMode: page.viewMode,
2904
3036
  gridCols: gridColsProp,
2905
- getKey: (item) => String(item.id ?? ""),
3037
+ getKey: keyOf,
2906
3038
  focusedIndex: keyboardNav.focusedIndex,
2907
3039
  keyboardContainerRef: keyboardNav.containerRef,
2908
3040
  renderTable: (items) => renderTableProp ? renderTableProp(items) : renderSlot(
@@ -2911,7 +3043,7 @@ function ResourcePage(props) {
2911
3043
  {
2912
3044
  items,
2913
3045
  columns: tableColumns,
2914
- getKey: (item) => String(item.id ?? ""),
3046
+ getKey: keyOf,
2915
3047
  onRowClick: detail !== "none" ? handleRowClick : void 0,
2916
3048
  selectable: !!bulkActions && bulkActions.length > 0,
2917
3049
  selected: selectedIds,