@marktiderman/genesis-ui 2.2.1 → 2.3.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.
@@ -1,22 +1,23 @@
1
1
  import * as React from 'react';
2
- import { ReactNode, RefObject } from 'react';
3
- import { c as PageHeaderOptions, A as AppShell$1, P as PageHeader$1 } from '../../AppShell-sQT0UbXg.js';
4
- export { a as AppShellOptions, b as AppShellProps, N as NavItem, d as PageHeaderProps } from '../../AppShell-sQT0UbXg.js';
2
+ import { ReactNode, ComponentType, ComponentProps, RefObject } from 'react';
3
+ import { e as PageHeaderAction, c as PageHeaderOptions, P as PageHeader$1, A as AppShell$1 } from '../../AppShell-D_E_Hk4g.js';
4
+ export { a as AppShellOptions, b as AppShellProps, N as NavItem, d as PageHeaderProps } from '../../AppShell-D_E_Hk4g.js';
5
5
  import { V as ViewMode } from '../../view-toggle-CahQ87PW.js';
6
6
  export { a as ViewToggle } from '../../view-toggle-CahQ87PW.js';
7
7
  import { LucideIcon } from 'lucide-react';
8
8
  import { Density } from '../../hooks/use-view-settings.js';
9
- import { R as ResourceActions, a as ResourceColumnDef } from '../../use-resource-page-DTQS011Q.js';
10
- export { D as DataTable, c as DataTableColumn, d as DataTableProps, L as LinkCell, T as ThumbnailCell } from '../../use-resource-page-DTQS011Q.js';
9
+ import { R as ResourceActions, a as ResourceColumnDef, D as DataTable } from '../../use-resource-page-DTQS011Q.js';
10
+ export { c as DataTableColumn, d as DataTableProps, L as LinkCell, T as ThumbnailCell } from '../../use-resource-page-DTQS011Q.js';
11
11
  export { EmptyState } from '../patterns/empty-state.js';
12
12
  export { V as ViewSettings } from '../../view-settings-kHKCHMjj.js';
13
13
  import { L as LinkComponent, N as NavigateFn } from '../../navigation-CpOEyUSb.js';
14
14
  export { D as DefaultLink, G as GenesisLinkProps, d as defaultNavigate, i as isPathActive } from '../../navigation-CpOEyUSb.js';
15
15
  import { UseFormReturn, Control, FieldErrors } from 'react-hook-form';
16
- import { WizardStep, UseResourceOptions } from '@marktiderman/genesis-core/hooks';
17
- import { FilterParam, SortParam, BaseRecord, Identifier } from '@marktiderman/genesis-core/provider';
18
16
  import { ResourceFormFieldDef } from '@marktiderman/genesis-core';
19
17
  export { FieldType, ResourceFormFieldDef, SelectOption } from '@marktiderman/genesis-core';
18
+ import { WizardStep, UseResourceOptions } from '@marktiderman/genesis-core/hooks';
19
+ import { b as Slots, a as SlotPropsFor } from '../../slots-tR0wf89f.js';
20
+ import { FilterParam, SortParam, BaseRecord, Identifier } from '@marktiderman/genesis-core/provider';
20
21
  export { CommandPalette, CommandPaletteItem, CommandPaletteProps, useCommandPaletteHotkey } from '../ui/command-palette.js';
21
22
  export { UseSavedViewsReturn, useSavedViews } from '../../hooks/use-saved-views.js';
22
23
 
