@marktiderman/genesis-ui 2.9.0 → 2.10.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/SKILL.md +17 -0
- package/dist/{AppShell-LXQykEqe.d.ts → AppShell-BVr4V0UK.d.ts} +1 -110
- package/dist/PageHeader-V9lVkZtF.d.ts +114 -0
- package/dist/{chunk-S7NXD37O.js → chunk-25XEXYGC.js} +4 -4
- package/dist/chunk-25XEXYGC.js.map +1 -0
- package/dist/chunk-C7PQ54JI.js +763 -0
- package/dist/chunk-C7PQ54JI.js.map +1 -0
- package/dist/chunk-I4NBE2KL.js +16 -0
- package/dist/chunk-I4NBE2KL.js.map +1 -0
- package/dist/components/data/index.d.ts +42 -1012
- package/dist/components/data/index.js +411 -704
- package/dist/components/data/index.js.map +1 -1
- package/dist/components/layout/index.d.ts +2 -1
- package/dist/components/patterns/index.js +2 -1
- package/dist/components/patterns/sortable-list.js +2 -1
- package/dist/hooks/index.d.ts +7 -1
- package/dist/hooks/index.js +2 -0
- package/dist/hooks/use-resource-page.d.ts +1 -1
- package/dist/index.js +3 -2
- package/dist/{use-resource-page-BDnnCssE.d.ts → use-resource-page-DaJzuq3M.d.ts} +1 -1
- package/dist/use-resource-table-DCVf_lEG.d.ts +1113 -0
- package/package.json +2 -2
- package/dist/chunk-S7NXD37O.js.map +0 -1
|
@@ -0,0 +1,1113 @@
|
|
|
1
|
+
import { R as ResourceActions, a as ResourceColumnDef, D as DataTable, c as DataTableColumn, d as DataTableSort } from './use-resource-page-DaJzuq3M.js';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { ReactNode, ComponentType, ComponentProps, RefObject } from 'react';
|
|
4
|
+
import { LucideIcon } from 'lucide-react';
|
|
5
|
+
import { UseFormReturn } from 'react-hook-form';
|
|
6
|
+
import { c as PageHeaderAction, a as PageHeaderOptions, P as PageHeader } from './PageHeader-V9lVkZtF.js';
|
|
7
|
+
import { V as ViewMode } from './view-toggle-CahQ87PW.js';
|
|
8
|
+
import { Density } from './hooks/use-view-settings.js';
|
|
9
|
+
import { WizardStep, MutationMode, UndoableWrite, UseResourceOptions } from '@marktiderman/genesis-core/hooks';
|
|
10
|
+
import { ResourceFormFieldDef } from '@marktiderman/genesis-core';
|
|
11
|
+
import { b as Slots, a as SlotPropsFor } from './slots-tR0wf89f.js';
|
|
12
|
+
import { ViewModeDefault } from './hooks/use-view-preference.js';
|
|
13
|
+
import { ResourceUrlStateOptions } from './hooks/use-resource-url-state.js';
|
|
14
|
+
import { FilterParam, SortParam } from '@marktiderman/genesis-core/provider';
|
|
15
|
+
import { a as NavigateFn, S as SearchParamsAdapter } from './navigation-Bha0R3Fu.js';
|
|
16
|
+
|
|
17
|
+
interface DataPageShellProps {
|
|
18
|
+
/** Display title for the page */
|
|
19
|
+
title: string;
|
|
20
|
+
icon?: LucideIcon;
|
|
21
|
+
/** Singular entity name for generated text, e.g. "session" */
|
|
22
|
+
entityName?: string;
|
|
23
|
+
/** Subtitle text */
|
|
24
|
+
subtitle?: string;
|
|
25
|
+
createAction?: ReactNode;
|
|
26
|
+
/** Extra actions, rendered in the header's one overflow menu. */
|
|
27
|
+
secondaryActions?: PageHeaderAction[];
|
|
28
|
+
isLoading?: boolean;
|
|
29
|
+
isEmpty?: boolean;
|
|
30
|
+
emptyIcon?: LucideIcon;
|
|
31
|
+
emptyMessage?: string;
|
|
32
|
+
/**
|
|
33
|
+
* A sentence under the empty-state title — what this list is for, or what
|
|
34
|
+
* to do first. Forwarded to `EmptyState`'s `description`; hidden while
|
|
35
|
+
* filters are active, where "no results" is the whole story.
|
|
36
|
+
*/
|
|
37
|
+
emptyDescription?: string;
|
|
38
|
+
emptyAction?: ReactNode;
|
|
39
|
+
/** Error state */
|
|
40
|
+
hasError?: boolean;
|
|
41
|
+
errorMessage?: string;
|
|
42
|
+
onRetry?: () => void;
|
|
43
|
+
/** Number of filtered/visible items */
|
|
44
|
+
count?: number;
|
|
45
|
+
/** Total items before filtering */
|
|
46
|
+
totalCount?: number;
|
|
47
|
+
viewMode?: ViewMode;
|
|
48
|
+
onViewModeChange?: (mode: ViewMode) => void;
|
|
49
|
+
/** View settings */
|
|
50
|
+
pageSize?: number;
|
|
51
|
+
onPageSizeChange?: (size: number) => void;
|
|
52
|
+
density?: Density;
|
|
53
|
+
onDensityChange?: (density: Density) => void;
|
|
54
|
+
/**
|
|
55
|
+
* Slot for a stat cards row. Forwarded to the header, which renders it
|
|
56
|
+
* full width under the title block and above the filters — but only when
|
|
57
|
+
* `headerOptions.showStats` is on (default off), so a page that gains
|
|
58
|
+
* `stats` shows nothing new until the developer or user opts in.
|
|
59
|
+
*/
|
|
60
|
+
stats?: ReactNode;
|
|
61
|
+
/** Slot for filter bar */
|
|
62
|
+
filters?: ReactNode;
|
|
63
|
+
/** Whether filters are currently active */
|
|
64
|
+
hasActiveFilters?: boolean;
|
|
65
|
+
onClearFilters?: () => void;
|
|
66
|
+
/**
|
|
67
|
+
* When true, filters render inside PageHeader details (collapsed by default).
|
|
68
|
+
* Default false preserves always-visible filters on existing boards.
|
|
69
|
+
*/
|
|
70
|
+
filtersCollapsible?: boolean;
|
|
71
|
+
defaultFiltersOpen?: boolean;
|
|
72
|
+
filtersOpen?: boolean;
|
|
73
|
+
onFiltersOpenChange?: (open: boolean) => void;
|
|
74
|
+
filtersLabel?: string;
|
|
75
|
+
filtersHint?: ReactNode;
|
|
76
|
+
/**
|
|
77
|
+
* PageHeader chrome config — developer defaults; users can further
|
|
78
|
+
* customize via the in-header Header options menu.
|
|
79
|
+
*/
|
|
80
|
+
headerOptions?: PageHeaderOptions;
|
|
81
|
+
/** Persist user header-option overrides (localStorage) */
|
|
82
|
+
headerOptionsStorageKey?: string;
|
|
83
|
+
children: ReactNode;
|
|
84
|
+
/** Merged onto the root, not replacing its layout. */
|
|
85
|
+
className?: string;
|
|
86
|
+
/**
|
|
87
|
+
* Replace the header outright. It receives exactly the props
|
|
88
|
+
* `DataPageShell` would have given `PageHeader`, so the common case is a
|
|
89
|
+
* wrapper that renders `PageHeader` with one thing added — an overflow
|
|
90
|
+
* menu, a status strip, a second row — rather than a reimplementation.
|
|
91
|
+
*
|
|
92
|
+
* `null` ejects it: shell layout, states and filter row, no header.
|
|
93
|
+
*
|
|
94
|
+
* `"toolbar"` keeps the header's actions — count, view toggle, view
|
|
95
|
+
* settings, create, overflow menu — and drops the title block, for a list
|
|
96
|
+
* embedded in a tab or card whose page already has the heading. `title`
|
|
97
|
+
* still feeds the generated copy ("Failed to load …", the count's noun).
|
|
98
|
+
*/
|
|
99
|
+
header?: ComponentType<ComponentProps<typeof PageHeader>> | null | "toolbar";
|
|
100
|
+
}
|
|
101
|
+
declare function DataPageShell({ title, icon, entityName, subtitle, createAction, secondaryActions, isLoading, isEmpty, emptyIcon, emptyMessage, emptyDescription, emptyAction, hasError, errorMessage, onRetry, count, totalCount, viewMode, onViewModeChange, pageSize, onPageSizeChange, density, onDensityChange, stats, filters, hasActiveFilters, onClearFilters, filtersCollapsible, defaultFiltersOpen, filtersOpen, onFiltersOpenChange, filtersLabel, filtersHint, headerOptions, headerOptionsStorageKey, children, className, header, }: DataPageShellProps): React.JSX.Element;
|
|
102
|
+
|
|
103
|
+
interface FilterOption {
|
|
104
|
+
value: string;
|
|
105
|
+
label?: string;
|
|
106
|
+
count?: number;
|
|
107
|
+
}
|
|
108
|
+
interface FilterComboboxProps {
|
|
109
|
+
label: string;
|
|
110
|
+
icon: LucideIcon;
|
|
111
|
+
options: FilterOption[];
|
|
112
|
+
selected: string[];
|
|
113
|
+
onChange: (selected: string[]) => void;
|
|
114
|
+
searchPlaceholder?: string;
|
|
115
|
+
allLabel?: string;
|
|
116
|
+
/** Optional map of value -> display label */
|
|
117
|
+
labelMap?: Record<string, string>;
|
|
118
|
+
}
|
|
119
|
+
declare function FilterCombobox({ label, icon: Icon, options, selected, onChange, searchPlaceholder, allLabel, labelMap, }: FilterComboboxProps): React.JSX.Element;
|
|
120
|
+
|
|
121
|
+
interface DataFilterConfig {
|
|
122
|
+
key: string;
|
|
123
|
+
label: string;
|
|
124
|
+
icon: LucideIcon;
|
|
125
|
+
options: FilterOption[];
|
|
126
|
+
selected: string[];
|
|
127
|
+
onChange: (values: string[]) => void;
|
|
128
|
+
searchPlaceholder?: string;
|
|
129
|
+
allLabel?: string;
|
|
130
|
+
labelMap?: Record<string, string>;
|
|
131
|
+
}
|
|
132
|
+
interface SavedViewItem {
|
|
133
|
+
id: string;
|
|
134
|
+
name: string;
|
|
135
|
+
filters: Record<string, unknown>;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* One status chip. A plain string is shorthand for `{ value, label: value }`.
|
|
139
|
+
* `count` renders as a muted number after the label.
|
|
140
|
+
*/
|
|
141
|
+
interface StatusChipOption {
|
|
142
|
+
value: string;
|
|
143
|
+
label?: string;
|
|
144
|
+
count?: number;
|
|
145
|
+
}
|
|
146
|
+
/** Rendering of the status chips — a row of pills, or one segmented strip. */
|
|
147
|
+
type StatusChipsVariant = "chips" | "tabs";
|
|
148
|
+
interface DataFiltersProps {
|
|
149
|
+
search: string;
|
|
150
|
+
onSearchChange: (value: string) => void;
|
|
151
|
+
searchPlaceholder?: string;
|
|
152
|
+
sort: string;
|
|
153
|
+
onSortChange: (sort: string) => void;
|
|
154
|
+
sortOptions: Array<{
|
|
155
|
+
value: string;
|
|
156
|
+
label: string;
|
|
157
|
+
}>;
|
|
158
|
+
filters?: DataFilterConfig[];
|
|
159
|
+
/** Status chip toggles */
|
|
160
|
+
statusChips?: {
|
|
161
|
+
options: Array<string | StatusChipOption>;
|
|
162
|
+
selected: string[];
|
|
163
|
+
onChange: (values: string[]) => void;
|
|
164
|
+
/**
|
|
165
|
+
* `"chips"` (default) renders pills inside the filter row. `"tabs"`
|
|
166
|
+
* renders a segmented strip on its own row above the search row —
|
|
167
|
+
* the same border-and-fill look as `ViewToggle`. Selection is
|
|
168
|
+
* multi-select in both.
|
|
169
|
+
*/
|
|
170
|
+
variant?: StatusChipsVariant;
|
|
171
|
+
};
|
|
172
|
+
/** Saved views */
|
|
173
|
+
savedViews?: SavedViewItem[];
|
|
174
|
+
onLoadView?: (filters: Record<string, unknown>) => void;
|
|
175
|
+
onSaveView?: (name: string) => void;
|
|
176
|
+
onDeleteView?: (id: string) => void;
|
|
177
|
+
canSaveViews?: boolean;
|
|
178
|
+
/** Called after a view is saved successfully */
|
|
179
|
+
onViewSaved?: (name: string) => void;
|
|
180
|
+
/** Called when an error occurs */
|
|
181
|
+
onError?: (message: string) => void;
|
|
182
|
+
/** Called when "Clear all" is clicked */
|
|
183
|
+
onClearAll?: () => void;
|
|
184
|
+
/** Expose the search input ref for keyboard nav "/" shortcut */
|
|
185
|
+
searchInputRef?: React.RefObject<HTMLInputElement | null>;
|
|
186
|
+
/** Show the search box. Default `true`; turn off when the data source can't search. */
|
|
187
|
+
showSearch?: boolean;
|
|
188
|
+
/** Show the sort select. Default `true`; turn off when the data source can't sort. */
|
|
189
|
+
showSort?: boolean;
|
|
190
|
+
}
|
|
191
|
+
declare function DataFilters({ search, onSearchChange, searchPlaceholder, sort, onSortChange, sortOptions, filters, statusChips, savedViews, onLoadView, onSaveView, onDeleteView, canSaveViews, onViewSaved, onClearAll, searchInputRef, showSearch, showSort, }: DataFiltersProps): React.JSX.Element;
|
|
192
|
+
|
|
193
|
+
interface DataGridProps<T> {
|
|
194
|
+
items: T[];
|
|
195
|
+
viewMode: ViewMode;
|
|
196
|
+
/** Items per infinite scroll batch (grid only). Default 48 */
|
|
197
|
+
batchSize?: number;
|
|
198
|
+
/** Tailwind grid column classes */
|
|
199
|
+
gridCols?: string;
|
|
200
|
+
/** Render a single card in grid mode */
|
|
201
|
+
renderCard: (item: T, index: number) => ReactNode;
|
|
202
|
+
/** Render the table view (receives currently visible items) */
|
|
203
|
+
renderTable?: (items: T[]) => ReactNode;
|
|
204
|
+
/** Render a compact list row */
|
|
205
|
+
renderListItem?: (item: T, index: number) => ReactNode;
|
|
206
|
+
/** Animate cards with staggered fade-in. Default true */
|
|
207
|
+
staggerAnimation?: boolean;
|
|
208
|
+
/** Unique key extractor. Defaults to (item as any).id */
|
|
209
|
+
getKey?: (item: T) => string;
|
|
210
|
+
/** Keyboard navigation: focused item index (-1 = none) */
|
|
211
|
+
focusedIndex?: number;
|
|
212
|
+
/** Keyboard navigation: container ref for scroll management */
|
|
213
|
+
keyboardContainerRef?: RefObject<HTMLDivElement | null>;
|
|
214
|
+
/**
|
|
215
|
+
* Open an item. Makes each card (and list row) one keyboard-reachable
|
|
216
|
+
* button: click, Enter or Space activates it, with a focus ring. Clicks on
|
|
217
|
+
* a link, button or input inside the card are left to that control, so a
|
|
218
|
+
* card can still carry its own actions. Use this instead of wrapping cards
|
|
219
|
+
* in a hand-rolled `div role="button"`.
|
|
220
|
+
*/
|
|
221
|
+
onItemClick?: ((item: T) => void) | undefined;
|
|
222
|
+
/** Accessible name for an activatable item, e.g. `Open ${item.name}`. Used with `onItemClick`. */
|
|
223
|
+
getItemLabel?: ((item: T) => string) | undefined;
|
|
224
|
+
}
|
|
225
|
+
declare function DataGrid<T>({ items, viewMode, batchSize, gridCols, renderCard, renderTable, renderListItem, staggerAnimation, getKey, focusedIndex, keyboardContainerRef, onItemClick, getItemLabel, }: DataGridProps<T>): React.JSX.Element;
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* One button in the bulk action bar.
|
|
229
|
+
*
|
|
230
|
+
* @stability Stable
|
|
231
|
+
*/
|
|
232
|
+
interface BulkAction {
|
|
233
|
+
label: string;
|
|
234
|
+
icon: LucideIcon;
|
|
235
|
+
onClick: () => void;
|
|
236
|
+
variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link";
|
|
237
|
+
/**
|
|
238
|
+
* Makes this action inert: it renders with the Button primitive's native
|
|
239
|
+
* `disabled` attribute, so it is neither clickable nor focusable, and it
|
|
240
|
+
* picks up the disabled styling.
|
|
241
|
+
*
|
|
242
|
+
* The case this exists for is an in-flight bulk mutation. A consumer that
|
|
243
|
+
* fires a move/duplicate/delete over the selected ids wants every bulk
|
|
244
|
+
* action dead until it settles, so a user cannot double-fire a destructive
|
|
245
|
+
* operation on the same selection. Without this field the only guard
|
|
246
|
+
* available is an early return inside `onClick` — which does stop the
|
|
247
|
+
* second call, but leaves the button looking live, so the affordance lies
|
|
248
|
+
* about what pressing it will do. Concretely, that is what blocks a real
|
|
249
|
+
* consumer's hand-rolled bulk bar from migrating to `DataBulkBar`: it
|
|
250
|
+
* would have to give up its double-submit guard to do so.
|
|
251
|
+
*
|
|
252
|
+
* Optional and defaulted off — omitting it behaves exactly as before.
|
|
253
|
+
*/
|
|
254
|
+
disabled?: boolean;
|
|
255
|
+
/**
|
|
256
|
+
* `data-testid` on the rendered button.
|
|
257
|
+
*
|
|
258
|
+
* Without it, a consumer migrating a hand-rolled bulk bar to `DataBulkBar`
|
|
259
|
+
* loses every selector its e2e suite uses to drive one — which is a real
|
|
260
|
+
* reason not to migrate, and the kind of small missing affordance that
|
|
261
|
+
* keeps a parallel implementation alive.
|
|
262
|
+
*/
|
|
263
|
+
testId?: string;
|
|
264
|
+
}
|
|
265
|
+
interface DataBulkBarProps {
|
|
266
|
+
selected: Set<string>;
|
|
267
|
+
totalCount: number;
|
|
268
|
+
onToggleAll: () => void;
|
|
269
|
+
onClearSelection: () => void;
|
|
270
|
+
actions: BulkAction[];
|
|
271
|
+
/** Merged onto the bar, not replacing its positioning. */
|
|
272
|
+
className?: string;
|
|
273
|
+
}
|
|
274
|
+
declare function DataBulkBar({ selected, totalCount, onToggleAll, onClearSelection, actions, className, }: DataBulkBarProps): React.JSX.Element | null;
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Props for {@link DetailPanel} — a shell-agnostic record detail view.
|
|
278
|
+
* `layout` selects only the surrounding shell (inline / dialog / sheet);
|
|
279
|
+
* the header and content below it render identically across all three.
|
|
280
|
+
*
|
|
281
|
+
* @stability Beta
|
|
282
|
+
*/
|
|
283
|
+
interface DetailPanelProps<T> {
|
|
284
|
+
/** The record to display */
|
|
285
|
+
item: T | null;
|
|
286
|
+
/** Whether panel is visible */
|
|
287
|
+
open: boolean;
|
|
288
|
+
/** Close handler */
|
|
289
|
+
onClose: () => void;
|
|
290
|
+
/** Header title */
|
|
291
|
+
title?: string;
|
|
292
|
+
/** Header subtitle */
|
|
293
|
+
subtitle?: string;
|
|
294
|
+
/** Shows edit button if provided */
|
|
295
|
+
onEdit?: (item: T) => void;
|
|
296
|
+
/** Shows delete button if provided */
|
|
297
|
+
onDelete?: (item: T) => void;
|
|
298
|
+
/** Custom content renderer */
|
|
299
|
+
children?: (item: T) => ReactNode;
|
|
300
|
+
/** Field definitions for auto-rendering */
|
|
301
|
+
fields?: Array<{
|
|
302
|
+
key: string;
|
|
303
|
+
label: string;
|
|
304
|
+
render?: (value: unknown, item: T) => ReactNode;
|
|
305
|
+
}>;
|
|
306
|
+
/**
|
|
307
|
+
* Panel width. For `"sheet"` / `"inline"` this is a fixed pixel width
|
|
308
|
+
* (matching this component's original, pre-`layout` sizing exactly). For
|
|
309
|
+
* `"dialog"` it maps to Tailwind's modal max-width scale instead, since a
|
|
310
|
+
* centered modal has a different sizing idiom than an edge-anchored panel.
|
|
311
|
+
*/
|
|
312
|
+
width?: "sm" | "md" | "lg";
|
|
313
|
+
/**
|
|
314
|
+
* Rendering shell for the header + content below.
|
|
315
|
+
* - `"sheet"` (default): fixed, right-anchored slide-in panel, composed
|
|
316
|
+
* from the shared `Sheet` primitive. Matches this component's original
|
|
317
|
+
* (pre-`layout`-prop) behavior — existing consumers that don't pass
|
|
318
|
+
* `layout` see no change, other than an overlay now present at all
|
|
319
|
+
* breakpoints (previously mobile-only) since it's a real `Sheet`.
|
|
320
|
+
* - `"dialog"`: centered modal overlay, composed from the shared `Dialog`
|
|
321
|
+
* primitive.
|
|
322
|
+
* - `"inline"`: plain in-flow panel — no overlay, backdrop, or fixed
|
|
323
|
+
* positioning — for master-detail / split-pane layouts.
|
|
324
|
+
*/
|
|
325
|
+
layout?: "inline" | "dialog" | "sheet";
|
|
326
|
+
}
|
|
327
|
+
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;
|
|
328
|
+
|
|
329
|
+
interface ResourceFormProps {
|
|
330
|
+
resource: string;
|
|
331
|
+
action: "create" | "edit";
|
|
332
|
+
item?: Record<string, unknown>;
|
|
333
|
+
open: boolean;
|
|
334
|
+
onOpenChange: (open: boolean) => void;
|
|
335
|
+
title?: string;
|
|
336
|
+
fields?: ResourceFormFieldDef[];
|
|
337
|
+
/** Full dataset used to auto-detect select fields from unique values. */
|
|
338
|
+
listData?: Record<string, unknown>[];
|
|
339
|
+
renderForm?: (form: UseFormReturn<Record<string, unknown>>, fields: ResourceFormFieldDef[]) => ReactNode;
|
|
340
|
+
onSuccess?: (data: Record<string, unknown>) => void;
|
|
341
|
+
onError?: (error: Error) => void;
|
|
342
|
+
/** Layout mode for the form container. Default "dialog". */
|
|
343
|
+
layout?: "dialog" | "sheet" | "page";
|
|
344
|
+
/** Form presentation mode. Default "standard". */
|
|
345
|
+
mode?: "standard" | "wizard";
|
|
346
|
+
/** Wizard step definitions. Required when mode is "wizard". */
|
|
347
|
+
steps?: WizardStep[];
|
|
348
|
+
/** Number of columns for the form layout. Default 1. */
|
|
349
|
+
columns?: 1 | 2;
|
|
350
|
+
/**
|
|
351
|
+
* Pre-fill a create form from the page URL: a query param named like a
|
|
352
|
+
* field (`?status=draft`) becomes that field's starting value. Default
|
|
353
|
+
* `true`, as before. Pass `false` when the page's query string means
|
|
354
|
+
* something else — a list's own filters, say — and must not leak into the
|
|
355
|
+
* new record.
|
|
356
|
+
*/
|
|
357
|
+
prefillFromUrl?: boolean;
|
|
358
|
+
/**
|
|
359
|
+
* Generate a placeholder ("Enter name") for fields whose `placeholder` is
|
|
360
|
+
* unset. Default `false`: a placeholder that restates the label adds noise,
|
|
361
|
+
* and reads as a filled value at a glance. A field's own `placeholder`
|
|
362
|
+
* always shows. (Select fields keep their "Select…" prompt either way.)
|
|
363
|
+
*/
|
|
364
|
+
autoPlaceholder?: boolean;
|
|
365
|
+
/**
|
|
366
|
+
* Warn before unsaved changes are lost. Default false. While the form is
|
|
367
|
+
* dirty, Cancel / Esc / clicking outside asks first, and closing or
|
|
368
|
+
* reloading the tab shows the browser's prompt. Pass `{ message }` to
|
|
369
|
+
* change the question. For router navigation, see `onDirtyChange`.
|
|
370
|
+
*/
|
|
371
|
+
warnWhenUnsaved?: boolean | {
|
|
372
|
+
message?: string;
|
|
373
|
+
};
|
|
374
|
+
/**
|
|
375
|
+
* Called when the form becomes dirty or clean, with a `confirmLeave` that
|
|
376
|
+
* asks when needed: the hook for a router blocker (SKILL.md "Unsaved
|
|
377
|
+
* changes").
|
|
378
|
+
*/
|
|
379
|
+
onDirtyChange?: (isDirty: boolean, confirmLeave: () => boolean) => void;
|
|
380
|
+
/**
|
|
381
|
+
* When a save reaches the list. Default `"pessimistic"`: the form waits
|
|
382
|
+
* for the provider. `"optimistic"` closes at once and updates the cached
|
|
383
|
+
* list, putting it back (and toasting the error) if the save fails.
|
|
384
|
+
* `"undoable"` does the same and shows an Undo toast; the save is sent
|
|
385
|
+
* after `undoDelayMs` unless undone (or at once, undone by the provider,
|
|
386
|
+
* when it serves `undo`). Needs a mounted `<Toaster />`.
|
|
387
|
+
*/
|
|
388
|
+
mutationMode?: MutationMode;
|
|
389
|
+
/** How long an `undoable` save waits for an undo, in ms. Default 5000. */
|
|
390
|
+
undoDelayMs?: number;
|
|
391
|
+
/** Replaces the undo toast. */
|
|
392
|
+
onUndoable?: (write: UndoableWrite) => void;
|
|
393
|
+
}
|
|
394
|
+
declare function ResourceForm(props: ResourceFormProps): React.JSX.Element | null;
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Props for {@link ConfirmDeleteDialog}.
|
|
398
|
+
*
|
|
399
|
+
* @stability Beta
|
|
400
|
+
*/
|
|
401
|
+
interface ConfirmDeleteDialogProps {
|
|
402
|
+
open: boolean;
|
|
403
|
+
onOpenChange: (open: boolean) => void;
|
|
404
|
+
/**
|
|
405
|
+
* Runs the delete. The dialog stays open while it is pending, closes when
|
|
406
|
+
* it resolves, and shows the error's message (staying open, so the user
|
|
407
|
+
* can retry or cancel) when it rejects.
|
|
408
|
+
*/
|
|
409
|
+
onConfirm: () => void | Promise<void>;
|
|
410
|
+
/** Default "Delete this record?". */
|
|
411
|
+
title?: string;
|
|
412
|
+
/**
|
|
413
|
+
* What happens to the record: `"permanent"` (default) or `"recycle"` when
|
|
414
|
+
* a delete moves it to a recycle bin. Sets the default description.
|
|
415
|
+
*/
|
|
416
|
+
kind?: "permanent" | "recycle";
|
|
417
|
+
/**
|
|
418
|
+
* Default by `kind`: "This can't be undone.", or "It moves to the recycle
|
|
419
|
+
* bin, where it can be restored."
|
|
420
|
+
*/
|
|
421
|
+
description?: ReactNode;
|
|
422
|
+
/** Default "Delete". */
|
|
423
|
+
confirmLabel?: string;
|
|
424
|
+
/** Default "Cancel". */
|
|
425
|
+
cancelLabel?: string;
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* The confirm step in front of a destructive delete: an `AlertDialog` (Radix
|
|
429
|
+
* owns focus trap, Escape and focus return), a pending state, and the
|
|
430
|
+
* failure shown in place rather than swallowed. `ResourcePage` renders it
|
|
431
|
+
* for `allowDelete` / `confirmDelete`, as its `deleteDialog` slot.
|
|
432
|
+
*
|
|
433
|
+
* @stability Beta
|
|
434
|
+
*/
|
|
435
|
+
declare function ConfirmDeleteDialog({ open, onOpenChange, onConfirm, title, kind, description, confirmLabel, cancelLabel, }: ConfirmDeleteDialogProps): React.JSX.Element;
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* What a user may do with a list's records, for ResourcePage's `can`.
|
|
439
|
+
*
|
|
440
|
+
* @stability Beta
|
|
441
|
+
*/
|
|
442
|
+
type ResourceAction = "create" | "edit" | "delete";
|
|
443
|
+
/**
|
|
444
|
+
* Row-bound actions handed to `renderCard`, on top of the resource-wide
|
|
445
|
+
* `ResourceActions`. `open` is per-row, which is why it lives here rather
|
|
446
|
+
* than on `ResourceActions` — that object is built once and shared by every
|
|
447
|
+
* row and column, so it cannot carry a row-specific callback.
|
|
448
|
+
*
|
|
449
|
+
* @stability Beta
|
|
450
|
+
*/
|
|
451
|
+
interface ResourceCardActions<T> extends ResourceActions<T> {
|
|
452
|
+
/**
|
|
453
|
+
* Activate this row — opens the configured detail panel / modal / route,
|
|
454
|
+
* exactly as clicking the built-in card does. Wire it to your card's
|
|
455
|
+
* `onClick` (and call `stopPropagation` on any nested control that should
|
|
456
|
+
* not also activate the row).
|
|
457
|
+
*/
|
|
458
|
+
open: () => void;
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Config-driven resource page: wires data-fetching (or a pre-fetched `data`
|
|
462
|
+
* array), search/filter/sort, table/grid/list views, and a detail
|
|
463
|
+
* panel/modal/route around a `DataProvider` resource — the CRUD page most
|
|
464
|
+
* apps hand-roll, expressed as one component tree. `renderCard` lets a
|
|
465
|
+
* consumer fully own item presentation while ResourcePage keeps handling
|
|
466
|
+
* data-fetching, filtering, and layout; see also the built-in
|
|
467
|
+
* `useKeyboardNavigation` wiring (`keyboardNavigation` prop) and `gridCols`.
|
|
468
|
+
*
|
|
469
|
+
* Three levels of control, and each option is available at all three:
|
|
470
|
+
*
|
|
471
|
+
* 1. **The primitive offers it** — table / grid / list views, search, sort,
|
|
472
|
+
* status chips, column visibility and resizing, density, page size,
|
|
473
|
+
* detail panel / modal / route, built-in create and edit forms.
|
|
474
|
+
* 2. **The instance turns it on or off** — every one of those is a prop, so
|
|
475
|
+
* a given page decides which affordances it offers at all.
|
|
476
|
+
* 3. **The end user picks their own, and it sticks** — for the options left
|
|
477
|
+
* on at level 2, the user's choice persists to their own storage:
|
|
478
|
+
* `viewSettingsKey` for page size and density, `columnVisibilityKey` and
|
|
479
|
+
* `resizeKey` for column layout, `savedViews` (pair with `useSavedViews`)
|
|
480
|
+
* for named filter sets. Persistence needs a key; without one the choice
|
|
481
|
+
* lasts only for the session.
|
|
482
|
+
*
|
|
483
|
+
* A server-paginated list drives the same component through the controlled
|
|
484
|
+
* props (`page`, `perPage`, `onPageChange`, `onSortChange`, `onSearchChange`
|
|
485
|
+
* and friends) — supplying any of them stops ResourcePage from searching,
|
|
486
|
+
* sorting or paging the `data` a second time. See that block in
|
|
487
|
+
* {@link ResourcePageProps}.
|
|
488
|
+
*
|
|
489
|
+
* @stability Beta
|
|
490
|
+
*/
|
|
491
|
+
/**
|
|
492
|
+
* Every interior part `ResourcePage` composes, with the props it composes it
|
|
493
|
+
* with. Each key is a slot: replace it via `slots`, feed it more props via
|
|
494
|
+
* `slotProps`, or eject it with `null`.
|
|
495
|
+
*
|
|
496
|
+
* The map is the contract. A part that is not named here cannot be reached,
|
|
497
|
+
* so adding a part to the render tree means adding it here in the same
|
|
498
|
+
* change — otherwise the next surface that needs it forks the whole page.
|
|
499
|
+
*/
|
|
500
|
+
type ResourcePageSlotMap<T extends Record<string, unknown> = Record<string, unknown>> = {
|
|
501
|
+
/** The page shell: header, count pill, loading / empty / error states. */
|
|
502
|
+
shell: ComponentProps<typeof DataPageShell>;
|
|
503
|
+
/** The filter row — search, sort, facets, saved views. */
|
|
504
|
+
filters: ComponentProps<typeof DataFilters>;
|
|
505
|
+
/** The selection bar. Ejecting it leaves selection working, just unshown. */
|
|
506
|
+
bulkBar: ComponentProps<typeof DataBulkBar>;
|
|
507
|
+
/** The card/table switch. */
|
|
508
|
+
grid: ComponentProps<typeof DataGrid<T>>;
|
|
509
|
+
/** The table inside the grid. `renderTable` stays the terser way to swap. */
|
|
510
|
+
table: ComponentProps<typeof DataTable<T>>;
|
|
511
|
+
/** The detail peek. */
|
|
512
|
+
detail: ComponentProps<typeof DetailPanel<T>>;
|
|
513
|
+
/** The create / edit form. */
|
|
514
|
+
form: ComponentProps<typeof ResourceForm>;
|
|
515
|
+
/**
|
|
516
|
+
* The confirm step in front of a delete. Rendered only when a delete is
|
|
517
|
+
* confirmed (`allowDelete`, or `onDelete` with `confirmDelete`).
|
|
518
|
+
*/
|
|
519
|
+
deleteDialog: ComponentProps<typeof ConfirmDeleteDialog>;
|
|
520
|
+
};
|
|
521
|
+
/**
|
|
522
|
+
* `slots` for {@link ResourcePage}, with one asymmetry the map cannot express:
|
|
523
|
+
* every part may be ejected with `null` EXCEPT `shell`.
|
|
524
|
+
*
|
|
525
|
+
* The shell is not an interior part, it is the page — `Shell` is what the
|
|
526
|
+
* render returns, so ejecting it does not remove a piece, it removes the
|
|
527
|
+
* component (`const Shell = slots?.shell ?? DataPageShell` would become
|
|
528
|
+
* `null`, and React throws on the element). A caller who wants no shell wants
|
|
529
|
+
* rung 3 of the ladder, not a `null` here. Replacing it stays supported, which
|
|
530
|
+
* is the way to wrap it and change one thing.
|
|
531
|
+
*/
|
|
532
|
+
type ResourcePageSlots<T extends Record<string, unknown> = Record<string, unknown>> = Omit<Slots<ResourcePageSlotMap<T>>, "shell"> & {
|
|
533
|
+
shell?: ComponentType<ComponentProps<typeof DataPageShell>>;
|
|
534
|
+
};
|
|
535
|
+
/**
|
|
536
|
+
* `slotProps` for {@link ResourcePage}: extra props merged into a part you
|
|
537
|
+
* are keeping, winning over ResourcePage's own.
|
|
538
|
+
*/
|
|
539
|
+
type ResourcePageSlotProps<T extends Record<string, unknown> = Record<string, unknown>> = SlotPropsFor<ResourcePageSlotMap<T>>;
|
|
540
|
+
/**
|
|
541
|
+
* Copy for the built-in delete confirmation. Every field is optional;
|
|
542
|
+
* omitted ones fall back to `ConfirmDeleteDialog`'s defaults.
|
|
543
|
+
*
|
|
544
|
+
* @stability Beta
|
|
545
|
+
*/
|
|
546
|
+
interface ResourceDeleteConfirmCopy<T> {
|
|
547
|
+
/** Default "Delete this record?". A function receives the row. */
|
|
548
|
+
title?: string | ((item: T) => string);
|
|
549
|
+
/** Default "This can't be undone." */
|
|
550
|
+
description?: ReactNode | ((item: T) => ReactNode);
|
|
551
|
+
/** Default "Delete". */
|
|
552
|
+
confirmLabel?: string;
|
|
553
|
+
/** Default "Cancel". */
|
|
554
|
+
cancelLabel?: string;
|
|
555
|
+
}
|
|
556
|
+
interface ResourcePageProps<T extends Record<string, unknown> = Record<string, unknown>> {
|
|
557
|
+
/** Client-side mode: resource name passed to useResource / DataProvider. */
|
|
558
|
+
resource?: string;
|
|
559
|
+
/** Server-data mode: pre-fetched data array. */
|
|
560
|
+
data?: T[];
|
|
561
|
+
/**
|
|
562
|
+
* Server-data mode: total record count (before client-side filtering).
|
|
563
|
+
*
|
|
564
|
+
* Required whenever `onPageChange` is supplied. It cannot be derived from
|
|
565
|
+
* `data`, which holds only the current page. Omitted, `displayTotal` falls
|
|
566
|
+
* back to `0`, so the range span reads "No records" and `Next` stays
|
|
567
|
+
* disabled while the rows render directly below it. Rather than show a
|
|
568
|
+
* pager that contradicts the list, the component refuses to render the
|
|
569
|
+
* pager and shows an error instead. See {@link perPage} and
|
|
570
|
+
* {@link onPageChange}.
|
|
571
|
+
*/
|
|
572
|
+
total?: number;
|
|
573
|
+
/**
|
|
574
|
+
* How `resource` mode reaches rows past the first page. Ignored with
|
|
575
|
+
* `data` (server-data mode pages through the controlled props instead).
|
|
576
|
+
*
|
|
577
|
+
* - `"client"` (default): fetch up to `clientRowLimit` rows in one request
|
|
578
|
+
* and search, sort, filter and page them in the browser — header-click
|
|
579
|
+
* sort and instant search keep working. If the resource holds more rows
|
|
580
|
+
* than that, a notice under the list says how many are shown (and a
|
|
581
|
+
* console warning names this prop), rather than silently truncating.
|
|
582
|
+
* - `"server"`: fetch one page at a time. The pager under the list drives
|
|
583
|
+
* `useResource`'s page, the header's page size its `perPage`, and
|
|
584
|
+
* search / sort / status chips become provider queries (search is
|
|
585
|
+
* debounced), and the current page stays on screen while the next one
|
|
586
|
+
* loads. Give `statusFilter` explicit `options` here — derived ones can
|
|
587
|
+
* only see the current page's values. Search needs `searchFields` (or
|
|
588
|
+
* `resourceOptions.searchFields`) — the provider must be told which
|
|
589
|
+
* columns to match — so the search box is hidden without them. Table
|
|
590
|
+
* header sorting is withdrawn, as in server-data mode; the sort control
|
|
591
|
+
* in the filter row drives the query.
|
|
592
|
+
*/
|
|
593
|
+
pagination?: "client" | "server";
|
|
594
|
+
/**
|
|
595
|
+
* `pagination="client"` only: the most rows fetched. Default 1000. An
|
|
596
|
+
* explicit `resourceOptions.defaultPerPage` wins. Keep it at or under the
|
|
597
|
+
* provider's `maxPerPage` (`capabilities().maxPerPage`; 1000 by default for
|
|
598
|
+
* Supabase): a larger page is refused, not cut short.
|
|
599
|
+
*/
|
|
600
|
+
clientRowLimit?: number;
|
|
601
|
+
title: string;
|
|
602
|
+
/** Page subtitle shown below the title. */
|
|
603
|
+
subtitle?: string;
|
|
604
|
+
icon?: LucideIcon;
|
|
605
|
+
columns?: Array<string | ResourceColumnDef<T>>;
|
|
606
|
+
/**
|
|
607
|
+
* A row's identity, for React keys and bulk selection. Defaults to
|
|
608
|
+
* `String(item.id)`. Supply it for rows keyed some other way — a composite
|
|
609
|
+
* key (`encodeRecordKey` from `@marktiderman/genesis-core/schema`), or a
|
|
610
|
+
* table whose key column isn't called `id`. Detail `route` mode still
|
|
611
|
+
* navigates by `item.id`.
|
|
612
|
+
*/
|
|
613
|
+
getKey?: (item: T) => string;
|
|
614
|
+
/** How row-click detail is shown. Default "panel". */
|
|
615
|
+
detail?: "panel" | "modal" | "route" | "none";
|
|
616
|
+
detailWidth?: "sm" | "md" | "lg";
|
|
617
|
+
titleField?: string;
|
|
618
|
+
subtitleField?: string;
|
|
619
|
+
/**
|
|
620
|
+
* Name a record from more than one field (`${first} ${last}`). Wins over
|
|
621
|
+
* `titleField` for the detail title and the card's accessible name.
|
|
622
|
+
*/
|
|
623
|
+
getTitle?: (item: T) => string;
|
|
624
|
+
/**
|
|
625
|
+
* The detail peek's subtitle, computed from the row ("2 staff", "Due
|
|
626
|
+
* Friday"). Wins over `subtitleField`, which can only show one column
|
|
627
|
+
* verbatim.
|
|
628
|
+
*/
|
|
629
|
+
getSubtitle?: (item: T) => string | undefined;
|
|
630
|
+
renderDetail?: (item: T) => ReactNode;
|
|
631
|
+
detailFields?: Array<{
|
|
632
|
+
key: string;
|
|
633
|
+
label: string;
|
|
634
|
+
render?: (value: unknown, item: T) => ReactNode;
|
|
635
|
+
}>;
|
|
636
|
+
/**
|
|
637
|
+
* Adds a status-chip filter. There is no dedicated status-change
|
|
638
|
+
* callback: chip clicks update the same filter state that already
|
|
639
|
+
* reports through `onFiltersChange`, whether or not the page is in
|
|
640
|
+
* server-controlled mode. A caller combining `statusFilter` with server
|
|
641
|
+
* pagination (`onPageChange` et al.) must supply `onFiltersChange` — see
|
|
642
|
+
* that prop — or status changes will update the chip UI without ever
|
|
643
|
+
* reaching the server. In server-controlled mode a status change also
|
|
644
|
+
* resets to page 1 via `onPageChange(1)` — see {@link onPageChange}.
|
|
645
|
+
*/
|
|
646
|
+
statusFilter?: string | {
|
|
647
|
+
field: string;
|
|
648
|
+
options?: string[];
|
|
649
|
+
};
|
|
650
|
+
searchFields?: string[];
|
|
651
|
+
searchPlaceholder?: string;
|
|
652
|
+
initialFilters?: FilterParam[];
|
|
653
|
+
onFiltersChange?: (filters: FilterParam[]) => void;
|
|
654
|
+
/**
|
|
655
|
+
* Extra multi-select filter comboboxes, handed straight to `DataFilters`
|
|
656
|
+
* and rendered beside the status chips. Each config owns its own
|
|
657
|
+
* `selected` array and `onChange` — ResourcePage never reads or writes
|
|
658
|
+
* them, because it cannot know what a caller's "Grade" or "Subject"
|
|
659
|
+
* combobox means against the row shape. That is deliberate rather than a
|
|
660
|
+
* gap: the built-in machinery (`searchFields`, `statusFilter`) covers the
|
|
661
|
+
* two shapes it *can* interpret, and this prop covers everything else by
|
|
662
|
+
* getting out of the way.
|
|
663
|
+
*
|
|
664
|
+
* Two consequences of that hands-off stance, stated rather than hidden:
|
|
665
|
+
* - These selections are invisible to `page.hasActiveFilters`, so the
|
|
666
|
+
* header's "Clear filters" affordance does not appear for them alone,
|
|
667
|
+
* and `onClearFilters` does not reset them. DataFilters' own "Clear all"
|
|
668
|
+
* does call each config's `onChange([])`, so wire real clearing there.
|
|
669
|
+
* - In client-side mode the rows are NOT re-filtered by these selections.
|
|
670
|
+
* A caller using them is expected to filter its own `data` (or ask the
|
|
671
|
+
* server to), exactly as it already does in server-controlled mode.
|
|
672
|
+
*/
|
|
673
|
+
filters?: DataFilterConfig[];
|
|
674
|
+
/** Current page, 1-based. Server-controlled mode only. */
|
|
675
|
+
page?: number;
|
|
676
|
+
/**
|
|
677
|
+
* Rows per page the server was asked for. Required whenever
|
|
678
|
+
* `onPageChange` is supplied: pager math (total pages, range start, Next)
|
|
679
|
+
* cannot be inferred from `data.length`, because a short final page is
|
|
680
|
+
* smaller than the real page size. Wins over the per-user page size
|
|
681
|
+
* persisted by `viewSettingsKey` — an explicit prop always beats a stored
|
|
682
|
+
* preference.
|
|
683
|
+
*
|
|
684
|
+
* Required whenever `onPageChange` is supplied. A short final page (the
|
|
685
|
+
* server's last page returning fewer rows than its page size) is
|
|
686
|
+
* indistinguishable from a small page size — there is no correct way to
|
|
687
|
+
* infer the divisor from the rows handed back. Rather than guess and
|
|
688
|
+
* render a wrong range or a stuck Next button, the component refuses to
|
|
689
|
+
* render the pager and shows an error instead when `onPageChange` is
|
|
690
|
+
* present without `perPage`.
|
|
691
|
+
*
|
|
692
|
+
* `total` is required on the same terms — see {@link total}. The error
|
|
693
|
+
* names every missing prop, not just the first.
|
|
694
|
+
*/
|
|
695
|
+
perPage?: number;
|
|
696
|
+
/**
|
|
697
|
+
* Called with the next 1-based page number. The pager only renders when
|
|
698
|
+
* this is supplied AND both `perPage` and `total` are supplied — see
|
|
699
|
+
* {@link perPage} and {@link total}. Supplying this without either one
|
|
700
|
+
* renders an error naming the missing props instead of a pager.
|
|
701
|
+
*
|
|
702
|
+
* Status changes reach the server through `onFiltersChange` — not a
|
|
703
|
+
* dedicated callback here — because `statusFilter` state is tracked by
|
|
704
|
+
* the same filter machinery that already calls `onFiltersChange`
|
|
705
|
+
* regardless of controlled mode (see {@link statusFilter}). In
|
|
706
|
+
* server-controlled mode a status change also calls `onPageChange(1)`
|
|
707
|
+
* directly, since the previous page number is meaningless against a new
|
|
708
|
+
* filtered result set.
|
|
709
|
+
*/
|
|
710
|
+
onPageChange?: (page: number) => void;
|
|
711
|
+
/** Called when the user picks a different page size in the header's view settings. */
|
|
712
|
+
onPerPageChange?: (perPage: number) => void;
|
|
713
|
+
/** Called when the user changes the sort, with `null` when the sort is cleared. */
|
|
714
|
+
onSortChange?: (sort: SortParam | null) => void;
|
|
715
|
+
/** Called when the user changes the search text (including clearing it). */
|
|
716
|
+
onSearchChange?: (search: string) => void;
|
|
717
|
+
/**
|
|
718
|
+
* Server-data mode: the caller's fetch is in flight, so render the shell's
|
|
719
|
+
* loading skeleton instead of the (as yet meaningless) `data`. Ignored in
|
|
720
|
+
* client-side mode, where `useResource`'s own loading state is used.
|
|
721
|
+
*/
|
|
722
|
+
loading?: boolean;
|
|
723
|
+
/**
|
|
724
|
+
* Server-data mode: the caller's fetch failed, so render the shell's error
|
|
725
|
+
* panel instead of an empty table. Takes effect even when `data` is
|
|
726
|
+
* non-empty — stale rows beside no indication of failure are exactly the
|
|
727
|
+
* silent-failure this prop exists to prevent. Ignored in client-side mode,
|
|
728
|
+
* where `useResource`'s own error state is used.
|
|
729
|
+
*/
|
|
730
|
+
error?: boolean;
|
|
731
|
+
/**
|
|
732
|
+
* Message shown in the error panel, in place of the shell's generic
|
|
733
|
+
* "Failed to load …". Forwarded in both modes — it is inert unless an
|
|
734
|
+
* error state is actually showing — so a client-side page can also give
|
|
735
|
+
* its failure a human sentence.
|
|
736
|
+
*/
|
|
737
|
+
errorMessage?: string;
|
|
738
|
+
/**
|
|
739
|
+
* Server-data mode: invoked by the error panel's Retry button. The button
|
|
740
|
+
* only appears when this is supplied, so a caller with no way to retry
|
|
741
|
+
* simply omits it and gets an error panel without a dead control. In
|
|
742
|
+
* client-side mode the button is already wired to `useResource`'s own
|
|
743
|
+
* `refetch` and this prop is ignored.
|
|
744
|
+
*/
|
|
745
|
+
onRetry?: () => void;
|
|
746
|
+
/** Saved filter views to offer. Pair with `useSavedViews` for per-user storage. */
|
|
747
|
+
savedViews?: SavedViewItem[];
|
|
748
|
+
onLoadView?: (filters: Record<string, unknown>) => void;
|
|
749
|
+
onSaveView?: (name: string) => void;
|
|
750
|
+
onDeleteView?: (id: string) => void;
|
|
751
|
+
/** Show the "save current view" affordance. */
|
|
752
|
+
canSaveViews?: boolean;
|
|
753
|
+
/**
|
|
754
|
+
* What the data source can do. A control it can't serve is hidden rather
|
|
755
|
+
* than left to error (DATA-STUDIO.md decision 9): `search: false` hides the
|
|
756
|
+
* search box, `sort: false` the sort select. Pass a provider's
|
|
757
|
+
* `capabilities()` straight in. Omitted, both show, as before.
|
|
758
|
+
*/
|
|
759
|
+
capabilities?: {
|
|
760
|
+
search?: boolean;
|
|
761
|
+
sort?: boolean;
|
|
762
|
+
};
|
|
763
|
+
/** Show the column-visibility dropdown. DataTable's default is `true`. */
|
|
764
|
+
columnVisibility?: boolean;
|
|
765
|
+
/** Persist key for the user's column-visibility picks. */
|
|
766
|
+
columnVisibilityKey?: string;
|
|
767
|
+
/** Allow drag-resizing columns. DataTable's default is `true`. */
|
|
768
|
+
resizableColumns?: boolean;
|
|
769
|
+
/** Persist key for the user's column widths. */
|
|
770
|
+
resizeKey?: string;
|
|
771
|
+
/** Keep the table header visible while scrolling. DataTable's default is `true`. */
|
|
772
|
+
stickyHeader?: boolean;
|
|
773
|
+
/** Keep the first column visible while scrolling horizontally. */
|
|
774
|
+
stickyFirstColumn?: boolean;
|
|
775
|
+
/**
|
|
776
|
+
* Page-size choices offered by `DataTable`'s own pager. Client-side mode
|
|
777
|
+
* only — server-controlled mode switches that pager off. (The header's
|
|
778
|
+
* view-settings popover has its own fixed 10/25/50/100 list.)
|
|
779
|
+
*/
|
|
780
|
+
pageSizeOptions?: number[];
|
|
781
|
+
onCreate?: () => void;
|
|
782
|
+
onEdit?: (item: T) => void;
|
|
783
|
+
/**
|
|
784
|
+
* Delete handler, shown as the detail peek's Delete button. Called
|
|
785
|
+
* directly, as before, unless `confirmDelete` is set — then it runs after
|
|
786
|
+
* the confirm dialog, which shows its error if the returned promise
|
|
787
|
+
* rejects, and closes the peek when it resolves.
|
|
788
|
+
*/
|
|
789
|
+
onDelete?: (item: T) => void | Promise<void>;
|
|
790
|
+
/**
|
|
791
|
+
* What the signed-in user may do: `can("create")`, `can("edit", item)`,
|
|
792
|
+
* `can("delete", item)`. Return false to hide the New button, or a row's
|
|
793
|
+
* Edit or Delete (and the built-in edit form won't open for it). Omitted,
|
|
794
|
+
* everything the handlers allow is offered. A UI gate only: the server
|
|
795
|
+
* must still refuse the write.
|
|
796
|
+
*/
|
|
797
|
+
can?: (action: ResourceAction, item?: T) => boolean;
|
|
798
|
+
/**
|
|
799
|
+
* @deprecated Use `can` — `can={(action, item) => !item || mayChange(item)}`.
|
|
800
|
+
* Removed in the next minor after one deprecation window. Still honoured:
|
|
801
|
+
* a row it refuses gets no Edit and no Delete.
|
|
802
|
+
*/
|
|
803
|
+
canEditItem?: (item: T) => boolean;
|
|
804
|
+
createLabel?: string;
|
|
805
|
+
/**
|
|
806
|
+
* @deprecated Use `can` — `can={(action, item) => action !== "edit" || !item || mayEdit(item)}`.
|
|
807
|
+
* Still honoured: a row it refuses gets no Edit.
|
|
808
|
+
*/
|
|
809
|
+
canEdit?: (item: T) => boolean;
|
|
810
|
+
/**
|
|
811
|
+
* @deprecated Use `can` — `can={(action, item) => action !== "delete" || !item || mayDelete(item)}`.
|
|
812
|
+
* Still honoured: a row it refuses gets no Delete.
|
|
813
|
+
*/
|
|
814
|
+
canDelete?: (item: T) => boolean;
|
|
815
|
+
/**
|
|
816
|
+
* Built-in delete for `resource` mode (ignored if `onDelete` is
|
|
817
|
+
* provided): the peek's Delete button opens a confirm dialog, then calls
|
|
818
|
+
* `useResource`'s `remove` with the row's `getKey` (when supplied) or its
|
|
819
|
+
* `id` — which invalidates the list — and closes the peek. A failure shows
|
|
820
|
+
* in the dialog.
|
|
821
|
+
*/
|
|
822
|
+
allowDelete?: boolean;
|
|
823
|
+
/**
|
|
824
|
+
* Confirm before deleting. Defaults to `true` for `allowDelete` and
|
|
825
|
+
* `false` for `onDelete` (which keeps its old, unconfirmed behaviour).
|
|
826
|
+
* Pass an object to set the dialog's copy.
|
|
827
|
+
*/
|
|
828
|
+
confirmDelete?: boolean | ResourceDeleteConfirmCopy<T>;
|
|
829
|
+
/** Called after a confirmed or built-in delete succeeds — e.g. a toast. */
|
|
830
|
+
onDeleted?: (item: T) => void;
|
|
831
|
+
/**
|
|
832
|
+
* Extra header actions beyond the create button, in one overflow menu.
|
|
833
|
+
* See {@link PageHeaderAction} — the budget is one primary, the rest here.
|
|
834
|
+
*/
|
|
835
|
+
secondaryActions?: PageHeaderAction[];
|
|
836
|
+
/** Enable built-in create form (ignored if onCreate callback is provided). */
|
|
837
|
+
allowCreate?: boolean;
|
|
838
|
+
/** Enable built-in edit form (ignored if onEdit callback is provided). */
|
|
839
|
+
allowEdit?: boolean;
|
|
840
|
+
/** Field definitions for the built-in form. */
|
|
841
|
+
formFields?: ResourceFormFieldDef[];
|
|
842
|
+
/** Custom form renderer — receives form instance and resolved fields. */
|
|
843
|
+
renderForm?: (form: UseFormReturn<Record<string, unknown>>, fields: ResourceFormFieldDef[]) => ReactNode;
|
|
844
|
+
/** Form presentation mode. Default "standard". */
|
|
845
|
+
formMode?: "standard" | "wizard";
|
|
846
|
+
/** Wizard step definitions. Required when formMode is "wizard". */
|
|
847
|
+
formSteps?: WizardStep[];
|
|
848
|
+
/** Layout mode for the built-in form container. If not set, defaults to sheet for edit, dialog for create. */
|
|
849
|
+
formLayout?: "dialog" | "sheet" | "page";
|
|
850
|
+
/**
|
|
851
|
+
* When the built-in form's saves and deletes reach the list: `"pessimistic"`
|
|
852
|
+
* (default, wait for the provider), `"optimistic"` or `"undoable"` (an Undo
|
|
853
|
+
* toast). See ResourceForm `mutationMode`. `resourceOptions.mutationMode`
|
|
854
|
+
* wins for deletes.
|
|
855
|
+
*/
|
|
856
|
+
mutationMode?: MutationMode;
|
|
857
|
+
bulkActions?: Array<{
|
|
858
|
+
label: string;
|
|
859
|
+
icon?: LucideIcon;
|
|
860
|
+
variant?: "default" | "destructive";
|
|
861
|
+
onAction: (ids: string[]) => void | Promise<void>;
|
|
862
|
+
/**
|
|
863
|
+
* Forwarded straight to the rendered bulk button (see `BulkAction`).
|
|
864
|
+
* Exists so a consumer can kill every bulk action while one of them is
|
|
865
|
+
* in flight — an early return inside `onAction` blocks the second call
|
|
866
|
+
* but leaves the button looking live, which misstates what it will do.
|
|
867
|
+
*/
|
|
868
|
+
disabled?: boolean;
|
|
869
|
+
/** `data-testid` on the rendered bulk button, so e2e can drive it. */
|
|
870
|
+
testId?: string;
|
|
871
|
+
}>;
|
|
872
|
+
/**
|
|
873
|
+
* Override the default card renderer used for grid view (and, absent a
|
|
874
|
+
* dedicated list layout, list view too — DataGrid falls back to the same
|
|
875
|
+
* renderer there). ResourcePage still owns data-fetching, filtering,
|
|
876
|
+
* sorting, and layout (grid columns, infinite-scroll batching, stagger
|
|
877
|
+
* animation) — this only swaps what's rendered inside each item slot.
|
|
878
|
+
*
|
|
879
|
+
* Receives `ResourceCardActions`: the row's `update`/`remove`/`refetch`
|
|
880
|
+
* plus `open()` to activate the row (opening the configured detail
|
|
881
|
+
* panel/modal/route). Row activation is *not* applied automatically to a
|
|
882
|
+
* custom card — wrapping it would double-fire against cards that bring
|
|
883
|
+
* their own click handling and would hijack clicks on nested buttons — so
|
|
884
|
+
* call `actions.open()` from wherever your card should activate.
|
|
885
|
+
*/
|
|
886
|
+
renderCard?: (item: T, index: number, actions: ResourceCardActions<T>) => ReactNode;
|
|
887
|
+
/**
|
|
888
|
+
* Replace the built-in `DataTable` that table view renders, keeping every
|
|
889
|
+
* other piece of the page — header, counts, view settings, filters,
|
|
890
|
+
* bulk bar, detail panel, form, pager — exactly as it is. The counterpart
|
|
891
|
+
* to `renderCard`, which does the same for grid and list view.
|
|
892
|
+
*
|
|
893
|
+
* Receives the rows for the current view, which in server-controlled mode
|
|
894
|
+
* is precisely the page the server returned. Ownership is total: row
|
|
895
|
+
* click-through, selection, density, column visibility and resizing are
|
|
896
|
+
* all things the built-in table wires for you and a custom table must
|
|
897
|
+
* wire for itself. That is the trade this prop makes, and it is why
|
|
898
|
+
* `renderCard` remains the lighter option when only the *cells* need to
|
|
899
|
+
* change — a `ResourceColumnDef.render` is lighter still.
|
|
900
|
+
*
|
|
901
|
+
* Omitted, the default `DataTable` render is used unchanged.
|
|
902
|
+
*/
|
|
903
|
+
renderTable?: (items: T[]) => ReactNode;
|
|
904
|
+
/**
|
|
905
|
+
* With a custom `renderCard`: make each whole card (and list row) one
|
|
906
|
+
* keyboard-reachable button that opens the detail, so the card needs no
|
|
907
|
+
* `actions.open()` wiring and no hand-rolled `div role="button"`. Links and
|
|
908
|
+
* buttons inside the card keep their own clicks. Off by default, because
|
|
909
|
+
* an existing card that already calls `open()` would fire it twice.
|
|
910
|
+
*/
|
|
911
|
+
openOnCardClick?: boolean;
|
|
912
|
+
/**
|
|
913
|
+
* Tailwind grid-column classes for the card grid, passed through to
|
|
914
|
+
* `DataGrid`'s `gridCols` — e.g. `"grid-cols-1 sm:grid-cols-2 lg:grid-cols-3"`.
|
|
915
|
+
* Defaults to DataGrid's own responsive 1/2/3/4-column ramp.
|
|
916
|
+
*/
|
|
917
|
+
gridCols?: string;
|
|
918
|
+
/**
|
|
919
|
+
* Built-in list keyboard navigation via `useKeyboardNavigation`: j/k (or
|
|
920
|
+
* Arrow Up/Down) move a focus cursor, Enter opens the focused item,
|
|
921
|
+
* Backspace (⌫) closes an open detail view, and x toggles the focused
|
|
922
|
+
* item's selection when `bulkActions` are configured. Set to `false` to
|
|
923
|
+
* opt out.
|
|
924
|
+
*
|
|
925
|
+
* Default-on, and scoped so that being on by default stays safe:
|
|
926
|
+
* - The j/k cursor runs while the grid or list view is showing. Table view
|
|
927
|
+
* keeps its own sort/pagination, so there `DataTable` handles the keys on
|
|
928
|
+
* its focusable rows instead (↑/↓ or j/k, Home/End, Enter, x); this prop
|
|
929
|
+
* turns that off too.
|
|
930
|
+
* - Cursor keys suspend while a detail panel/modal or the built-in form
|
|
931
|
+
* is open, so they can't drive the list behind the overlay; ⌫ stays
|
|
932
|
+
* live there so it can close it.
|
|
933
|
+
* - Backspace is only intercepted while there's an open detail to close.
|
|
934
|
+
* - Modified chords (Ctrl/Cmd/Alt) are never captured, so an app's
|
|
935
|
+
* Cmd+K-style shortcuts keep working.
|
|
936
|
+
*/
|
|
937
|
+
keyboardNavigation?: boolean;
|
|
938
|
+
toolbar?: ReactNode;
|
|
939
|
+
/** The empty state's action, e.g. the create button. */
|
|
940
|
+
emptyState?: ReactNode;
|
|
941
|
+
/** The empty state's icon. */
|
|
942
|
+
emptyIcon?: LucideIcon;
|
|
943
|
+
/** The empty state's title, e.g. "No tasks yet". Default "No results found". */
|
|
944
|
+
emptyMessage?: string;
|
|
945
|
+
/** A sentence under the empty-state title — forwarded to `DataPageShell`. */
|
|
946
|
+
emptyDescription?: string;
|
|
947
|
+
/**
|
|
948
|
+
* `"page"` (default) renders the full header with the title. `"toolbar"`
|
|
949
|
+
* keeps the header's actions (count, view toggle, settings, create) and
|
|
950
|
+
* drops the title block — for a list inside a tab or card whose page
|
|
951
|
+
* already has the heading. See `DataPageShell`'s `header`.
|
|
952
|
+
*/
|
|
953
|
+
header?: "page" | "toolbar";
|
|
954
|
+
/**
|
|
955
|
+
* Imperative navigation used only when `detail="route"` (navigates to
|
|
956
|
+
* `/{resource}/{id}` on row click). Defaults to a full-page navigation.
|
|
957
|
+
* Router consumers pass their navigate fn — e.g. RR `useNavigate()` or
|
|
958
|
+
* Next `router.push`.
|
|
959
|
+
*/
|
|
960
|
+
onNavigate?: NavigateFn;
|
|
961
|
+
resourceOptions?: UseResourceOptions<T>;
|
|
962
|
+
/**
|
|
963
|
+
* Keep the list's state in the URL: the open record (`?id=`), the search
|
|
964
|
+
* (`q`), the sort (`sort=field:asc`) and, with `pagination="server"`, the
|
|
965
|
+
* page. Pass your router's search-params adapter
|
|
966
|
+
* (`useReactRouterSearchParams()`, `useTanStackRouterSearchParams()`, or
|
|
967
|
+
* `useBrowserSearchParams()` with no router), or `{ searchParams, params }`
|
|
968
|
+
* to rename the keys. A copied link reopens the same record, and Back
|
|
969
|
+
* closes one that was opened. Search and sort are read when the page
|
|
970
|
+
* mounts and written as they change. A server-controlled page owns its
|
|
971
|
+
* own page, sort and search: use `useResourceUrlState` there.
|
|
972
|
+
*/
|
|
973
|
+
urlState?: SearchParamsAdapter | ResourceUrlStateOptions;
|
|
974
|
+
/**
|
|
975
|
+
* The view shown until the user picks one with the view toggle. Default
|
|
976
|
+
* `"table"`. Pass `{ mobile: "grid", desktop: "table" }` for cards on
|
|
977
|
+
* phones and a table on desktops. Only an explicit pick is persisted, so
|
|
978
|
+
* the responsive default stays responsive until the user chooses.
|
|
979
|
+
*/
|
|
980
|
+
defaultView?: ViewModeDefault;
|
|
981
|
+
/**
|
|
982
|
+
* Persist key for density / page size via `useViewSettings`.
|
|
983
|
+
* Defaults to `resource` or a slug of `title`.
|
|
984
|
+
*/
|
|
985
|
+
viewSettingsKey?: string;
|
|
986
|
+
/**
|
|
987
|
+
* The interior parts `ResourcePage` composes, by name. Replace one with your
|
|
988
|
+
* own component, or pass `null` to eject it entirely — see
|
|
989
|
+
* {@link ResourcePageSlots}.
|
|
990
|
+
*
|
|
991
|
+
* This is the reason a surface that needs one part to differ does not have
|
|
992
|
+
* to drop to the primitives and re-derive the pagination, selection and
|
|
993
|
+
* detail wiring that were already correct.
|
|
994
|
+
*/
|
|
995
|
+
slots?: ResourcePageSlots<T>;
|
|
996
|
+
/** Extra props merged into a part you are KEEPING. Wins over ours. */
|
|
997
|
+
slotProps?: ResourcePageSlotProps<T>;
|
|
998
|
+
/** Merged onto the page shell's root. */
|
|
999
|
+
className?: string;
|
|
1000
|
+
}
|
|
1001
|
+
/**
|
|
1002
|
+
* A list page for a resource: header, filters, table or cards, paging,
|
|
1003
|
+
* the open record's panel, and the built-in form and confirmed delete.
|
|
1004
|
+
* {@link useResourceTable} is the same behaviour without this markup.
|
|
1005
|
+
*/
|
|
1006
|
+
declare function ResourcePage<T extends Record<string, unknown> = Record<string, unknown>>(props: ResourcePageProps<T>): React.JSX.Element;
|
|
1007
|
+
|
|
1008
|
+
/**
|
|
1009
|
+
* What {@link useResourceTable} returns: the list's rows and the handlers a
|
|
1010
|
+
* custom layout wires to its controls.
|
|
1011
|
+
*
|
|
1012
|
+
* @stability Beta
|
|
1013
|
+
*/
|
|
1014
|
+
interface ResourceTableState<T extends Record<string, unknown> = Record<string, unknown>> {
|
|
1015
|
+
/**
|
|
1016
|
+
* The rows to show: filtered and sorted. With `pagination="client"` this
|
|
1017
|
+
* is every matching row (page it yourself); with server paging it is the
|
|
1018
|
+
* current page.
|
|
1019
|
+
*/
|
|
1020
|
+
rows: T[];
|
|
1021
|
+
/** How many rows match, across all pages. */
|
|
1022
|
+
total: number;
|
|
1023
|
+
loading: boolean;
|
|
1024
|
+
/** The read failed. */
|
|
1025
|
+
error: boolean;
|
|
1026
|
+
/** True when more than `clientRowLimit` rows exist and only that many were loaded. */
|
|
1027
|
+
truncated: boolean;
|
|
1028
|
+
/** The columns, resolved to DataTable's format. */
|
|
1029
|
+
columns: DataTableColumn<T>[];
|
|
1030
|
+
/** Write actions for cell renderers (update, remove, …). */
|
|
1031
|
+
actions: ResourceActions<T>;
|
|
1032
|
+
/** A row's identity: `getKey`, else its `id`. */
|
|
1033
|
+
getKey: (item: T) => string;
|
|
1034
|
+
/** A row's title: `getTitle`, else its title field. */
|
|
1035
|
+
getTitle: (item: T) => string;
|
|
1036
|
+
/** The page's permission check (`can`, then the deprecated per-row predicates). */
|
|
1037
|
+
can: (action: ResourceAction, item?: T) => boolean;
|
|
1038
|
+
search: string;
|
|
1039
|
+
setSearch: (value: string) => void;
|
|
1040
|
+
/** Sort keys, primary first. */
|
|
1041
|
+
sort: DataTableSort[];
|
|
1042
|
+
setSort: (sort: DataTableSort[]) => void;
|
|
1043
|
+
/** The status filter's choices, its selected values and its setter, when the list has one. */
|
|
1044
|
+
statusFilter: {
|
|
1045
|
+
options: string[];
|
|
1046
|
+
selected: string[];
|
|
1047
|
+
onChange: (values: string[]) => void;
|
|
1048
|
+
} | undefined;
|
|
1049
|
+
hasActiveFilters: boolean;
|
|
1050
|
+
clearFilters: () => void;
|
|
1051
|
+
/** The current page, from 1. */
|
|
1052
|
+
page: number;
|
|
1053
|
+
pageCount: number;
|
|
1054
|
+
pageSize: number;
|
|
1055
|
+
setPageSize: (size: number) => void;
|
|
1056
|
+
/** Go to a page; undefined when the page is not controlled (client paging). */
|
|
1057
|
+
setPage: ((page: number) => void) | undefined;
|
|
1058
|
+
/** The 1-based range of the rows shown, for "21–40 of 95"; 0 when empty. */
|
|
1059
|
+
rangeFrom: number;
|
|
1060
|
+
rangeTo: number;
|
|
1061
|
+
selectedIds: Set<string>;
|
|
1062
|
+
toggleSelected: (id: string) => void;
|
|
1063
|
+
toggleAll: () => void;
|
|
1064
|
+
clearSelection: () => void;
|
|
1065
|
+
/** The open row, kept current as the data changes. */
|
|
1066
|
+
openItem: T | null;
|
|
1067
|
+
detailOpen: boolean;
|
|
1068
|
+
/** What clicking a row does: opens the peek, modal or route. */
|
|
1069
|
+
openRow: (item: T) => void;
|
|
1070
|
+
closeDetail: () => void;
|
|
1071
|
+
/** A record linked by URL that does not exist (or was deleted). */
|
|
1072
|
+
openItemMissing: boolean;
|
|
1073
|
+
/** Undefined when the page cannot create. */
|
|
1074
|
+
create: (() => void) | undefined;
|
|
1075
|
+
edit: (item: T) => void;
|
|
1076
|
+
/** The built-in form's state, for rendering your own form. */
|
|
1077
|
+
form: {
|
|
1078
|
+
open: boolean;
|
|
1079
|
+
setOpen: (open: boolean) => void;
|
|
1080
|
+
action: "create" | "edit";
|
|
1081
|
+
/** The row being edited; null when creating. */
|
|
1082
|
+
item: T | null;
|
|
1083
|
+
};
|
|
1084
|
+
/** Delete a row: asks first when the delete is confirmed. Undefined when the page cannot delete. */
|
|
1085
|
+
remove: ((item: T) => void) | undefined;
|
|
1086
|
+
/** The confirm step's state, when `remove` asks first. */
|
|
1087
|
+
deleteConfirm: {
|
|
1088
|
+
open: boolean;
|
|
1089
|
+
setOpen: (open: boolean) => void;
|
|
1090
|
+
/** The row being confirmed (kept through the close animation). */
|
|
1091
|
+
item: T | null;
|
|
1092
|
+
/** Run the delete; rejects with the failure. */
|
|
1093
|
+
confirm: () => Promise<void>;
|
|
1094
|
+
/** "recycle" when the provider soft-deletes, so the copy can say it can be restored. */
|
|
1095
|
+
kind: "recycle" | "permanent";
|
|
1096
|
+
copy: ResourceDeleteConfirmCopy<T> | undefined;
|
|
1097
|
+
};
|
|
1098
|
+
}
|
|
1099
|
+
/**
|
|
1100
|
+
* The list behind {@link ResourcePage}, without any markup: where the rows
|
|
1101
|
+
* come from (a `resource` through the data provider, or `data` you pass),
|
|
1102
|
+
* search, sort, status filter and paging, selection, the open record (and
|
|
1103
|
+
* its URL state), the built-in create/edit form and confirmed delete, and
|
|
1104
|
+
* the row handlers. Takes the same options as `ResourcePage`.
|
|
1105
|
+
*
|
|
1106
|
+
* Use it to build a different layout over the same behaviour; `ResourcePage`
|
|
1107
|
+
* itself is this behaviour plus Genesis's markup.
|
|
1108
|
+
*
|
|
1109
|
+
* @stability Beta
|
|
1110
|
+
*/
|
|
1111
|
+
declare function useResourceTable<T extends Record<string, unknown> = Record<string, unknown>>(props: ResourcePageProps<T>): ResourceTableState<T>;
|
|
1112
|
+
|
|
1113
|
+
export { type BulkAction as B, ConfirmDeleteDialog as C, DataBulkBar as D, FilterCombobox as F, type ResourceTableState as R, type SavedViewItem as S, type ConfirmDeleteDialogProps as a, type DataFilterConfig as b, DataFilters as c, DataGrid as d, DataPageShell as e, type DataPageShellProps as f, DetailPanel as g, type DetailPanelProps as h, type FilterOption as i, type ResourceAction as j, type ResourceCardActions as k, type ResourceDeleteConfirmCopy as l, ResourceForm as m, type ResourceFormProps as n, ResourcePage as o, type ResourcePageProps as p, type ResourcePageSlotMap as q, type ResourcePageSlotProps as r, type ResourcePageSlots as s, type StatusChipOption as t, useResourceTable as u, type StatusChipsVariant as v };
|