@marktiderman/genesis-ui 2.6.0 → 2.8.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/EXTENDING.md +40 -1
- package/SKILL.md +145 -9
- package/dist/{AppShell-CX0M-dfc.d.ts → AppShell-LXQykEqe.d.ts} +47 -5
- package/dist/{chunk-M62CHO3N.js → chunk-4C4ESW6O.js} +9 -4
- package/dist/chunk-4C4ESW6O.js.map +1 -0
- package/dist/{chunk-IXGAEECT.js → chunk-DWCAC2E7.js} +6 -3
- package/dist/chunk-DWCAC2E7.js.map +1 -0
- package/dist/chunk-FUVWKTPL.js +19 -0
- package/dist/chunk-FUVWKTPL.js.map +1 -0
- package/dist/{chunk-PLIYF6KI.js → chunk-MPI33I4K.js} +2 -2
- package/dist/{chunk-PLIYF6KI.js.map → chunk-MPI33I4K.js.map} +1 -1
- package/dist/{chunk-JA3ZKULS.js → chunk-OITWCIG2.js} +3 -3
- package/dist/chunk-OITWCIG2.js.map +1 -0
- package/dist/chunk-PKFDGGZV.js +65 -0
- package/dist/chunk-PKFDGGZV.js.map +1 -0
- package/dist/chunk-S7NXD37O.js +321 -0
- package/dist/chunk-S7NXD37O.js.map +1 -0
- package/dist/{chunk-DWM7ELFI.js → chunk-UYI3GLFJ.js} +163 -20
- package/dist/chunk-UYI3GLFJ.js.map +1 -0
- package/dist/{chunk-CDYZRZ6U.js → chunk-WGMEVX3Y.js} +2 -2
- package/dist/chunk-WGMEVX3Y.js.map +1 -0
- package/dist/chunk-ZDHWNQTR.js +552 -0
- package/dist/chunk-ZDHWNQTR.js.map +1 -0
- package/dist/components/data/index.d.ts +239 -15
- package/dist/components/data/index.js +524 -109
- package/dist/components/data/index.js.map +1 -1
- package/dist/components/layout/form-page.d.ts +1 -1
- package/dist/components/layout/form-page.js +1 -1
- package/dist/components/layout/index.d.ts +2 -2
- package/dist/components/layout/index.js +9 -9
- package/dist/components/patterns/index.d.ts +1 -0
- package/dist/components/patterns/index.js +6 -4
- package/dist/components/patterns/sortable-list.d.ts +78 -0
- package/dist/components/patterns/sortable-list.js +5 -0
- package/dist/components/patterns/sortable-list.js.map +1 -0
- package/dist/components/rich-text/rich-text-editor.d.ts +194 -0
- package/dist/components/rich-text/rich-text-editor.js +833 -0
- package/dist/components/rich-text/rich-text-editor.js.map +1 -0
- package/dist/components/rich-text/rich-text.d.ts +51 -0
- package/dist/components/rich-text/rich-text.js +4 -0
- package/dist/components/rich-text/rich-text.js.map +1 -0
- package/dist/components/ui/alert-dialog.d.ts +12 -1
- package/dist/components/ui/alert-dialog.js +1 -1
- package/dist/hooks/index.d.ts +3 -3
- package/dist/hooks/index.js +4 -4
- package/dist/hooks/use-data-filters.d.ts +5 -3
- package/dist/hooks/use-data-filters.js +2 -2
- package/dist/hooks/use-resource-page.d.ts +2 -1
- package/dist/hooks/use-resource-page.js +3 -2
- package/dist/hooks/use-view-preference.d.ts +17 -2
- package/dist/hooks/use-view-preference.js +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +30 -29
- package/dist/{navigation-DBwcZ6vb.d.ts → navigation-Bha0R3Fu.d.ts} +6 -3
- package/dist/rich-text-doc-C54fME2C.d.ts +180 -0
- package/dist/router/react-router.d.ts +47 -0
- package/dist/router/react-router.js +53 -0
- package/dist/router/react-router.js.map +1 -0
- package/dist/router/tanstack-router.d.ts +47 -0
- package/dist/router/tanstack-router.js +105 -0
- package/dist/router/tanstack-router.js.map +1 -0
- package/dist/{use-resource-page-Dd2VUv30.d.ts → use-resource-page-D4byuwAs.d.ts} +20 -3
- package/package.json +62 -7
- package/dist/chunk-CDYZRZ6U.js.map +0 -1
- package/dist/chunk-DWM7ELFI.js.map +0 -1
- package/dist/chunk-FTTNGGLA.js +0 -20
- package/dist/chunk-FTTNGGLA.js.map +0 -1
- package/dist/chunk-IXGAEECT.js.map +0 -1
- package/dist/chunk-JA3ZKULS.js.map +0 -1
- package/dist/chunk-M62CHO3N.js.map +0 -1
|
@@ -1,47 +1,48 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { AppShell, PageHeader } from '../../chunk-
|
|
2
|
+
import { AppShell, PageHeader } from '../../chunk-UYI3GLFJ.js';
|
|
3
3
|
export { ViewSettings, ViewToggle } from '../../chunk-46TZGMFW.js';
|
|
4
4
|
import { renderSlot } from '../../chunk-7S7LKQBD.js';
|
|
5
|
-
import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from '../../chunk-N2ZFNOOT.js';
|
|
6
5
|
import { Textarea } from '../../chunk-YETMISCE.js';
|
|
7
6
|
import { Separator } from '../../chunk-SXSCHWZU.js';
|
|
8
7
|
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription } from '../../chunk-XCGZPFT7.js';
|
|
9
8
|
import { Skeleton } from '../../chunk-QFM4Z2SS.js';
|
|
9
|
+
import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from '../../chunk-N2ZFNOOT.js';
|
|
10
10
|
import { Progress } from '../../chunk-W5Y3LG5K.js';
|
|
11
|
-
import { Input } from '../../chunk-QSXVL5UB.js';
|
|
12
|
-
import { Checkbox } from '../../chunk-SEBU6JCK.js';
|
|
13
11
|
import { Card, CardContent, CardHeader, CardTitle } from '../../chunk-FN3FZVUR.js';
|
|
14
|
-
import {
|
|
12
|
+
import { Checkbox } from '../../chunk-SEBU6JCK.js';
|
|
13
|
+
import { AlertDialog, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogDescription, AlertDialogFooter, AlertDialogCancel, AlertDialogAction } from '../../chunk-4C4ESW6O.js';
|
|
14
|
+
import { Input } from '../../chunk-QSXVL5UB.js';
|
|
15
15
|
import '../../chunk-B2GIBUQD.js';
|
|
16
16
|
import { Badge } from '../../chunk-NPNADZHM.js';
|
|
17
|
+
import { DetailPage } from '../../chunk-AFZ76Q26.js';
|
|
18
|
+
import '../../chunk-D2LIXA2Y.js';
|
|
19
|
+
import '../../chunk-AVFZ4P4B.js';
|
|
20
|
+
import { PageLoading } from '../../chunk-YRYQXU3G.js';
|
|
21
|
+
import { Spinner } from '../../chunk-OVU3Z2MT.js';
|
|
17
22
|
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '../../chunk-FK42ZGGV.js';
|
|
18
|
-
import {
|
|
23
|
+
import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuCheckboxItem, DropdownMenuSeparator, DropdownMenuItem, DropdownMenuLabel } from '../../chunk-QJ6P2DW6.js';
|
|
19
24
|
import { Switch } from '../../chunk-27ELYTGE.js';
|
|
25
|
+
import { Popover, PopoverTrigger, PopoverContent } from '../../chunk-22XB3Q6H.js';
|
|
20
26
|
import { EmptyState } from '../../chunk-65SPT7EA.js';
|
|
21
27
|
export { EmptyState } from '../../chunk-65SPT7EA.js';
|
|
22
28
|
import { Button } from '../../chunk-NWSCYLPT.js';
|
|
23
|
-
import { PageLoading } from '../../chunk-YRYQXU3G.js';
|
|
24
|
-
import { Spinner } from '../../chunk-OVU3Z2MT.js';
|
|
25
29
|
import { FormField, FormLabel, FormError, FormDescription } from '../../chunk-R7QFP6EL.js';
|
|
26
30
|
import '../../chunk-F47U5KNW.js';
|
|
27
|
-
import {
|
|
28
|
-
import '../../chunk-D2LIXA2Y.js';
|
|
29
|
-
import '../../chunk-AVFZ4P4B.js';
|
|
30
|
-
import { titleCase, useResourcePage } from '../../chunk-IXGAEECT.js';
|
|
31
|
+
import { titleCase, useResourcePage } from '../../chunk-DWCAC2E7.js';
|
|
31
32
|
export { useSavedViews } from '../../chunk-GCQFVMPI.js';
|
|
32
|
-
import '../../chunk-
|
|
33
|
+
import '../../chunk-PKFDGGZV.js';
|
|
33
34
|
import { useViewSettings } from '../../chunk-OZ7ZAYCS.js';
|
|
34
35
|
export { CommandPalette, useCommandPaletteHotkey } from '../../chunk-CNTWQSUT.js';
|
|
35
36
|
import { Command, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem, CommandSeparator } from '../../chunk-SEFK4NZV.js';
|
|
36
37
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription } from '../../chunk-7XMZVISM.js';
|
|
37
38
|
import { cn } from '../../chunk-NZ5WBA4R.js';
|
|
38
|
-
import { DefaultLink, defaultNavigate } from '../../chunk-
|
|
39
|
-
export { DefaultLink, defaultNavigate, isPathActive, resolveNavigation } from '../../chunk-
|
|
39
|
+
import { DefaultLink, defaultNavigate } from '../../chunk-WGMEVX3Y.js';
|
|
40
|
+
export { DefaultLink, defaultNavigate, isPathActive, resolveNavigation } from '../../chunk-WGMEVX3Y.js';
|
|
40
41
|
import { useKeyboardNavigation } from '../../chunk-YJ6MPS6U.js';
|
|
41
42
|
import { useIsMobile } from '../../chunk-VFO3I7PV.js';
|
|
42
43
|
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
44
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
44
|
-
import { useState, useRef, useEffect, useCallback, useMemo, Fragment as Fragment$1 } from 'react';
|
|
45
|
+
import { useState, useRef, useEffect, useCallback, useMemo, useId, Fragment as Fragment$1 } from 'react';
|
|
45
46
|
import { formatDistanceToNow, format } from 'date-fns';
|
|
46
47
|
import { useFieldArray, Controller, useWatch } from 'react-hook-form';
|
|
47
48
|
import { useResource, useWizard, useResourceForm, useOne, buildArrayItemDefaults } from '@marktiderman/genesis-core/hooks';
|
|
@@ -59,6 +60,7 @@ function DataPageShell({
|
|
|
59
60
|
isEmpty = false,
|
|
60
61
|
emptyIcon,
|
|
61
62
|
emptyMessage,
|
|
63
|
+
emptyDescription,
|
|
62
64
|
emptyAction,
|
|
63
65
|
hasError = false,
|
|
64
66
|
errorMessage: errorMessage2,
|
|
@@ -89,11 +91,13 @@ function DataPageShell({
|
|
|
89
91
|
}) {
|
|
90
92
|
const entity = entityName || title.toLowerCase();
|
|
91
93
|
const collapseFilters = filtersCollapsible && filters != null && header !== null;
|
|
94
|
+
const toolbarOnly = header === "toolbar";
|
|
95
|
+
const baseHeaderOptions = toolbarOnly ? { showTitle: false, ...headerOptions } : headerOptions;
|
|
92
96
|
const mergedOptions = {
|
|
93
97
|
defaultDetailsOpen: defaultFiltersOpen,
|
|
94
98
|
detailsLabel: filtersLabel,
|
|
95
99
|
subtitleInDetails: collapseFilters ? true : void 0,
|
|
96
|
-
...
|
|
100
|
+
...baseHeaderOptions
|
|
97
101
|
};
|
|
98
102
|
const sharedHeaderProps = {
|
|
99
103
|
title,
|
|
@@ -114,7 +118,7 @@ function DataPageShell({
|
|
|
114
118
|
stats,
|
|
115
119
|
optionsStorageKey: headerOptionsStorageKey
|
|
116
120
|
};
|
|
117
|
-
const Header = header === null ? (
|
|
121
|
+
const Header = header === null ? () => null : header === "toolbar" || header === void 0 ? PageHeader : header;
|
|
118
122
|
return /* @__PURE__ */ jsxs("div", { className: cn("space-y-6", className), children: [
|
|
119
123
|
collapseFilters ? /* @__PURE__ */ jsx(
|
|
120
124
|
Header,
|
|
@@ -128,7 +132,7 @@ function DataPageShell({
|
|
|
128
132
|
options: mergedOptions
|
|
129
133
|
}
|
|
130
134
|
) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
131
|
-
/* @__PURE__ */ jsx(Header, { ...sharedHeaderProps, options:
|
|
135
|
+
/* @__PURE__ */ jsx(Header, { ...sharedHeaderProps, options: baseHeaderOptions }),
|
|
132
136
|
filters
|
|
133
137
|
] }),
|
|
134
138
|
isLoading ? /* @__PURE__ */ jsx(
|
|
@@ -158,6 +162,7 @@ function DataPageShell({
|
|
|
158
162
|
{
|
|
159
163
|
icon: emptyIcon,
|
|
160
164
|
title: emptyMessage,
|
|
165
|
+
...emptyDescription ? { description: emptyDescription } : {},
|
|
161
166
|
action: emptyAction,
|
|
162
167
|
hasFilters: hasActiveFilters,
|
|
163
168
|
onClearFilters
|
|
@@ -540,6 +545,28 @@ function DataFilters({
|
|
|
540
545
|
] });
|
|
541
546
|
}
|
|
542
547
|
var DEFAULT_BATCH_SIZE = 48;
|
|
548
|
+
var NESTED_CONTROL = "a,button,input,select,textarea,label,[role=button],[role=link],[role=checkbox],[role=switch],[role=menuitem],[contenteditable=true]";
|
|
549
|
+
function activation(item, onItemClick, getItemLabel) {
|
|
550
|
+
if (!onItemClick) return {};
|
|
551
|
+
return {
|
|
552
|
+
role: "button",
|
|
553
|
+
tabIndex: 0,
|
|
554
|
+
"aria-label": getItemLabel?.(item),
|
|
555
|
+
onClick: (e) => {
|
|
556
|
+
const control = e.target.closest(NESTED_CONTROL);
|
|
557
|
+
if (control && control !== e.currentTarget) return;
|
|
558
|
+
onItemClick(item);
|
|
559
|
+
},
|
|
560
|
+
onKeyDown: (e) => {
|
|
561
|
+
if (e.target !== e.currentTarget) return;
|
|
562
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
563
|
+
e.preventDefault();
|
|
564
|
+
onItemClick(item);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
var ACTIVATABLE = "cursor-pointer outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background";
|
|
543
570
|
function DataGrid({
|
|
544
571
|
items,
|
|
545
572
|
viewMode,
|
|
@@ -551,7 +578,9 @@ function DataGrid({
|
|
|
551
578
|
staggerAnimation = true,
|
|
552
579
|
getKey,
|
|
553
580
|
focusedIndex = -1,
|
|
554
|
-
keyboardContainerRef
|
|
581
|
+
keyboardContainerRef,
|
|
582
|
+
onItemClick,
|
|
583
|
+
getItemLabel
|
|
555
584
|
}) {
|
|
556
585
|
const [visibleCount, setVisibleCount] = useState(batchSize);
|
|
557
586
|
const sentinelRef = useRef(null);
|
|
@@ -587,8 +616,10 @@ function DataGrid({
|
|
|
587
616
|
"div",
|
|
588
617
|
{
|
|
589
618
|
"data-nav-item": true,
|
|
619
|
+
...activation(item, onItemClick, getItemLabel),
|
|
590
620
|
className: cn(
|
|
591
621
|
"px-4 py-3 transition-colors hover:bg-accent/50",
|
|
622
|
+
onItemClick && ACTIVATABLE,
|
|
592
623
|
focusedIndex === i && "ring-2 ring-inset ring-primary bg-accent/50"
|
|
593
624
|
),
|
|
594
625
|
children: renderListItem(item, i)
|
|
@@ -605,8 +636,10 @@ function DataGrid({
|
|
|
605
636
|
"div",
|
|
606
637
|
{
|
|
607
638
|
"data-nav-item": true,
|
|
639
|
+
...activation(item, onItemClick, getItemLabel),
|
|
608
640
|
className: cn(
|
|
609
641
|
staggerAnimation && "animate-fade-in",
|
|
642
|
+
onItemClick && cn(ACTIVATABLE, "h-full rounded-xl"),
|
|
610
643
|
focusedIndex === i && "ring-2 ring-primary ring-offset-2 ring-offset-background rounded-xl transition-shadow duration-150"
|
|
611
644
|
),
|
|
612
645
|
style: staggerAnimation ? {
|
|
@@ -639,7 +672,20 @@ var DENSITY_HEAD = {
|
|
|
639
672
|
};
|
|
640
673
|
function getCellClasses(col) {
|
|
641
674
|
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" : "";
|
|
642
|
-
return cn(col.width, hide);
|
|
675
|
+
return cn(typeof col.width === "string" && !PX_WIDTH.test(col.width) ? col.width : void 0, hide);
|
|
676
|
+
}
|
|
677
|
+
var PX_WIDTH = /^\s*\d+(\.\d+)?(px)?\s*$/;
|
|
678
|
+
function pxWidth(width) {
|
|
679
|
+
if (typeof width === "number") return width > 0 ? width : void 0;
|
|
680
|
+
if (width && PX_WIDTH.test(width)) {
|
|
681
|
+
const px = parseFloat(width);
|
|
682
|
+
return px > 0 ? px : void 0;
|
|
683
|
+
}
|
|
684
|
+
return void 0;
|
|
685
|
+
}
|
|
686
|
+
function colHideClass(hideBelow) {
|
|
687
|
+
if (!hideBelow) return void 0;
|
|
688
|
+
return hideBelow === "sm" ? "hidden sm:table-column" : hideBelow === "md" ? "hidden md:table-column" : hideBelow === "lg" ? "hidden lg:table-column" : "hidden xl:table-column";
|
|
643
689
|
}
|
|
644
690
|
function errorMessage(error) {
|
|
645
691
|
return error instanceof Error ? error.message : String(error);
|
|
@@ -946,10 +992,8 @@ function DataTable({
|
|
|
946
992
|
}
|
|
947
993
|
const initial = {};
|
|
948
994
|
columns.forEach((c) => {
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
if (!isNaN(px) && px > 0) initial[c.key] = px;
|
|
952
|
-
}
|
|
995
|
+
const px = pxWidth(c.width);
|
|
996
|
+
if (px) initial[c.key] = px;
|
|
953
997
|
});
|
|
954
998
|
return initial;
|
|
955
999
|
});
|
|
@@ -1042,6 +1086,21 @@ function DataTable({
|
|
|
1042
1086
|
)
|
|
1043
1087
|
] }) }),
|
|
1044
1088
|
/* @__PURE__ */ jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxs(Table, { style: { tableLayout: "fixed" }, children: [
|
|
1089
|
+
/* @__PURE__ */ jsxs("colgroup", { children: [
|
|
1090
|
+
selectable && /* @__PURE__ */ jsx("col", { style: { width: 40 } }),
|
|
1091
|
+
activeColumns.map((col) => {
|
|
1092
|
+
const w = colWidths[col.key];
|
|
1093
|
+
return /* @__PURE__ */ jsx(
|
|
1094
|
+
"col",
|
|
1095
|
+
{
|
|
1096
|
+
className: colHideClass(col.hideBelow),
|
|
1097
|
+
style: w ? { width: w } : void 0
|
|
1098
|
+
},
|
|
1099
|
+
col.key
|
|
1100
|
+
);
|
|
1101
|
+
}),
|
|
1102
|
+
renderActions && /* @__PURE__ */ jsx("col", { style: { width: 40 } })
|
|
1103
|
+
] }),
|
|
1045
1104
|
/* @__PURE__ */ jsx(
|
|
1046
1105
|
TableHeader,
|
|
1047
1106
|
{
|
|
@@ -1066,6 +1125,7 @@ function DataTable({
|
|
|
1066
1125
|
getCellClasses(col),
|
|
1067
1126
|
headDensity,
|
|
1068
1127
|
col.sortable && "cursor-pointer select-none",
|
|
1128
|
+
"overflow-hidden",
|
|
1069
1129
|
resizableColumns && "relative",
|
|
1070
1130
|
isFirstCol && "sticky left-0 bg-background",
|
|
1071
1131
|
isFirstCol && stickyHeader ? "z-[15]" : isFirstCol ? "z-[5]" : void 0
|
|
@@ -1073,12 +1133,12 @@ function DataTable({
|
|
|
1073
1133
|
style: resizeWidth ? { width: resizeWidth, minWidth: resizeWidth } : void 0,
|
|
1074
1134
|
onClick: col.sortable ? (e) => handleSort(col.key, e.shiftKey) : void 0,
|
|
1075
1135
|
children: [
|
|
1076
|
-
/* @__PURE__ */ jsxs("span", { className: "
|
|
1077
|
-
col.header,
|
|
1136
|
+
/* @__PURE__ */ jsxs("span", { className: "flex min-w-0 items-center gap-1", children: [
|
|
1137
|
+
/* @__PURE__ */ jsx("span", { className: "truncate", title: col.header, children: col.header }),
|
|
1078
1138
|
col.sortable && (sortEntry ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1079
|
-
sortEntry.dir === "asc" ? /* @__PURE__ */ jsx(ArrowUp, { className: "h-3 w-3 text-foreground" }) : /* @__PURE__ */ jsx(ArrowDown, { className: "h-3 w-3 text-foreground" }),
|
|
1139
|
+
sortEntry.dir === "asc" ? /* @__PURE__ */ jsx(ArrowUp, { className: "h-3 w-3 shrink-0 text-foreground" }) : /* @__PURE__ */ jsx(ArrowDown, { className: "h-3 w-3 shrink-0 text-foreground" }),
|
|
1080
1140
|
sorts.length > 1 && /* @__PURE__ */ jsx("span", { className: "text-[10px] text-muted-foreground font-mono", children: sortIndex + 1 })
|
|
1081
|
-
] }) : /* @__PURE__ */ jsx(ArrowUpDown, { className: "h-3 w-3 text-muted-foreground/50" }))
|
|
1141
|
+
] }) : /* @__PURE__ */ jsx(ArrowUpDown, { className: "h-3 w-3 shrink-0 text-muted-foreground/50" }))
|
|
1082
1142
|
] }),
|
|
1083
1143
|
resizableColumns && /* @__PURE__ */ jsx(
|
|
1084
1144
|
ResizeHandle,
|
|
@@ -1754,7 +1814,11 @@ function RelationField({
|
|
|
1754
1814
|
field,
|
|
1755
1815
|
value,
|
|
1756
1816
|
onChange,
|
|
1757
|
-
disabled
|
|
1817
|
+
disabled,
|
|
1818
|
+
id,
|
|
1819
|
+
"aria-describedby": ariaDescribedBy,
|
|
1820
|
+
"aria-invalid": ariaInvalid,
|
|
1821
|
+
"aria-required": ariaRequired
|
|
1758
1822
|
}) {
|
|
1759
1823
|
const [open, setOpen] = useState(false);
|
|
1760
1824
|
const [createOpen, setCreateOpen] = useState(false);
|
|
@@ -1813,7 +1877,11 @@ function RelationField({
|
|
|
1813
1877
|
{
|
|
1814
1878
|
variant: "outline",
|
|
1815
1879
|
role: "combobox",
|
|
1880
|
+
id,
|
|
1816
1881
|
"aria-expanded": open,
|
|
1882
|
+
"aria-describedby": ariaDescribedBy,
|
|
1883
|
+
"aria-invalid": ariaInvalid,
|
|
1884
|
+
"aria-required": ariaRequired,
|
|
1817
1885
|
className: "w-full justify-between font-normal",
|
|
1818
1886
|
disabled: disabled || isLoading,
|
|
1819
1887
|
children: [
|
|
@@ -1924,7 +1992,12 @@ function FileUploadField({
|
|
|
1924
1992
|
field,
|
|
1925
1993
|
value,
|
|
1926
1994
|
onChange,
|
|
1927
|
-
disabled
|
|
1995
|
+
disabled,
|
|
1996
|
+
id,
|
|
1997
|
+
"aria-labelledby": ariaLabelledBy,
|
|
1998
|
+
"aria-describedby": ariaDescribedBy,
|
|
1999
|
+
"aria-invalid": ariaInvalid,
|
|
2000
|
+
"aria-required": ariaRequired
|
|
1928
2001
|
}) {
|
|
1929
2002
|
const multiple = field.multiple ?? false;
|
|
1930
2003
|
const maxSize = field.maxSize ?? DEFAULT_MAX_SIZE;
|
|
@@ -1997,7 +2070,16 @@ function FileUploadField({
|
|
|
1997
2070
|
/* @__PURE__ */ jsxs(
|
|
1998
2071
|
"div",
|
|
1999
2072
|
{
|
|
2000
|
-
...getRootProps(
|
|
2073
|
+
...getRootProps({
|
|
2074
|
+
// The focusable drop zone carries the field's name, description
|
|
2075
|
+
// and state; react-dropzone's default `role="presentation"` would
|
|
2076
|
+
// leave a keyboard user on an unnamed tab stop.
|
|
2077
|
+
role: "button",
|
|
2078
|
+
"aria-labelledby": ariaLabelledBy,
|
|
2079
|
+
"aria-describedby": ariaDescribedBy,
|
|
2080
|
+
"aria-invalid": ariaInvalid,
|
|
2081
|
+
"aria-disabled": disabled || void 0
|
|
2082
|
+
}),
|
|
2001
2083
|
className: cn(
|
|
2002
2084
|
"flex cursor-pointer flex-col items-center justify-center rounded-lg border-2 border-dashed px-6 py-8 text-center transition-colors",
|
|
2003
2085
|
isDragActive ? "border-primary bg-primary/5" : "border-muted-foreground/25 hover:border-muted-foreground/50",
|
|
@@ -2005,7 +2087,17 @@ function FileUploadField({
|
|
|
2005
2087
|
hasFiles && "py-4"
|
|
2006
2088
|
),
|
|
2007
2089
|
children: [
|
|
2008
|
-
/* @__PURE__ */ jsx(
|
|
2090
|
+
/* @__PURE__ */ jsx(
|
|
2091
|
+
"input",
|
|
2092
|
+
{
|
|
2093
|
+
...getInputProps({
|
|
2094
|
+
id,
|
|
2095
|
+
"aria-describedby": ariaDescribedBy,
|
|
2096
|
+
"aria-invalid": ariaInvalid,
|
|
2097
|
+
"aria-required": ariaRequired
|
|
2098
|
+
})
|
|
2099
|
+
}
|
|
2100
|
+
),
|
|
2009
2101
|
isImage ? /* @__PURE__ */ jsx(ImageIcon, { className: "mb-2 h-8 w-8 text-muted-foreground" }) : /* @__PURE__ */ jsx(Upload, { className: "mb-2 h-8 w-8 text-muted-foreground" }),
|
|
2010
2102
|
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: isDragActive ? "Drop files here..." : "Drag & drop or click to upload" }),
|
|
2011
2103
|
/* @__PURE__ */ jsxs("p", { className: "mt-1 text-xs text-muted-foreground/70", children: [
|
|
@@ -2182,6 +2274,11 @@ function ResourceFormFieldRenderer({
|
|
|
2182
2274
|
watchedValues
|
|
2183
2275
|
}) {
|
|
2184
2276
|
const label = field.label ?? titleCase(field.key);
|
|
2277
|
+
const baseId = useId();
|
|
2278
|
+
const inputId = `${baseId}-input`;
|
|
2279
|
+
const errorId = `${baseId}-error`;
|
|
2280
|
+
const descId = `${baseId}-desc`;
|
|
2281
|
+
const labelId = `${baseId}-label`;
|
|
2185
2282
|
const fieldType = field.type ?? "text";
|
|
2186
2283
|
const isReadOnly = action === "edit" && field.readOnlyOnEdit;
|
|
2187
2284
|
const isDisabled = isReadOnly || (watchedValues ? field.disabled?.(watchedValues) === true : false);
|
|
@@ -2213,14 +2310,20 @@ function ResourceFormFieldRenderer({
|
|
|
2213
2310
|
control,
|
|
2214
2311
|
render: ({ field: f, fieldState }) => {
|
|
2215
2312
|
const errorMessage2 = fieldState.error?.message;
|
|
2313
|
+
const describedBy = [field.description ? descId : null, errorMessage2 ? errorId : null].filter(Boolean).join(" ") || void 0;
|
|
2216
2314
|
const ariaProps = {
|
|
2315
|
+
id: inputId,
|
|
2217
2316
|
"aria-invalid": !!errorMessage2 || void 0,
|
|
2218
|
-
"aria-describedby":
|
|
2317
|
+
"aria-describedby": describedBy,
|
|
2219
2318
|
"aria-required": field.required || void 0
|
|
2220
2319
|
};
|
|
2221
2320
|
if (fieldType === "switch") {
|
|
2222
2321
|
return /* @__PURE__ */ jsxs(FormField, { className: "flex flex-row items-center justify-between rounded-lg border p-4", children: [
|
|
2223
|
-
/* @__PURE__ */
|
|
2322
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-0.5", children: [
|
|
2323
|
+
/* @__PURE__ */ jsx(FormLabel, { htmlFor: inputId, error: !!errorMessage2, children: label }),
|
|
2324
|
+
field.description && /* @__PURE__ */ jsx(FormDescription, { id: descId, children: field.description }),
|
|
2325
|
+
/* @__PURE__ */ jsx(FormError, { id: errorId, children: errorMessage2 })
|
|
2326
|
+
] }),
|
|
2224
2327
|
/* @__PURE__ */ jsx(
|
|
2225
2328
|
Switch,
|
|
2226
2329
|
{
|
|
@@ -2244,14 +2347,15 @@ function ResourceFormFieldRenderer({
|
|
|
2244
2347
|
}
|
|
2245
2348
|
),
|
|
2246
2349
|
/* @__PURE__ */ jsxs("div", { className: "space-y-1 leading-none", children: [
|
|
2247
|
-
/* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage2, children: label }),
|
|
2248
|
-
/* @__PURE__ */ jsx(
|
|
2350
|
+
/* @__PURE__ */ jsx(FormLabel, { htmlFor: inputId, error: !!errorMessage2, children: label }),
|
|
2351
|
+
field.description && /* @__PURE__ */ jsx(FormDescription, { id: descId, children: field.description }),
|
|
2352
|
+
/* @__PURE__ */ jsx(FormError, { id: errorId, children: errorMessage2 })
|
|
2249
2353
|
] })
|
|
2250
2354
|
] });
|
|
2251
2355
|
}
|
|
2252
2356
|
if (fieldType === "select" && field.options) {
|
|
2253
2357
|
return /* @__PURE__ */ jsxs(FormField, { children: [
|
|
2254
|
-
/* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage2, required: field.required, children: label }),
|
|
2358
|
+
/* @__PURE__ */ jsx(FormLabel, { htmlFor: inputId, error: !!errorMessage2, required: field.required, children: label }),
|
|
2255
2359
|
/* @__PURE__ */ jsxs(
|
|
2256
2360
|
Select,
|
|
2257
2361
|
{
|
|
@@ -2270,49 +2374,53 @@ function ResourceFormFieldRenderer({
|
|
|
2270
2374
|
]
|
|
2271
2375
|
}
|
|
2272
2376
|
),
|
|
2273
|
-
field.description && /* @__PURE__ */ jsx(FormDescription, { id:
|
|
2274
|
-
/* @__PURE__ */ jsx(FormError, { id:
|
|
2377
|
+
field.description && /* @__PURE__ */ jsx(FormDescription, { id: descId, children: field.description }),
|
|
2378
|
+
/* @__PURE__ */ jsx(FormError, { id: errorId, children: errorMessage2 })
|
|
2275
2379
|
] });
|
|
2276
2380
|
}
|
|
2277
2381
|
if (fieldType === "relation" && field.relation) {
|
|
2278
2382
|
return /* @__PURE__ */ jsxs(FormField, { children: [
|
|
2279
|
-
/* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage2, required: field.required, children: label }),
|
|
2383
|
+
/* @__PURE__ */ jsx(FormLabel, { htmlFor: inputId, error: !!errorMessage2, required: field.required, children: label }),
|
|
2280
2384
|
/* @__PURE__ */ jsx(
|
|
2281
2385
|
RelationField,
|
|
2282
2386
|
{
|
|
2283
2387
|
field,
|
|
2284
2388
|
value: f.value != null ? String(f.value) : "",
|
|
2285
2389
|
onChange: f.onChange,
|
|
2286
|
-
disabled: isDisabled
|
|
2390
|
+
disabled: isDisabled,
|
|
2391
|
+
...ariaProps
|
|
2287
2392
|
}
|
|
2288
2393
|
),
|
|
2289
|
-
field.description && /* @__PURE__ */ jsx(FormDescription, { id:
|
|
2290
|
-
/* @__PURE__ */ jsx(FormError, { id:
|
|
2394
|
+
field.description && /* @__PURE__ */ jsx(FormDescription, { id: descId, children: field.description }),
|
|
2395
|
+
/* @__PURE__ */ jsx(FormError, { id: errorId, children: errorMessage2 })
|
|
2291
2396
|
] });
|
|
2292
2397
|
}
|
|
2293
2398
|
if (fieldType === "file" || fieldType === "image") {
|
|
2294
2399
|
return /* @__PURE__ */ jsxs(FormField, { children: [
|
|
2295
|
-
/* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage2, required: field.required, children: label }),
|
|
2400
|
+
/* @__PURE__ */ jsx(FormLabel, { id: labelId, htmlFor: inputId, error: !!errorMessage2, required: field.required, children: label }),
|
|
2296
2401
|
/* @__PURE__ */ jsx(
|
|
2297
2402
|
FileUploadField,
|
|
2298
2403
|
{
|
|
2404
|
+
"aria-labelledby": labelId,
|
|
2299
2405
|
field,
|
|
2300
2406
|
value: f.value,
|
|
2301
2407
|
onChange: f.onChange,
|
|
2302
|
-
disabled: isDisabled
|
|
2408
|
+
disabled: isDisabled,
|
|
2409
|
+
...ariaProps
|
|
2303
2410
|
}
|
|
2304
2411
|
),
|
|
2305
|
-
field.description && /* @__PURE__ */ jsx(FormDescription, { id:
|
|
2306
|
-
/* @__PURE__ */ jsx(FormError, { id:
|
|
2412
|
+
field.description && /* @__PURE__ */ jsx(FormDescription, { id: descId, children: field.description }),
|
|
2413
|
+
/* @__PURE__ */ jsx(FormError, { id: errorId, children: errorMessage2 })
|
|
2307
2414
|
] });
|
|
2308
2415
|
}
|
|
2309
2416
|
if (fieldType === "textarea") {
|
|
2310
2417
|
return /* @__PURE__ */ jsxs(FormField, { children: [
|
|
2311
|
-
/* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage2, required: field.required, children: label }),
|
|
2418
|
+
/* @__PURE__ */ jsx(FormLabel, { htmlFor: inputId, error: !!errorMessage2, required: field.required, children: label }),
|
|
2312
2419
|
/* @__PURE__ */ jsx(
|
|
2313
2420
|
Textarea,
|
|
2314
2421
|
{
|
|
2315
2422
|
placeholder: field.placeholder,
|
|
2423
|
+
maxLength: field.maxLength,
|
|
2316
2424
|
rows: field.rows ?? 3,
|
|
2317
2425
|
value: f.value != null ? String(f.value) : "",
|
|
2318
2426
|
onChange: (e) => f.onChange(e.target.value),
|
|
@@ -2321,13 +2429,13 @@ function ResourceFormFieldRenderer({
|
|
|
2321
2429
|
...ariaProps
|
|
2322
2430
|
}
|
|
2323
2431
|
),
|
|
2324
|
-
field.description && /* @__PURE__ */ jsx(FormDescription, { id:
|
|
2325
|
-
/* @__PURE__ */ jsx(FormError, { id:
|
|
2432
|
+
field.description && /* @__PURE__ */ jsx(FormDescription, { id: descId, children: field.description }),
|
|
2433
|
+
/* @__PURE__ */ jsx(FormError, { id: errorId, children: errorMessage2 })
|
|
2326
2434
|
] });
|
|
2327
2435
|
}
|
|
2328
2436
|
if (fieldType === "number") {
|
|
2329
2437
|
return /* @__PURE__ */ jsxs(FormField, { children: [
|
|
2330
|
-
/* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage2, required: field.required, children: label }),
|
|
2438
|
+
/* @__PURE__ */ jsx(FormLabel, { htmlFor: inputId, error: !!errorMessage2, required: field.required, children: label }),
|
|
2331
2439
|
/* @__PURE__ */ jsx(
|
|
2332
2440
|
Input,
|
|
2333
2441
|
{
|
|
@@ -2345,17 +2453,18 @@ function ResourceFormFieldRenderer({
|
|
|
2345
2453
|
...ariaProps
|
|
2346
2454
|
}
|
|
2347
2455
|
),
|
|
2348
|
-
field.description && /* @__PURE__ */ jsx(FormDescription, { id:
|
|
2349
|
-
/* @__PURE__ */ jsx(FormError, { id:
|
|
2456
|
+
field.description && /* @__PURE__ */ jsx(FormDescription, { id: descId, children: field.description }),
|
|
2457
|
+
/* @__PURE__ */ jsx(FormError, { id: errorId, children: errorMessage2 })
|
|
2350
2458
|
] });
|
|
2351
2459
|
}
|
|
2352
2460
|
return /* @__PURE__ */ jsxs(FormField, { children: [
|
|
2353
|
-
/* @__PURE__ */ jsx(FormLabel, { error: !!errorMessage2, required: field.required, children: label }),
|
|
2461
|
+
/* @__PURE__ */ jsx(FormLabel, { htmlFor: inputId, error: !!errorMessage2, required: field.required, children: label }),
|
|
2354
2462
|
/* @__PURE__ */ jsx(
|
|
2355
2463
|
Input,
|
|
2356
2464
|
{
|
|
2357
2465
|
type: fieldType,
|
|
2358
2466
|
placeholder: field.placeholder,
|
|
2467
|
+
maxLength: field.maxLength,
|
|
2359
2468
|
value: f.value != null ? String(f.value) : "",
|
|
2360
2469
|
onChange: (e) => f.onChange(e.target.value),
|
|
2361
2470
|
disabled: isDisabled,
|
|
@@ -2363,8 +2472,8 @@ function ResourceFormFieldRenderer({
|
|
|
2363
2472
|
...ariaProps
|
|
2364
2473
|
}
|
|
2365
2474
|
),
|
|
2366
|
-
field.description && /* @__PURE__ */ jsx(FormDescription, { id:
|
|
2367
|
-
/* @__PURE__ */ jsx(FormError, { id:
|
|
2475
|
+
field.description && /* @__PURE__ */ jsx(FormDescription, { id: descId, children: field.description }),
|
|
2476
|
+
/* @__PURE__ */ jsx(FormError, { id: errorId, children: errorMessage2 })
|
|
2368
2477
|
] });
|
|
2369
2478
|
}
|
|
2370
2479
|
}
|
|
@@ -2476,6 +2585,19 @@ function FormBody({
|
|
|
2476
2585
|
control: form.control,
|
|
2477
2586
|
disabled: !hasConditionalFields
|
|
2478
2587
|
});
|
|
2588
|
+
const rootError = form.formState.errors.root?.message;
|
|
2589
|
+
const rootErrorBanner = rootError ? /* @__PURE__ */ jsxs(
|
|
2590
|
+
"div",
|
|
2591
|
+
{
|
|
2592
|
+
role: "alert",
|
|
2593
|
+
"data-testid": "resource-form-error",
|
|
2594
|
+
className: "flex items-start gap-2 rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-sm text-destructive",
|
|
2595
|
+
children: [
|
|
2596
|
+
/* @__PURE__ */ jsx(AlertTriangle, { className: "mt-0.5 h-4 w-4 shrink-0", "aria-hidden": "true" }),
|
|
2597
|
+
/* @__PURE__ */ jsx("span", { children: rootError })
|
|
2598
|
+
]
|
|
2599
|
+
}
|
|
2600
|
+
) : null;
|
|
2479
2601
|
const onInvalid = (errors) => {
|
|
2480
2602
|
const firstErrorKey = Object.keys(errors)[0];
|
|
2481
2603
|
if (firstErrorKey) form.setFocus(firstErrorKey);
|
|
@@ -2484,18 +2606,21 @@ function FormBody({
|
|
|
2484
2606
|
return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-8", children: /* @__PURE__ */ jsx(Spinner, {}) });
|
|
2485
2607
|
}
|
|
2486
2608
|
if (mode === "wizard" && steps) {
|
|
2487
|
-
return /* @__PURE__ */
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
|
|
2609
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
2610
|
+
rootErrorBanner,
|
|
2611
|
+
/* @__PURE__ */ jsx(
|
|
2612
|
+
WizardForm,
|
|
2613
|
+
{
|
|
2614
|
+
steps,
|
|
2615
|
+
form,
|
|
2616
|
+
fields: resolvedFields,
|
|
2617
|
+
onSubmit: handleSave,
|
|
2618
|
+
isSaving,
|
|
2619
|
+
action,
|
|
2620
|
+
onInvalid
|
|
2621
|
+
}
|
|
2622
|
+
)
|
|
2623
|
+
] });
|
|
2499
2624
|
}
|
|
2500
2625
|
const visibleFields = resolvedFields.filter(
|
|
2501
2626
|
(field) => field.visible?.(watchedValues) !== false
|
|
@@ -2535,6 +2660,7 @@ function FormBody({
|
|
|
2535
2660
|
return /* @__PURE__ */ jsxs(
|
|
2536
2661
|
"form",
|
|
2537
2662
|
{
|
|
2663
|
+
noValidate: true,
|
|
2538
2664
|
onSubmit: (e) => {
|
|
2539
2665
|
e.preventDefault();
|
|
2540
2666
|
void form.handleSubmit(handleSave, onInvalid)(e);
|
|
@@ -2548,6 +2674,7 @@ function FormBody({
|
|
|
2548
2674
|
] }),
|
|
2549
2675
|
renderFields(group.fields)
|
|
2550
2676
|
] }, group.name ?? "__default")) }) }),
|
|
2677
|
+
rootErrorBanner,
|
|
2551
2678
|
/* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
|
|
2552
2679
|
/* @__PURE__ */ jsx(
|
|
2553
2680
|
Button,
|
|
@@ -2584,7 +2711,9 @@ function ResourceForm(props) {
|
|
|
2584
2711
|
layout: layoutProp = "dialog",
|
|
2585
2712
|
mode = "standard",
|
|
2586
2713
|
steps,
|
|
2587
|
-
columns = 1
|
|
2714
|
+
columns = 1,
|
|
2715
|
+
prefillFromUrl,
|
|
2716
|
+
autoPlaceholder = false
|
|
2588
2717
|
} = props;
|
|
2589
2718
|
const isMobile = useIsMobile();
|
|
2590
2719
|
const effectiveLayout = isMobile ? "sheet" : layoutProp;
|
|
@@ -2602,7 +2731,9 @@ function ResourceForm(props) {
|
|
|
2602
2731
|
onOpenChange(false);
|
|
2603
2732
|
onSuccess?.(data);
|
|
2604
2733
|
},
|
|
2605
|
-
onError
|
|
2734
|
+
onError,
|
|
2735
|
+
prefillFromUrl,
|
|
2736
|
+
autoPlaceholder
|
|
2606
2737
|
};
|
|
2607
2738
|
const { form, resolvedFields, handleSave, isSaving, isLoading } = useResourceForm(hookOptions);
|
|
2608
2739
|
const bodyProps = {
|
|
@@ -2642,11 +2773,85 @@ function ResourceForm(props) {
|
|
|
2642
2773
|
/* @__PURE__ */ jsx(CardContent, { children: /* @__PURE__ */ jsx(FormBody, { ...bodyProps }) })
|
|
2643
2774
|
] });
|
|
2644
2775
|
}
|
|
2776
|
+
function ConfirmDeleteDialog({
|
|
2777
|
+
open,
|
|
2778
|
+
onOpenChange,
|
|
2779
|
+
onConfirm,
|
|
2780
|
+
title = "Delete this record?",
|
|
2781
|
+
description = "This can't be undone.",
|
|
2782
|
+
confirmLabel = "Delete",
|
|
2783
|
+
cancelLabel = "Cancel"
|
|
2784
|
+
}) {
|
|
2785
|
+
const [pending, setPending] = useState(false);
|
|
2786
|
+
const [error, setError] = useState(null);
|
|
2787
|
+
useEffect(() => {
|
|
2788
|
+
if (open) setError(null);
|
|
2789
|
+
}, [open]);
|
|
2790
|
+
const confirm = async () => {
|
|
2791
|
+
setPending(true);
|
|
2792
|
+
setError(null);
|
|
2793
|
+
try {
|
|
2794
|
+
await onConfirm();
|
|
2795
|
+
onOpenChange(false);
|
|
2796
|
+
} catch (err) {
|
|
2797
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
2798
|
+
} finally {
|
|
2799
|
+
setPending(false);
|
|
2800
|
+
}
|
|
2801
|
+
};
|
|
2802
|
+
return /* @__PURE__ */ jsx(
|
|
2803
|
+
AlertDialog,
|
|
2804
|
+
{
|
|
2805
|
+
open,
|
|
2806
|
+
onOpenChange: (next) => {
|
|
2807
|
+
if (!pending) onOpenChange(next);
|
|
2808
|
+
},
|
|
2809
|
+
children: /* @__PURE__ */ jsxs(AlertDialogContent, { "data-testid": "confirm-delete-dialog", children: [
|
|
2810
|
+
/* @__PURE__ */ jsxs(AlertDialogHeader, { children: [
|
|
2811
|
+
/* @__PURE__ */ jsx(AlertDialogTitle, { children: title }),
|
|
2812
|
+
/* @__PURE__ */ jsx(AlertDialogDescription, { children: description })
|
|
2813
|
+
] }),
|
|
2814
|
+
error ? /* @__PURE__ */ jsxs(
|
|
2815
|
+
"div",
|
|
2816
|
+
{
|
|
2817
|
+
role: "alert",
|
|
2818
|
+
"data-testid": "confirm-delete-error",
|
|
2819
|
+
className: "flex items-start gap-2 rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-sm text-destructive",
|
|
2820
|
+
children: [
|
|
2821
|
+
/* @__PURE__ */ jsx(AlertTriangle, { className: "mt-0.5 h-4 w-4 shrink-0", "aria-hidden": "true" }),
|
|
2822
|
+
/* @__PURE__ */ jsx("span", { children: error })
|
|
2823
|
+
]
|
|
2824
|
+
}
|
|
2825
|
+
) : null,
|
|
2826
|
+
/* @__PURE__ */ jsxs(AlertDialogFooter, { children: [
|
|
2827
|
+
/* @__PURE__ */ jsx(AlertDialogCancel, { disabled: pending, "data-testid": "confirm-delete-cancel", children: cancelLabel }),
|
|
2828
|
+
/* @__PURE__ */ jsxs(
|
|
2829
|
+
AlertDialogAction,
|
|
2830
|
+
{
|
|
2831
|
+
disabled: pending,
|
|
2832
|
+
"data-testid": "confirm-delete-confirm",
|
|
2833
|
+
onClick: (e) => {
|
|
2834
|
+
e.preventDefault();
|
|
2835
|
+
void confirm();
|
|
2836
|
+
},
|
|
2837
|
+
children: [
|
|
2838
|
+
pending ? /* @__PURE__ */ jsx(Spinner, { size: "sm", className: "mr-2" }) : null,
|
|
2839
|
+
confirmLabel
|
|
2840
|
+
]
|
|
2841
|
+
}
|
|
2842
|
+
)
|
|
2843
|
+
] })
|
|
2844
|
+
] })
|
|
2845
|
+
}
|
|
2846
|
+
);
|
|
2847
|
+
}
|
|
2645
2848
|
function ResourcePage(props) {
|
|
2646
2849
|
const {
|
|
2647
2850
|
resource: resourceName,
|
|
2648
2851
|
data: dataProp,
|
|
2649
2852
|
total: totalProp,
|
|
2853
|
+
pagination = "client",
|
|
2854
|
+
clientRowLimit = 1e3,
|
|
2650
2855
|
title,
|
|
2651
2856
|
subtitle,
|
|
2652
2857
|
icon,
|
|
@@ -2656,6 +2861,8 @@ function ResourcePage(props) {
|
|
|
2656
2861
|
detailWidth = "md",
|
|
2657
2862
|
titleField: titleFieldProp,
|
|
2658
2863
|
subtitleField: subtitleFieldProp,
|
|
2864
|
+
getTitle,
|
|
2865
|
+
getSubtitle,
|
|
2659
2866
|
renderDetail: renderDetailProp,
|
|
2660
2867
|
detailFields,
|
|
2661
2868
|
statusFilter,
|
|
@@ -2689,7 +2896,13 @@ function ResourcePage(props) {
|
|
|
2689
2896
|
onCreate,
|
|
2690
2897
|
onEdit,
|
|
2691
2898
|
onDelete,
|
|
2899
|
+
canEditItem,
|
|
2692
2900
|
createLabel = "New",
|
|
2901
|
+
canEdit,
|
|
2902
|
+
canDelete,
|
|
2903
|
+
allowDelete,
|
|
2904
|
+
confirmDelete,
|
|
2905
|
+
onDeleted,
|
|
2693
2906
|
secondaryActions,
|
|
2694
2907
|
allowCreate,
|
|
2695
2908
|
allowEdit,
|
|
@@ -2700,12 +2913,17 @@ function ResourcePage(props) {
|
|
|
2700
2913
|
formLayout: formLayoutProp,
|
|
2701
2914
|
bulkActions,
|
|
2702
2915
|
renderCard: renderCardProp,
|
|
2916
|
+
openOnCardClick = false,
|
|
2703
2917
|
renderTable: renderTableProp,
|
|
2704
2918
|
gridCols: gridColsProp,
|
|
2705
2919
|
keyboardNavigation = true,
|
|
2706
2920
|
capabilities,
|
|
2707
2921
|
toolbar,
|
|
2708
2922
|
emptyState,
|
|
2923
|
+
emptyIcon,
|
|
2924
|
+
emptyMessage,
|
|
2925
|
+
emptyDescription,
|
|
2926
|
+
header: headerMode = "page",
|
|
2709
2927
|
resourceOptions,
|
|
2710
2928
|
defaultView = "table",
|
|
2711
2929
|
onNavigate = defaultNavigate,
|
|
@@ -2715,14 +2933,26 @@ function ResourcePage(props) {
|
|
|
2715
2933
|
className
|
|
2716
2934
|
} = props;
|
|
2717
2935
|
const isClientMode = !!resourceName;
|
|
2718
|
-
const
|
|
2936
|
+
const resourceServerPaging = isClientMode && pagination === "server";
|
|
2937
|
+
const isServerControlled = resourceServerPaging || pageProp !== void 0 || perPageProp !== void 0 || onPageChange !== void 0 || onPerPageChange !== void 0 || onSortChange !== void 0 || onSearchChange !== void 0;
|
|
2719
2938
|
const viewSettingsKey = viewSettingsKeyProp ?? resourceName ?? title.toLowerCase().replace(/\s+/g, "-");
|
|
2720
2939
|
const { settings, updateSetting } = useViewSettings(viewSettingsKey);
|
|
2721
|
-
const
|
|
2940
|
+
const serverSearchFields = resourceOptions?.searchFields ?? searchFields;
|
|
2722
2941
|
const resourceHook = useResource(
|
|
2723
2942
|
resourceName ?? "__noop__",
|
|
2724
|
-
isClientMode ?
|
|
2943
|
+
isClientMode ? {
|
|
2944
|
+
...resourceOptions,
|
|
2945
|
+
// Client paging fetches everything it can in one request; server
|
|
2946
|
+
// paging starts at the user's stored page size. An explicit
|
|
2947
|
+
// `defaultPerPage` wins over both.
|
|
2948
|
+
defaultPerPage: resourceOptions?.defaultPerPage ?? (resourceServerPaging ? perPageProp ?? settings.pageSize : clientRowLimit),
|
|
2949
|
+
searchFields: resourceServerPaging ? serverSearchFields : resourceOptions?.searchFields,
|
|
2950
|
+
// Paging, searching and sorting change the query key; keep the
|
|
2951
|
+
// current page on screen while the next one loads.
|
|
2952
|
+
keepPreviousData: resourceOptions?.keepPreviousData ?? resourceServerPaging
|
|
2953
|
+
} : { enabled: false }
|
|
2725
2954
|
);
|
|
2955
|
+
const effectivePageSize = resourceServerPaging ? resourceHook.params.pagination?.perPage ?? settings.pageSize : perPageProp ?? settings.pageSize;
|
|
2726
2956
|
const displayData = isClientMode ? resourceHook.list.data?.data ?? [] : dataProp ?? [];
|
|
2727
2957
|
const displayTotal = isClientMode ? resourceHook.list.data?.total ?? 0 : totalProp ?? 0;
|
|
2728
2958
|
const isLoading = isClientMode ? resourceHook.list.isLoading : loadingProp ?? false;
|
|
@@ -2742,6 +2972,66 @@ function ResourcePage(props) {
|
|
|
2742
2972
|
subtitleField: subtitleFieldProp,
|
|
2743
2973
|
controlled: isServerControlled
|
|
2744
2974
|
});
|
|
2975
|
+
const {
|
|
2976
|
+
setSearch: setResourceSearch,
|
|
2977
|
+
setSort: setResourceSort,
|
|
2978
|
+
setFilters: setResourceFilters
|
|
2979
|
+
} = resourceHook;
|
|
2980
|
+
const pushedSearchRef = useRef("");
|
|
2981
|
+
const searchEffectRanRef = useRef(false);
|
|
2982
|
+
useEffect(() => {
|
|
2983
|
+
if (!resourceServerPaging) return;
|
|
2984
|
+
const firstRun = !searchEffectRanRef.current;
|
|
2985
|
+
searchEffectRanRef.current = true;
|
|
2986
|
+
if (page.search === pushedSearchRef.current) return;
|
|
2987
|
+
const push = () => {
|
|
2988
|
+
pushedSearchRef.current = page.search;
|
|
2989
|
+
setResourceSearch(page.search);
|
|
2990
|
+
};
|
|
2991
|
+
if (firstRun || page.search === "") {
|
|
2992
|
+
push();
|
|
2993
|
+
return;
|
|
2994
|
+
}
|
|
2995
|
+
const timer = setTimeout(push, 300);
|
|
2996
|
+
return () => clearTimeout(timer);
|
|
2997
|
+
}, [resourceServerPaging, page.search, setResourceSearch]);
|
|
2998
|
+
const defaultSort = resourceOptions?.defaultSort;
|
|
2999
|
+
const pushedSortRef = useRef("");
|
|
3000
|
+
useEffect(() => {
|
|
3001
|
+
if (!resourceServerPaging) return;
|
|
3002
|
+
const key = page.sort ? `${page.sort.field}:${page.sort.order}` : "";
|
|
3003
|
+
if (key === pushedSortRef.current) return;
|
|
3004
|
+
pushedSortRef.current = key;
|
|
3005
|
+
setResourceSort(page.sort ?? defaultSort);
|
|
3006
|
+
}, [resourceServerPaging, page.sort, defaultSort, setResourceSort]);
|
|
3007
|
+
const statusField = statusFilter ? typeof statusFilter === "string" ? statusFilter : statusFilter.field : void 0;
|
|
3008
|
+
const defaultFilters = resourceOptions?.defaultFilters;
|
|
3009
|
+
const pushedStatusRef = useRef("");
|
|
3010
|
+
useEffect(() => {
|
|
3011
|
+
if (!resourceServerPaging || !statusField) return;
|
|
3012
|
+
const key = JSON.stringify([page.statusValue, defaultFilters ?? null]);
|
|
3013
|
+
if (key === pushedStatusRef.current) return;
|
|
3014
|
+
pushedStatusRef.current = key;
|
|
3015
|
+
setResourceFilters([
|
|
3016
|
+
...defaultFilters ?? [],
|
|
3017
|
+
...page.statusValue.length > 0 ? [{ field: statusField, operator: "in", value: page.statusValue }] : []
|
|
3018
|
+
]);
|
|
3019
|
+
}, [
|
|
3020
|
+
resourceServerPaging,
|
|
3021
|
+
statusField,
|
|
3022
|
+
page.statusValue,
|
|
3023
|
+
defaultFilters,
|
|
3024
|
+
setResourceFilters
|
|
3025
|
+
]);
|
|
3026
|
+
const clientTruncated = isClientMode && !resourceServerPaging && !isLoading && !hasError && displayTotal > displayData.length;
|
|
3027
|
+
const warnedTruncatedRef = useRef(false);
|
|
3028
|
+
useEffect(() => {
|
|
3029
|
+
if (!clientTruncated || warnedTruncatedRef.current) return;
|
|
3030
|
+
warnedTruncatedRef.current = true;
|
|
3031
|
+
console.warn(
|
|
3032
|
+
`ResourcePage: "${resourceName}" has ${displayTotal} rows but only ${displayData.length} were fetched. Pass \`pagination="server"\` to page through all of them, or raise \`clientRowLimit\`.`
|
|
3033
|
+
);
|
|
3034
|
+
}, [clientTruncated, resourceName, displayTotal, displayData.length]);
|
|
2745
3035
|
const actions = useMemo(() => {
|
|
2746
3036
|
if (isClientMode) {
|
|
2747
3037
|
return {
|
|
@@ -2774,6 +3064,8 @@ function ResourcePage(props) {
|
|
|
2774
3064
|
(item) => getKeyProp ? getKeyProp(item) : String(item.id ?? ""),
|
|
2775
3065
|
[getKeyProp]
|
|
2776
3066
|
);
|
|
3067
|
+
const selectedKey = page.selectedItem ? keyOf(page.selectedItem) : null;
|
|
3068
|
+
const selectedItem = !page.selectedItem ? null : selectedKey ? displayData.find((row) => keyOf(row) === selectedKey) ?? page.selectedItem : page.selectedItem;
|
|
2777
3069
|
const [selectedIds, setSelectedIds] = useState(/* @__PURE__ */ new Set());
|
|
2778
3070
|
const handleToggleSelect = useCallback((id) => {
|
|
2779
3071
|
setSelectedIds((prev) => {
|
|
@@ -2808,6 +3100,8 @@ function ResourcePage(props) {
|
|
|
2808
3100
|
}, [useBuiltInCreate, onCreate]);
|
|
2809
3101
|
const handleEdit = useCallback(
|
|
2810
3102
|
(item) => {
|
|
3103
|
+
if (canEdit && !canEdit(item)) return;
|
|
3104
|
+
if (canEditItem && !canEditItem(item)) return;
|
|
2811
3105
|
if (useBuiltInEdit) {
|
|
2812
3106
|
setFormAction("edit");
|
|
2813
3107
|
setFormItem(item);
|
|
@@ -2816,8 +3110,57 @@ function ResourcePage(props) {
|
|
|
2816
3110
|
onEdit(item);
|
|
2817
3111
|
}
|
|
2818
3112
|
},
|
|
2819
|
-
[useBuiltInEdit, onEdit]
|
|
3113
|
+
[useBuiltInEdit, onEdit, canEdit, canEditItem]
|
|
3114
|
+
);
|
|
3115
|
+
const closeDetail = useCallback(() => {
|
|
3116
|
+
page.setDetailOpen(false);
|
|
3117
|
+
page.setSelectedItem(null);
|
|
3118
|
+
}, [page]);
|
|
3119
|
+
const useBuiltInDelete = !!allowDelete && !onDelete && isClientMode;
|
|
3120
|
+
const deleteConfirmed = confirmDelete === void 0 ? useBuiltInDelete : !!confirmDelete;
|
|
3121
|
+
const deleteCopy = typeof confirmDelete === "object" ? confirmDelete : void 0;
|
|
3122
|
+
const [pendingDelete, setPendingDelete] = useState(null);
|
|
3123
|
+
const [deleteOpen, setDeleteOpen] = useState(false);
|
|
3124
|
+
const { remove: removeRecord } = resourceHook;
|
|
3125
|
+
const performDelete = useCallback(
|
|
3126
|
+
async (item) => {
|
|
3127
|
+
if (useBuiltInDelete) {
|
|
3128
|
+
await removeRecord(
|
|
3129
|
+
getKeyProp ? keyOf(item) : item.id
|
|
3130
|
+
);
|
|
3131
|
+
} else if (onDelete) {
|
|
3132
|
+
await onDelete(item);
|
|
3133
|
+
}
|
|
3134
|
+
closeDetail();
|
|
3135
|
+
onDeleted?.(item);
|
|
3136
|
+
},
|
|
3137
|
+
[
|
|
3138
|
+
useBuiltInDelete,
|
|
3139
|
+
removeRecord,
|
|
3140
|
+
getKeyProp,
|
|
3141
|
+
keyOf,
|
|
3142
|
+
onDelete,
|
|
3143
|
+
closeDetail,
|
|
3144
|
+
onDeleted
|
|
3145
|
+
]
|
|
3146
|
+
);
|
|
3147
|
+
const deleteHandler = !(useBuiltInDelete || onDelete) ? void 0 : deleteConfirmed ? (item) => {
|
|
3148
|
+
setPendingDelete(item);
|
|
3149
|
+
setDeleteOpen(true);
|
|
3150
|
+
} : useBuiltInDelete ? (item) => {
|
|
3151
|
+
performDelete(item).catch((err) => {
|
|
3152
|
+
console.error("ResourcePage: delete failed", err);
|
|
3153
|
+
});
|
|
3154
|
+
} : (
|
|
3155
|
+
// Unconfirmed `onDelete` keeps its original contract exactly:
|
|
3156
|
+
// called straight from the peek, nothing closed for it.
|
|
3157
|
+
(item) => {
|
|
3158
|
+
void onDelete?.(item);
|
|
3159
|
+
}
|
|
2820
3160
|
);
|
|
3161
|
+
const mayChange = (item) => canEditItem ? canEditItem(item) : true;
|
|
3162
|
+
const selectedEditable = !!selectedItem && mayChange(selectedItem) && (canEdit ? canEdit(selectedItem) : true);
|
|
3163
|
+
const selectedDeletable = !!selectedItem && mayChange(selectedItem) && (canDelete ? canDelete(selectedItem) : true);
|
|
2821
3164
|
const tableColumns = useMemo(() => {
|
|
2822
3165
|
const base = isClientMode ? page.columns.map((col) => ({
|
|
2823
3166
|
key: col.key,
|
|
@@ -2837,19 +3180,24 @@ function ResourcePage(props) {
|
|
|
2837
3180
|
page.tableColumns,
|
|
2838
3181
|
actions
|
|
2839
3182
|
]);
|
|
2840
|
-
const
|
|
3183
|
+
const pagerPage = resourceServerPaging ? resourceHook.params.pagination?.page ?? 1 : pageProp;
|
|
3184
|
+
const pagerPerPage = resourceServerPaging ? resourceHook.params.pagination?.perPage : perPageProp;
|
|
3185
|
+
const pagerTotal = resourceServerPaging ? displayTotal : totalProp;
|
|
3186
|
+
const pagerOnPageChange = resourceServerPaging ? resourceHook.setPage : onPageChange;
|
|
3187
|
+
const currentPage = pagerPage ?? 1;
|
|
2841
3188
|
const handleSearchChange = useCallback(
|
|
2842
3189
|
(value) => {
|
|
2843
3190
|
page.filterConfig.onSearchChange(value);
|
|
2844
3191
|
if (!isServerControlled) return;
|
|
2845
3192
|
onSearchChange?.(value);
|
|
2846
|
-
if (currentPage !== 1) onPageChange?.(1);
|
|
3193
|
+
if (currentPage !== 1 && !resourceServerPaging) onPageChange?.(1);
|
|
2847
3194
|
},
|
|
2848
3195
|
[
|
|
2849
3196
|
page.filterConfig,
|
|
2850
3197
|
isServerControlled,
|
|
2851
3198
|
onSearchChange,
|
|
2852
3199
|
onPageChange,
|
|
3200
|
+
resourceServerPaging,
|
|
2853
3201
|
currentPage
|
|
2854
3202
|
]
|
|
2855
3203
|
);
|
|
@@ -2861,24 +3209,43 @@ function ResourcePage(props) {
|
|
|
2861
3209
|
onSortChange?.(
|
|
2862
3210
|
field && (order === "asc" || order === "desc") ? { field, order } : null
|
|
2863
3211
|
);
|
|
2864
|
-
onPageChange?.(1);
|
|
3212
|
+
if (!resourceServerPaging) onPageChange?.(1);
|
|
2865
3213
|
},
|
|
2866
|
-
[
|
|
3214
|
+
[
|
|
3215
|
+
page.filterConfig,
|
|
3216
|
+
isServerControlled,
|
|
3217
|
+
onSortChange,
|
|
3218
|
+
onPageChange,
|
|
3219
|
+
resourceServerPaging
|
|
3220
|
+
]
|
|
2867
3221
|
);
|
|
2868
3222
|
const handleClearFilters = useCallback(() => {
|
|
2869
3223
|
page.clearFilters();
|
|
2870
3224
|
if (!isServerControlled) return;
|
|
2871
3225
|
onSearchChange?.("");
|
|
2872
3226
|
onSortChange?.(null);
|
|
2873
|
-
onPageChange?.(1);
|
|
2874
|
-
}, [
|
|
3227
|
+
if (!resourceServerPaging) onPageChange?.(1);
|
|
3228
|
+
}, [
|
|
3229
|
+
page,
|
|
3230
|
+
isServerControlled,
|
|
3231
|
+
onSearchChange,
|
|
3232
|
+
onSortChange,
|
|
3233
|
+
onPageChange,
|
|
3234
|
+
resourceServerPaging
|
|
3235
|
+
]);
|
|
2875
3236
|
const handleStatusChange = useCallback(
|
|
2876
3237
|
(values) => {
|
|
2877
3238
|
page.filterConfig.statusChips?.onChange(values);
|
|
2878
3239
|
if (!isServerControlled) return;
|
|
2879
|
-
if (currentPage !== 1) onPageChange?.(1);
|
|
3240
|
+
if (currentPage !== 1 && !resourceServerPaging) onPageChange?.(1);
|
|
2880
3241
|
},
|
|
2881
|
-
[
|
|
3242
|
+
[
|
|
3243
|
+
page.filterConfig,
|
|
3244
|
+
isServerControlled,
|
|
3245
|
+
onPageChange,
|
|
3246
|
+
resourceServerPaging,
|
|
3247
|
+
currentPage
|
|
3248
|
+
]
|
|
2882
3249
|
);
|
|
2883
3250
|
const statusChipsConfig = useMemo(() => {
|
|
2884
3251
|
const base = page.filterConfig.statusChips;
|
|
@@ -2889,12 +3256,20 @@ function ResourcePage(props) {
|
|
|
2889
3256
|
(size) => {
|
|
2890
3257
|
updateSetting("pageSize", size);
|
|
2891
3258
|
onPerPageChange?.(size);
|
|
2892
|
-
if (
|
|
3259
|
+
if (resourceServerPaging) resourceHook.setPerPage(size);
|
|
3260
|
+
else if (isServerControlled) onPageChange?.(1);
|
|
2893
3261
|
},
|
|
2894
|
-
[
|
|
3262
|
+
[
|
|
3263
|
+
updateSetting,
|
|
3264
|
+
onPerPageChange,
|
|
3265
|
+
resourceServerPaging,
|
|
3266
|
+
resourceHook.setPerPage,
|
|
3267
|
+
isServerControlled,
|
|
3268
|
+
onPageChange
|
|
3269
|
+
]
|
|
2895
3270
|
);
|
|
2896
|
-
const missingPerPage = isServerControlled && !!
|
|
2897
|
-
const missingTotal = isServerControlled && !!
|
|
3271
|
+
const missingPerPage = isServerControlled && !!pagerOnPageChange && pagerPerPage === void 0;
|
|
3272
|
+
const missingTotal = isServerControlled && !!pagerOnPageChange && pagerTotal === void 0;
|
|
2898
3273
|
const pagerMisconfigured = missingPerPage || missingTotal;
|
|
2899
3274
|
const missingPagerProps = [
|
|
2900
3275
|
...missingPerPage ? ["perPage"] : [],
|
|
@@ -2909,18 +3284,23 @@ function ResourcePage(props) {
|
|
|
2909
3284
|
`ResourcePage: ${missingPagerProps.map((name) => `\`${name}\``).join(" and ")} ${missingPagerProps.length > 1 ? "are" : "is"} required when \`onPageChange\` is supplied.`
|
|
2910
3285
|
);
|
|
2911
3286
|
}, [pagerMisconfigured]);
|
|
2912
|
-
const pagerPageSize =
|
|
3287
|
+
const pagerPageSize = pagerPerPage ?? effectivePageSize;
|
|
2913
3288
|
const totalPages = Math.max(
|
|
2914
3289
|
1,
|
|
2915
3290
|
Math.ceil(displayTotal / Math.max(1, pagerPageSize))
|
|
2916
3291
|
);
|
|
2917
3292
|
const rangeFrom = page.data.length === 0 ? 0 : (currentPage - 1) * pagerPageSize + 1;
|
|
2918
3293
|
const rangeTo = rangeFrom === 0 ? 0 : rangeFrom + page.data.length - 1;
|
|
2919
|
-
const
|
|
2920
|
-
|
|
2921
|
-
)
|
|
2922
|
-
|
|
2923
|
-
|
|
3294
|
+
const { setPage: setResourcePage } = resourceHook;
|
|
3295
|
+
const settledPage = !isLoading && !hasError && !resourceHook.list.isPlaceholderData;
|
|
3296
|
+
useEffect(() => {
|
|
3297
|
+
if (!resourceServerPaging || !settledPage) return;
|
|
3298
|
+
if (currentPage > totalPages) setResourcePage(totalPages);
|
|
3299
|
+
}, [resourceServerPaging, settledPage, currentPage, totalPages, setResourcePage]);
|
|
3300
|
+
const titleOf = (item) => getTitle ? getTitle(item) : String(item[page.titleField] ?? "");
|
|
3301
|
+
const detailTitle = selectedItem ? titleOf(selectedItem) : "";
|
|
3302
|
+
const detailSubtitle = !selectedItem ? void 0 : getSubtitle ? getSubtitle(selectedItem) : page.subtitleField ? String(
|
|
3303
|
+
selectedItem[page.subtitleField] ?? ""
|
|
2924
3304
|
) : void 0;
|
|
2925
3305
|
const handleRowClick = (item) => {
|
|
2926
3306
|
if (detail === "route") {
|
|
@@ -2933,7 +3313,7 @@ function ResourcePage(props) {
|
|
|
2933
3313
|
page.handleRowClick(item);
|
|
2934
3314
|
};
|
|
2935
3315
|
const hasBulkSelect = !!bulkActions && bulkActions.length > 0;
|
|
2936
|
-
const overlayOpen = page.detailOpen || formOpen;
|
|
3316
|
+
const overlayOpen = page.detailOpen || formOpen || deleteOpen;
|
|
2937
3317
|
const keyboardNav = useKeyboardNavigation({
|
|
2938
3318
|
itemCount: page.data.length,
|
|
2939
3319
|
enabled: keyboardNavigation && page.viewMode !== "table",
|
|
@@ -3072,6 +3452,10 @@ function ResourcePage(props) {
|
|
|
3072
3452
|
hasActiveFilters: page.hasActiveFilters,
|
|
3073
3453
|
onClearFilters: handleClearFilters,
|
|
3074
3454
|
emptyAction: emptyState,
|
|
3455
|
+
emptyIcon,
|
|
3456
|
+
emptyMessage,
|
|
3457
|
+
emptyDescription,
|
|
3458
|
+
...headerMode === "toolbar" ? { header: "toolbar" } : {},
|
|
3075
3459
|
filters: renderSlot(
|
|
3076
3460
|
DataFilters,
|
|
3077
3461
|
slots?.filters,
|
|
@@ -3090,7 +3474,7 @@ function ResourcePage(props) {
|
|
|
3090
3474
|
onDeleteView,
|
|
3091
3475
|
canSaveViews,
|
|
3092
3476
|
onClearAll: page.hasActiveFilters ? handleClearFilters : void 0,
|
|
3093
|
-
showSearch: capabilities?.search !== false,
|
|
3477
|
+
showSearch: capabilities?.search !== false && !(resourceServerPaging && !serverSearchFields?.length),
|
|
3094
3478
|
showSort: capabilities?.sort !== false
|
|
3095
3479
|
},
|
|
3096
3480
|
slotProps?.filters
|
|
@@ -3156,12 +3540,32 @@ function ResourcePage(props) {
|
|
|
3156
3540
|
},
|
|
3157
3541
|
slotProps?.table
|
|
3158
3542
|
),
|
|
3543
|
+
...openOnCardClick && renderCardProp && detail !== "none" ? {
|
|
3544
|
+
onItemClick: handleRowClick,
|
|
3545
|
+
getItemLabel: (item) => `Open ${titleOf(item)}`.trim()
|
|
3546
|
+
} : {},
|
|
3159
3547
|
renderCard: (item, index) => renderCardProp ? renderCardProp(item, index, cardActionsFor(item)) : renderDefaultCard(item),
|
|
3160
3548
|
renderListItem: (item, index) => renderCardProp ? renderCardProp(item, index, cardActionsFor(item)) : renderDefaultListItem(item)
|
|
3161
3549
|
},
|
|
3162
3550
|
slotProps?.grid
|
|
3163
3551
|
),
|
|
3164
|
-
|
|
3552
|
+
clientTruncated && /* @__PURE__ */ jsxs(
|
|
3553
|
+
"p",
|
|
3554
|
+
{
|
|
3555
|
+
role: "status",
|
|
3556
|
+
className: "px-3 py-2 text-sm text-muted-foreground",
|
|
3557
|
+
"data-testid": "resource-page-truncated",
|
|
3558
|
+
children: [
|
|
3559
|
+
"Showing the first ",
|
|
3560
|
+
displayData.length.toLocaleString(),
|
|
3561
|
+
" of",
|
|
3562
|
+
" ",
|
|
3563
|
+
displayTotal.toLocaleString(),
|
|
3564
|
+
" records."
|
|
3565
|
+
]
|
|
3566
|
+
}
|
|
3567
|
+
),
|
|
3568
|
+
isServerControlled && pagerOnPageChange && pagerMisconfigured && /* @__PURE__ */ jsxs(
|
|
3165
3569
|
"div",
|
|
3166
3570
|
{
|
|
3167
3571
|
className: "flex items-center gap-2 rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-sm text-destructive",
|
|
@@ -3186,10 +3590,11 @@ function ResourcePage(props) {
|
|
|
3186
3590
|
]
|
|
3187
3591
|
}
|
|
3188
3592
|
),
|
|
3189
|
-
isServerControlled &&
|
|
3593
|
+
isServerControlled && pagerOnPageChange && !pagerMisconfigured && /* @__PURE__ */ jsxs(
|
|
3190
3594
|
"nav",
|
|
3191
3595
|
{
|
|
3192
3596
|
"aria-label": "Pagination",
|
|
3597
|
+
"aria-busy": resourceServerPaging && resourceHook.list.isPlaceholderData || void 0,
|
|
3193
3598
|
className: "flex flex-col sm:flex-row items-center justify-between gap-2 px-3 py-2 text-sm",
|
|
3194
3599
|
"data-testid": "resource-page-pagination",
|
|
3195
3600
|
children: [
|
|
@@ -3209,7 +3614,7 @@ function ResourcePage(props) {
|
|
|
3209
3614
|
variant: "outline",
|
|
3210
3615
|
size: "sm",
|
|
3211
3616
|
disabled: currentPage <= 1,
|
|
3212
|
-
onClick: () =>
|
|
3617
|
+
onClick: () => pagerOnPageChange(currentPage - 1),
|
|
3213
3618
|
testID: "resource-page-prev",
|
|
3214
3619
|
children: [
|
|
3215
3620
|
/* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4 mr-1", "aria-hidden": "true" }),
|
|
@@ -3236,7 +3641,7 @@ function ResourcePage(props) {
|
|
|
3236
3641
|
variant: "outline",
|
|
3237
3642
|
size: "sm",
|
|
3238
3643
|
disabled: currentPage >= totalPages,
|
|
3239
|
-
onClick: () =>
|
|
3644
|
+
onClick: () => pagerOnPageChange(currentPage + 1),
|
|
3240
3645
|
testID: "resource-page-next",
|
|
3241
3646
|
children: [
|
|
3242
3647
|
"Next",
|
|
@@ -3252,16 +3657,13 @@ function ResourcePage(props) {
|
|
|
3252
3657
|
DetailPanel,
|
|
3253
3658
|
slots?.detail,
|
|
3254
3659
|
{
|
|
3255
|
-
item:
|
|
3660
|
+
item: selectedItem,
|
|
3256
3661
|
open: page.detailOpen,
|
|
3257
|
-
onClose:
|
|
3258
|
-
page.setDetailOpen(false);
|
|
3259
|
-
page.setSelectedItem(null);
|
|
3260
|
-
},
|
|
3662
|
+
onClose: closeDetail,
|
|
3261
3663
|
title: detailTitle,
|
|
3262
3664
|
subtitle: detailSubtitle,
|
|
3263
|
-
onEdit: onEdit ?? (useBuiltInEdit ? handleEdit : void 0),
|
|
3264
|
-
onDelete,
|
|
3665
|
+
onEdit: selectedEditable ? onEdit ?? (useBuiltInEdit ? handleEdit : void 0) : void 0,
|
|
3666
|
+
onDelete: selectedDeletable ? deleteHandler : void 0,
|
|
3265
3667
|
fields: detailFields,
|
|
3266
3668
|
width: detailWidth,
|
|
3267
3669
|
layout: detail === "modal" ? "dialog" : "sheet",
|
|
@@ -3287,11 +3689,24 @@ function ResourcePage(props) {
|
|
|
3287
3689
|
listData: page.data,
|
|
3288
3690
|
renderForm: renderFormProp,
|
|
3289
3691
|
onSuccess: () => {
|
|
3290
|
-
|
|
3291
|
-
page.setSelectedItem(null);
|
|
3692
|
+
if (formAction === "create") closeDetail();
|
|
3292
3693
|
}
|
|
3293
3694
|
},
|
|
3294
3695
|
slotProps?.form
|
|
3696
|
+
),
|
|
3697
|
+
deleteConfirmed && (useBuiltInDelete || !!onDelete) && renderSlot(
|
|
3698
|
+
ConfirmDeleteDialog,
|
|
3699
|
+
slots?.deleteDialog,
|
|
3700
|
+
{
|
|
3701
|
+
open: deleteOpen,
|
|
3702
|
+
onOpenChange: setDeleteOpen,
|
|
3703
|
+
onConfirm: () => pendingDelete ? performDelete(pendingDelete) : void 0,
|
|
3704
|
+
title: typeof deleteCopy?.title === "function" ? pendingDelete ? deleteCopy.title(pendingDelete) : void 0 : deleteCopy?.title,
|
|
3705
|
+
description: typeof deleteCopy?.description === "function" ? pendingDelete ? deleteCopy.description(pendingDelete) : void 0 : deleteCopy?.description,
|
|
3706
|
+
confirmLabel: deleteCopy?.confirmLabel,
|
|
3707
|
+
cancelLabel: deleteCopy?.cancelLabel
|
|
3708
|
+
},
|
|
3709
|
+
slotProps?.deleteDialog
|
|
3295
3710
|
)
|
|
3296
3711
|
]
|
|
3297
3712
|
}
|
|
@@ -3371,6 +3786,6 @@ function ResourceDetailPage({
|
|
|
3371
3786
|
var AppShell2 = AppShell;
|
|
3372
3787
|
var PageHeader2 = PageHeader;
|
|
3373
3788
|
|
|
3374
|
-
export { AppShell2 as AppShell, BadgeCell, CurrencyCell, DataBulkBar, DataFilters, DataGrid, DataList, DataPageShell, DataTable, DateCell, DetailPanel, FieldArrayRenderer, FileUploadField, FilterCombobox, KanbanBoard, LinkCell, NumberCell, PageHeader2 as PageHeader, RelationField, ResourceDetailPage, ResourceForm, ResourcePage, StatCard, ThumbnailCell, WizardForm };
|
|
3789
|
+
export { AppShell2 as AppShell, BadgeCell, ConfirmDeleteDialog, CurrencyCell, DataBulkBar, DataFilters, DataGrid, DataList, DataPageShell, DataTable, DateCell, DetailPanel, FieldArrayRenderer, FileUploadField, FilterCombobox, KanbanBoard, LinkCell, NumberCell, PageHeader2 as PageHeader, RelationField, ResourceDetailPage, ResourceForm, ResourcePage, StatCard, ThumbnailCell, WizardForm };
|
|
3375
3790
|
//# sourceMappingURL=index.js.map
|
|
3376
3791
|
//# sourceMappingURL=index.js.map
|