@@ -29,6 +30,8 @@ interface DataPageShellProps {
29
30
  /** Subtitle text */
30
31
  subtitle?: string;
31
32
  createAction?: ReactNode;
33
+ /** Extra actions, rendered in the header's one overflow menu. */
34
+ secondaryActions?: PageHeaderAction[];
32
35
  isLoading?: boolean;
33
36
  isEmpty?: boolean;
34
37
  emptyIcon?: LucideIcon;
@@ -72,8 +75,19 @@ interface DataPageShellProps {
72
75
  /** Persist user header-option overrides (localStorage) */
73
76
  headerOptionsStorageKey?: string;
74
77
  children: ReactNode;
78
+ /** Merged onto the root, not replacing its layout. */
79
+ className?: string;
80
+ /**
81
+ * Replace the header outright. It receives exactly the props
82
+ * `DataPageShell` would have given `PageHeader`, so the common case is a
83
+ * wrapper that renders `PageHeader` with one thing added — an overflow
84
+ * menu, a status strip, a second row — rather than a reimplementation.
85
+ *
86
+ * `null` ejects it: shell layout, states and filter row, no header.
87
+ */
88
+ header?: ComponentType<ComponentProps<typeof PageHeader$1>> | null;
75
89
  }
76
- declare function DataPageShell({ title, icon, entityName, subtitle, createAction, isLoading, isEmpty, emptyIcon, emptyMessage, emptyAction, hasError, errorMessage, onRetry, count, totalCount, viewMode, onViewModeChange, pageSize, onPageSizeChange, density, onDensityChange, filters, hasActiveFilters, onClearFilters, filtersCollapsible, defaultFiltersOpen, filtersOpen, onFiltersOpenChange, filtersLabel, filtersHint, headerOptions, headerOptionsStorageKey, children, }: DataPageShellProps): React.JSX.Element;
90
+ declare function DataPageShell({ title, icon, entityName, subtitle, createAction, secondaryActions, isLoading, isEmpty, emptyIcon, emptyMessage, emptyAction, hasError, errorMessage, onRetry, count, totalCount, viewMode, onViewModeChange, pageSize, onPageSizeChange, density, onDensityChange, filters, hasActiveFilters, onClearFilters, filtersCollapsible, defaultFiltersOpen, filtersOpen, onFiltersOpenChange, filtersLabel, filtersHint, headerOptions, headerOptionsStorageKey, children, className, header, }: DataPageShellProps): React.JSX.Element;
77
91
 
78
92
  interface FilterOption {
79
93
  value: string;
@@ -195,6 +209,15 @@ interface BulkAction {
195
209
  * Optional and defaulted off — omitting it behaves exactly as before.
196
210
  */
197
211
  disabled?: boolean;
212
+ /**
213
+ * `data-testid` on the rendered button.
214
+ *
215
+ * Without it, a consumer migrating a hand-rolled bulk bar to `DataBulkBar`
216
+ * loses every selector its e2e suite uses to drive one — which is a real
217
+ * reason not to migrate, and the kind of small missing affordance that
218
+ * keeps a parallel implementation alive.
219
+ */
220
+ testId?: string;
198
221
  }
199
222
  interface DataBulkBarProps {
200
223
  selected: Set<string>;
@@ -202,8 +225,10 @@ interface DataBulkBarProps {
202
225
  onToggleAll: () => void;
203
226
  onClearSelection: () => void;
204
227
  actions: BulkAction[];
228
+ /** Merged onto the bar, not replacing its positioning. */
229
+ className?: string;
205
230
  }
206
- declare function DataBulkBar({ selected, totalCount, onToggleAll, onClearSelection, actions, }: DataBulkBarProps): React.JSX.Element | null;
231
+ declare function DataBulkBar({ selected, totalCount, onToggleAll, onClearSelection, actions, className, }: DataBulkBarProps): React.JSX.Element | null;
207
232
 
208
233
  /**
209
234
  * DateCell -- shows relative date by default, formatted date otherwise.
@@ -329,6 +354,30 @@ interface DetailPanelProps<T> {
329
354
  }
330
355
  declare function DetailPanel<T extends Record<string, unknown>>({ item, open, onClose, title, subtitle, onEdit, onDelete, children, fields, width, layout, }: DetailPanelProps<T>): React.JSX.Element | null;
331
356
 
357
+ interface ResourceFormProps {
358
+ resource: string;
359
+ action: "create" | "edit";
360
+ item?: Record<string, unknown>;
361
+ open: boolean;
362
+ onOpenChange: (open: boolean) => void;
363
+ title?: string;
364
+ fields?: ResourceFormFieldDef[];
365
+ /** Full dataset used to auto-detect select fields from unique values. */
366
+ listData?: Record<string, unknown>[];
367
+ renderForm?: (form: UseFormReturn<Record<string, unknown>>, fields: ResourceFormFieldDef[]) => ReactNode;
368
+ onSuccess?: (data: Record<string, unknown>) => void;
369
+ onError?: (error: Error) => void;
370
+ /** Layout mode for the form container. Default "dialog". */
371
+ layout?: "dialog" | "sheet" | "page";
372
+ /** Form presentation mode. Default "standard". */
373
+ mode?: "standard" | "wizard";
374
+ /** Wizard step definitions. Required when mode is "wizard". */
375
+ steps?: WizardStep[];
376
+ /** Number of columns for the form layout. Default 1. */
377
+ columns?: 1 | 2;
378
+ }
379
+ declare function ResourceForm(props: ResourceFormProps): React.JSX.Element | null;
380
+
332
381
  /**
333
382
  * Row-bound actions handed to `renderCard`, on top of the resource-wide
334
383
  * `ResourceActions`. `open` is per-row, which is why it lives here rather
@@ -377,6 +426,32 @@ interface ResourceCardActions<T> extends ResourceActions<T> {
377
426
  *
378
427
  * @stability Beta
379
428
  */
429
+ /**
430
+ * Every interior part `ResourcePage` composes, with the props it composes it
431
+ * with. Each key is a slot: replace it via `slots`, feed it more props via
432
+ * `slotProps`, or eject it with `null`.
433
+ *
434
+ * The map is the contract. A part that is not named here cannot be reached,
435
+ * so adding a part to the render tree means adding it here in the same
436
+ * change — otherwise the next surface that needs it forks the whole page.
437
+ */
438
+ type ResourcePageSlotMap<T extends Record<string, unknown> = Record<string, unknown>> = {
439
+ /** The page shell: header, count pill, loading / empty / error states. */
440
+ shell: ComponentProps<typeof DataPageShell>;
441
+ /** The filter row — search, sort, facets, saved views. */
442
+ filters: ComponentProps<typeof DataFilters>;
443
+ /** The selection bar. Ejecting it leaves selection working, just unshown. */
444
+ bulkBar: ComponentProps<typeof DataBulkBar>;
445
+ /** The card/table switch. */
446
+ grid: ComponentProps<typeof DataGrid<T>>;
447
+ /** The table inside the grid. `renderTable` stays the terser way to swap. */
448
+ table: ComponentProps<typeof DataTable<T>>;
449
+ /** The detail peek. */
450
+ detail: ComponentProps<typeof DetailPanel<T>>;
451
+ /** The create / edit form. */
452
+ form: ComponentProps<typeof ResourceForm>;
453
+ };
454
+ type ResourcePageSlots<T extends Record<string, unknown> = Record<string, unknown>> = Slots<ResourcePageSlotMap<T>>;
380
455
  interface ResourcePageProps<T extends Record<string, unknown> = Record<string, unknown>> {
381
456
  /** Client-side mode: resource name passed to useResource / DataProvider. */
382
457
  resource?: string;
@@ -549,6 +624,11 @@ interface ResourcePageProps<T extends Record<string, unknown> = Record<string, u
549
624
  onEdit?: (item: T) => void;
550
625
  onDelete?: (item: T) => void;
551
626
  createLabel?: string;
627
+ /**
628
+ * Extra header actions beyond the create button, in one overflow menu.
629
+ * See {@link PageHeaderAction} — the budget is one primary, the rest here.
630
+ */
631
+ secondaryActions?: PageHeaderAction[];
552
632
  /** Enable built-in create form (ignored if onCreate callback is provided). */
553
633
  allowCreate?: boolean;
554
634
  /** Enable built-in edit form (ignored if onEdit callback is provided). */
@@ -575,6 +655,8 @@ interface ResourcePageProps<T extends Record<string, unknown> = Record<string, u
575
655
  * but leaves the button looking live, which misstates what it will do.
576
656
  */
577
657
  disabled?: boolean;
658
+ /** `data-testid` on the rendered bulk button, so e2e can drive it. */
659
+ testId?: string;
578
660
  }>;
579
661
  /**
580
662
  * Override the default card renderer used for grid view (and, absent a
@@ -649,6 +731,20 @@ interface ResourcePageProps<T extends Record<string, unknown> = Record<string, u
649
731
  * Defaults to `resource` or a slug of `title`.
650
732
  */
651
733
  viewSettingsKey?: string;
734
+ /**
735
+ * The interior parts `ResourcePage` composes, by name. Replace one with your
736
+ * own component, or pass `null` to eject it entirely — see
737
+ * {@link ResourcePageSlots}.
738
+ *
739
+ * This is the reason a surface that needs one part to differ does not have
740
+ * to drop to the primitives and re-derive the pagination, selection and
741
+ * detail wiring that were already correct.
742
+ */
743
+ slots?: ResourcePageSlots<T>;
744
+ /** Extra props merged into a part you are KEEPING. Wins over ours. */
745
+ slotProps?: SlotPropsFor<ResourcePageSlotMap<T>>;
746
+ /** Merged onto the page shell's root. */
747
+ className?: string;
652
748
  }
653
749
  declare function ResourcePage<T extends Record<string, unknown> = Record<string, unknown>>(props: ResourcePageProps<T>): React.JSX.Element;
654
750
 
@@ -731,30 +827,6 @@ interface ResourceDetailPageProps<T extends BaseRecord = BaseRecord> {
731
827
  */
732
828
  declare function ResourceDetailPage<T extends BaseRecord = BaseRecord>({ resource, id, titleField, subtitleField, select, breadcrumb, actions, sidebar, children, className, testID, }: ResourceDetailPageProps<T>): React.JSX.Element;
733
829
 
734
- interface ResourceFormProps {
735
- resource: string;
736
- action: "create" | "edit";
737
- item?: Record<string, unknown>;
738
- open: boolean;
739
- onOpenChange: (open: boolean) => void;
740
- title?: string;
741
- fields?: ResourceFormFieldDef[];
742
- /** Full dataset used to auto-detect select fields from unique values. */
743
- listData?: Record<string, unknown>[];
744
- renderForm?: (form: UseFormReturn<Record<string, unknown>>, fields: ResourceFormFieldDef[]) => ReactNode;
745
- onSuccess?: (data: Record<string, unknown>) => void;
746
- onError?: (error: Error) => void;
747
- /** Layout mode for the form container. Default "dialog". */
748
- layout?: "dialog" | "sheet" | "page";
749
- /** Form presentation mode. Default "standard". */
750
- mode?: "standard" | "wizard";
751
- /** Wizard step definitions. Required when mode is "wizard". */
752
- steps?: WizardStep[];
753
- /** Number of columns for the form layout. Default 1. */
754
- columns?: 1 | 2;
755
- }
756
- declare function ResourceForm(props: ResourceFormProps): React.JSX.Element | null;
757
-
758
830
  interface RelationFieldProps {
759
831
  field: ResourceFormFieldDef;
760
832
  value: string;
@@ -805,4 +877,4 @@ declare const AppShell: typeof AppShell$1;
805
877
  */
806
878
  declare const PageHeader: typeof PageHeader$1;
807
879
 
808
- export { AppShell, BadgeCell, type BulkAction, type ColumnStatus, CurrencyCell, DataBulkBar, type DataFilterConfig, DataFilters, DataGrid, DataList, type DataListProps, DataPageShell, type DataPageShellProps, DateCell, Density, DetailPanel, type DetailPanelProps, FieldArrayRenderer, FileUploadField, FilterCombobox, type FilterOption, KanbanBoard, type KanbanBoardProps, type KanbanColumn, LinkComponent, NavigateFn, NumberCell, PageHeader, PageHeaderOptions, RelationField, ResourceActions, type ResourceCardActions, ResourceColumnDef, ResourceDetailPage, type ResourceDetailPageProps, ResourceForm, type ResourceFormProps, ResourcePage, type ResourcePageProps, type SavedViewItem, StatCard, type StatCardColor, type StatCardProps, ViewMode, WizardForm };
880
+ export { AppShell, BadgeCell, type BulkAction, type ColumnStatus, CurrencyCell, DataBulkBar, type DataFilterConfig, DataFilters, DataGrid, DataList, type DataListProps, DataPageShell, type DataPageShellProps, DataTable, DateCell, Density, DetailPanel, type DetailPanelProps, FieldArrayRenderer, FileUploadField, FilterCombobox, type FilterOption, KanbanBoard, type KanbanBoardProps, type KanbanColumn, LinkComponent, NavigateFn, NumberCell, PageHeader, PageHeaderOptions, RelationField, ResourceActions, type ResourceCardActions, ResourceColumnDef, ResourceDetailPage, type ResourceDetailPageProps, ResourceForm, type ResourceFormProps, ResourcePage, type ResourcePageProps, type SavedViewItem, StatCard, type StatCardColor, type StatCardProps, ViewMode, WizardForm };
@@ -1,16 +1,17 @@
1
1
  "use client";
2
- import { AppShell, PageHeader } from '../../chunk-FEJAZFP3.js';
2
+ import { AppShell, PageHeader } from '../../chunk-UBCYXOXT.js';
3
3
  export { ViewSettings, ViewToggle } from '../../chunk-46TZGMFW.js';
4
+ import { renderSlot } from '../../chunk-7S7LKQBD.js';
4
5
  import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from '../../chunk-N2ZFNOOT.js';
5
6
  import { Textarea } from '../../chunk-YETMISCE.js';
6
7
  import { Separator } from '../../chunk-SXSCHWZU.js';
7
8
  import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetDescription } from '../../chunk-XCGZPFT7.js';
8
9
  import { Skeleton } from '../../chunk-QFM4Z2SS.js';
9
10
  import { Progress } from '../../chunk-W5Y3LG5K.js';
10
- import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuCheckboxItem, DropdownMenuSeparator, DropdownMenuItem } from '../../chunk-QJ6P2DW6.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
15
  import '../../chunk-B2GIBUQD.js';
15
16
  import { Badge } from '../../chunk-NPNADZHM.js';
16
17
  import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '../../chunk-FK42ZGGV.js';
@@ -53,6 +54,7 @@ function DataPageShell({
53
54
  entityName,
54
55
  subtitle,
55
56
  createAction,
57
+ secondaryActions,
56
58
  isLoading = false,
57
59
  isEmpty = false,
58
60
  emptyIcon,
@@ -80,7 +82,9 @@ function DataPageShell({
80
82
  filtersHint,
81
83
  headerOptions,
82
84
  headerOptionsStorageKey,
83
- children
85
+ children,
86
+ className,
87
+ header
84
88
  }) {
85
89
  const entity = entityName || title.toLowerCase();
86
90
  const collapseFilters = filtersCollapsible && filters != null;
@@ -105,11 +109,13 @@ function DataPageShell({
105
109
  density,
106
110
  onDensityChange,
107
111
  createAction,
112
+ secondaryActions,
108
113
  optionsStorageKey: headerOptionsStorageKey
109
114
  };
110
- return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
115
+ const Header = header === null ? (() => null) : header ?? PageHeader;
116
+ return /* @__PURE__ */ jsxs("div", { className: cn("space-y-6", className), children: [
111
117
  collapseFilters ? /* @__PURE__ */ jsx(
112
- PageHeader,
118
+ Header,
113
119
  {
114
120
  ...sharedHeaderProps,
115
121
  details: filters,
@@ -120,7 +126,7 @@ function DataPageShell({
120
126
  options: mergedOptions
121
127
  }
122
128
  ) : /* @__PURE__ */ jsxs(Fragment, { children: [
123
- /* @__PURE__ */ jsx(PageHeader, { ...sharedHeaderProps, options: headerOptions }),
129
+ /* @__PURE__ */ jsx(Header, { ...sharedHeaderProps, options: headerOptions }),
124
130
  filters
125
131
  ] }),
126
132
  isLoading ? /* @__PURE__ */ jsx(
@@ -1123,49 +1129,60 @@ function DataBulkBar({
1123
1129
  totalCount,
1124
1130
  onToggleAll,
1125
1131
  onClearSelection,
1126
- actions
1132
+ actions,
1133
+ className
1127
1134
  }) {
1128
1135
  if (selected.size === 0) return null;
1129
- return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 p-3 rounded-lg border bg-muted/50 animate-fade-in", children: [
1130
- /* @__PURE__ */ jsx(
1131
- Checkbox,
1132
- {
1133
- checked: selected.size === totalCount,
1134
- onCheckedChange: onToggleAll
1135
- }
1136
- ),
1137
- /* @__PURE__ */ jsxs(Badge, { variant: "secondary", children: [
1138
- selected.size,
1139
- " selected"
1140
- ] }),
1141
- actions.map((action) => {
1142
- const ActionIcon = action.icon;
1143
- return /* @__PURE__ */ jsxs(
1144
- Button,
1145
- {
1146
- size: "sm",
1147
- variant: action.variant || "outline",
1148
- onClick: action.onClick,
1149
- disabled: action.disabled,
1150
- children: [
1151
- /* @__PURE__ */ jsx(ActionIcon, { className: "h-3.5 w-3.5 mr-1" }),
1136
+ return /* @__PURE__ */ jsxs(
1137
+ "div",
1138
+ {
1139
+ className: cn(
1140
+ "flex items-center gap-2 p-3 rounded-lg border bg-muted/50 animate-fade-in",
1141
+ className
1142
+ ),
1143
+ children: [
1144
+ /* @__PURE__ */ jsx(
1145
+ Checkbox,
1146
+ {
1147
+ checked: selected.size === totalCount,
1148
+ onCheckedChange: onToggleAll
1149
+ }
1150
+ ),
1151
+ /* @__PURE__ */ jsxs(Badge, { variant: "secondary", children: [
1152
+ selected.size,
1153
+ " selected"
1154
+ ] }),
1155
+ actions.map((action) => {
1156
+ const ActionIcon = action.icon;
1157
+ return /* @__PURE__ */ jsxs(
1158
+ Button,
1159
+ {
1160
+ size: "sm",
1161
+ variant: action.variant || "outline",
1162
+ onClick: action.onClick,
1163
+ disabled: action.disabled,
1164
+ "data-testid": action.testId,
1165
+ children: [
1166
+ /* @__PURE__ */ jsx(ActionIcon, { className: "h-3.5 w-3.5 mr-1" }),
1167
+ action.label
1168
+ ]
1169
+ },
1152
1170
  action.label
1153
- ]
1154
- },
1155
- action.label
1156
- );
1157
- }),
1158
- /* @__PURE__ */ jsx(
1159
- Button,
1160
- {
1161
- size: "sm",
1162
- variant: "ghost",
1163
- onClick: onClearSelection,
1164
- className: "ml-auto text-xs text-muted-foreground",
1165
- children: "Clear"
1166
- }
1167
- )
1168
- ] });
1171
+ );
1172
+ }),
1173
+ /* @__PURE__ */ jsx(
1174
+ Button,
1175
+ {
1176
+ size: "sm",
1177
+ variant: "ghost",
1178
+ onClick: onClearSelection,
1179
+ className: "ml-auto text-xs text-muted-foreground",
1180
+ children: "Clear"
1181
+ }
1182
+ )
1183
+ ]
1184
+ }
1185
+ );
1169
1186
  }
1170
1187
  function DateCell(options) {
1171
1188
  return (_item, value) => {
@@ -2452,6 +2469,7 @@ function ResourcePage(props) {
2452
2469
  onEdit,
2453
2470
  onDelete,
2454
2471
  createLabel = "New",
2472
+ secondaryActions,
2455
2473
  allowCreate,
2456
2474
  allowEdit,
2457
2475
  formFields,
@@ -2469,7 +2487,10 @@ function ResourcePage(props) {
2469
2487
  resourceOptions,
2470
2488
  defaultView = "table",
2471
2489
  onNavigate = defaultNavigate,
2472
- viewSettingsKey: viewSettingsKeyProp
2490
+ viewSettingsKey: viewSettingsKeyProp,
2491
+ slots,
2492
+ slotProps,
2493
+ className
2473
2494
  } = props;
2474
2495
  const isClientMode = !!resourceName;
2475
2496
  const isServerControlled = pageProp !== void 0 || perPageProp !== void 0 || onPageChange !== void 0 || onPerPageChange !== void 0 || onSortChange !== void 0 || onSearchChange !== void 0;
@@ -2803,8 +2824,10 @@ function ResourcePage(props) {
2803
2824
  }
2804
2825
  );
2805
2826
  };
2827
+ const Shell = slots?.shell ?? DataPageShell;
2828
+ const shellSlotProps = slotProps?.shell;
2806
2829
  return /* @__PURE__ */ jsxs(
2807
- DataPageShell,
2830
+ Shell,
2808
2831
  {
2809
2832
  title,
2810
2833
  subtitle,
@@ -2823,11 +2846,13 @@ function ResourcePage(props) {
2823
2846
  density: settings.density,
2824
2847
  onDensityChange: (d) => updateSetting("density", d),
2825
2848
  createAction,
2849
+ secondaryActions,
2826
2850
  hasActiveFilters: page.hasActiveFilters,
2827
2851
  onClearFilters: handleClearFilters,
2828
2852
  emptyAction: emptyState,
2829
- filters: /* @__PURE__ */ jsx(
2853
+ filters: renderSlot(
2830
2854
  DataFilters,
2855
+ slots?.filters,
2831
2856
  {
2832
2857
  search: page.filterConfig.search,
2833
2858
  onSearchChange: handleSearchChange,
@@ -2843,11 +2868,15 @@ function ResourcePage(props) {
2843
2868
  onDeleteView,
2844
2869
  canSaveViews,
2845
2870
  onClearAll: page.hasActiveFilters ? handleClearFilters : void 0
2846
- }
2871
+ },
2872
+ slotProps?.filters
2847
2873
  ),
2874
+ ...shellSlotProps,
2875
+ className: cn(className, shellSlotProps?.className),
2848
2876
  children: [
2849
- bulkActions && bulkActions.length > 0 && /* @__PURE__ */ jsx(
2877
+ bulkActions && bulkActions.length > 0 && renderSlot(
2850
2878
  DataBulkBar,
2879
+ slots?.bulkBar,
2851
2880
  {
2852
2881
  selected: selectedIds,
2853
2882
  totalCount: page.data.length,
@@ -2858,14 +2887,17 @@ function ResourcePage(props) {
2858
2887
  icon: a.icon ?? Trash2,
2859
2888
  variant: a.variant,
2860
2889
  disabled: a.disabled,
2890
+ testId: a.testId,
2861
2891
  onClick: () => {
2862
2892
  void a.onAction(Array.from(selectedIds));
2863
2893
  }
2864
2894
  }))
2865
- }
2895
+ },
2896
+ slotProps?.bulkBar
2866
2897
  ),
2867
- /* @__PURE__ */ jsx(
2898
+ renderSlot(
2868
2899
  DataGrid,
2900
+ slots?.grid,
2869
2901
  {
2870
2902
  items: page.data,
2871
2903
  viewMode: page.viewMode,
@@ -2873,8 +2905,9 @@ function ResourcePage(props) {
2873
2905
  getKey: (item) => String(item.id ?? ""),
2874
2906
  focusedIndex: keyboardNav.focusedIndex,
2875
2907
  keyboardContainerRef: keyboardNav.containerRef,
2876
- renderTable: (items) => renderTableProp ? renderTableProp(items) : /* @__PURE__ */ jsx(
2908
+ renderTable: (items) => renderTableProp ? renderTableProp(items) : renderSlot(
2877
2909
  DataTable,
2910
+ slots?.table,
2878
2911
  {
2879
2912
  items,
2880
2913
  columns: tableColumns,
@@ -2893,12 +2926,16 @@ function ResourcePage(props) {
2893
2926
  resizeKey,
2894
2927
  stickyHeader,
2895
2928
  stickyFirstColumn,
2929
+ // The server already cut the page window; DataTable's own
2930
+ // pagination would slice the slice.
2896
2931
  pagination: isServerControlled ? false : void 0
2897
- }
2932
+ },
2933
+ slotProps?.table
2898
2934
  ),
2899
2935
  renderCard: (item, index) => renderCardProp ? renderCardProp(item, index, cardActionsFor(item)) : renderDefaultCard(item),
2900
2936
  renderListItem: (item, index) => renderCardProp ? renderCardProp(item, index, cardActionsFor(item)) : renderDefaultListItem(item)
2901
- }
2937
+ },
2938
+ slotProps?.grid
2902
2939
  ),
2903
2940
  isServerControlled && onPageChange && pagerMisconfigured && /* @__PURE__ */ jsxs(
2904
2941
  "div",
@@ -2987,8 +3024,9 @@ function ResourcePage(props) {
2987
3024
  ]
2988
3025
  }
2989
3026
  ),
2990
- (detail === "panel" || detail === "modal") && /* @__PURE__ */ jsx(
3027
+ (detail === "panel" || detail === "modal") && renderSlot(
2991
3028
  DetailPanel,
3029
+ slots?.detail,
2992
3030
  {
2993
3031
  item: page.selectedItem,
2994
3032
  open: page.detailOpen,
@@ -3004,10 +3042,12 @@ function ResourcePage(props) {
3004
3042
  width: detailWidth,
3005
3043
  layout: detail === "modal" ? "dialog" : "sheet",
3006
3044
  children: renderDetailProp ? (item) => renderDetailProp(item) : void 0
3007
- }
3045
+ },
3046
+ slotProps?.detail
3008
3047
  ),
3009
- (useBuiltInCreate || useBuiltInEdit) && resourceName && /* @__PURE__ */ jsx(
3048
+ (useBuiltInCreate || useBuiltInEdit) && resourceName && renderSlot(
3010
3049
  ResourceForm,
3050
+ slots?.form,
3011
3051
  {
3012
3052
  resource: resourceName,
3013
3053
  action: formAction,
@@ -3026,7 +3066,8 @@ function ResourcePage(props) {
3026
3066
  page.setDetailOpen(false);
3027
3067
  page.setSelectedItem(null);
3028
3068
  }
3029
- }
3069
+ },
3070
+ slotProps?.form
3030
3071
  )
3031
3072
  ]
3032
3073
  }