@marktiderman/genesis-ui 2.0.0 → 2.2.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/LICENSE +21 -0
- package/dist/{AppShell-BJarYZzz.d.ts → AppShell-sQT0UbXg.d.ts} +4 -4
- package/dist/{chunk-Q4I4SFBD.js → chunk-GDFNAHI4.js} +9 -5
- package/dist/chunk-GDFNAHI4.js.map +1 -0
- package/dist/components/data/index.d.ts +230 -27
- package/dist/components/data/index.js +253 -27
- package/dist/components/data/index.js.map +1 -1
- package/dist/components/layout/index.d.ts +2 -3
- package/dist/components/layout/index.js +4 -4
- package/dist/components/patterns/empty-state.d.ts +4 -5
- package/dist/components/patterns/form-field.d.ts +3 -4
- package/dist/components/patterns/index.d.ts +2 -3
- package/dist/components/patterns/index.js +3 -3
- package/dist/components/patterns/page-loading.d.ts +2 -2
- package/dist/components/patterns/settings-row.d.ts +1 -2
- package/dist/components/patterns/status-badge.d.ts +1 -2
- package/dist/components/patterns/toggle-row.d.ts +1 -2
- package/dist/components/patterns/user-avatar.d.ts +2 -2
- package/dist/components/ui/accessible-icon.d.ts +1 -2
- package/dist/components/ui/alert-dialog.d.ts +2 -3
- package/dist/components/ui/badge.d.ts +1 -2
- package/dist/components/ui/breadcrumb.d.ts +2 -3
- package/dist/components/ui/button-group.d.ts +1 -2
- package/dist/components/ui/calendar.d.ts +2 -3
- package/dist/components/ui/card.d.ts +6 -7
- package/dist/components/ui/carousel.d.ts +2 -3
- package/dist/components/ui/chart.d.ts +4 -5
- package/dist/components/ui/command-palette.d.ts +1 -2
- package/dist/components/ui/command.d.ts +2 -3
- package/dist/components/ui/context-menu.d.ts +1 -2
- package/dist/components/ui/date-picker.d.ts +2 -2
- package/dist/components/ui/dialog.d.ts +2 -3
- package/dist/components/ui/drawer.d.ts +3 -4
- package/dist/components/ui/dropdown-menu.d.ts +1 -2
- package/dist/components/ui/kbd.d.ts +1 -2
- package/dist/components/ui/menubar.d.ts +1 -2
- package/dist/components/ui/pagination.d.ts +5 -6
- package/dist/components/ui/password-toggle-field.d.ts +2 -3
- package/dist/components/ui/progress.d.ts +1 -2
- package/dist/components/ui/resizable.d.ts +2 -3
- package/dist/components/ui/separator.d.ts +1 -2
- package/dist/components/ui/sheet.d.ts +2 -3
- package/dist/components/ui/skeleton.d.ts +1 -2
- package/dist/components/ui/sonner.d.ts +1 -2
- package/dist/components/ui/spinner.d.ts +1 -2
- package/dist/components/ui/tooltip.d.ts +1 -2
- package/dist/components/ui/typography.d.ts +9 -10
- package/dist/hooks/index.d.ts +2 -3
- package/dist/hooks/index.js +1 -1
- package/dist/hooks/use-resource-page.d.ts +2 -3
- package/dist/hooks/use-resource-page.js +1 -1
- package/dist/hooks/use-view-preference.d.ts +2 -2
- package/dist/index.d.ts +0 -1
- package/dist/index.js +11 -11
- package/dist/{use-resource-page-CPs6wjKt.d.ts → use-resource-page-DTQS011Q.d.ts} +18 -6
- package/dist/{view-settings--KXNT_jE.d.ts → view-settings-kHKCHMjj.d.ts} +2 -2
- package/dist/{view-toggle-DDe_Luj2.d.ts → view-toggle-CahQ87PW.d.ts} +2 -2
- package/package.json +15 -15
- package/dist/chunk-Q4I4SFBD.js.map +0 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mark Tiderman
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as React from 'react';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
3
|
import { LucideIcon } from 'lucide-react';
|
|
4
|
-
import { V as ViewMode } from './view-toggle-
|
|
4
|
+
import { V as ViewMode } from './view-toggle-CahQ87PW.js';
|
|
5
5
|
import { Density } from './hooks/use-view-settings.js';
|
|
6
6
|
import { L as LinkComponent } from './navigation-CpOEyUSb.js';
|
|
7
7
|
|
|
@@ -65,7 +65,7 @@ interface PageHeaderProps {
|
|
|
65
65
|
onDensityChange?: (density: Density) => void;
|
|
66
66
|
createAction?: ReactNode;
|
|
67
67
|
}
|
|
68
|
-
declare function PageHeader({ title, subtitle, icon: Icon, options: optionsProp, optionsStorageKey, actions, details, detailsOpen, onDetailsOpenChange, detailsActive, detailsHint, count, totalCount, entityName, countActive, viewMode, onViewModeChange, pageSize, onPageSizeChange, density, onDensityChange, createAction, }: PageHeaderProps):
|
|
68
|
+
declare function PageHeader({ title, subtitle, icon: Icon, options: optionsProp, optionsStorageKey, actions, details, detailsOpen, onDetailsOpenChange, detailsActive, detailsHint, count, totalCount, entityName, countActive, viewMode, onViewModeChange, pageSize, onPageSizeChange, density, onDensityChange, createAction, }: PageHeaderProps): React.JSX.Element;
|
|
69
69
|
|
|
70
70
|
interface NavItem {
|
|
71
71
|
to: string;
|
|
@@ -193,6 +193,6 @@ interface AppShellProps {
|
|
|
193
193
|
*/
|
|
194
194
|
isActive?: (to: string, activePath?: string) => boolean;
|
|
195
195
|
}
|
|
196
|
-
declare function AppShell({ appName, appLogo, logo, navItems, secondaryNavItems, secondaryNavLabel, mobileNavItems, headerSlot, collapsedHeaderSlot, footer, userSlot, children, showThemeToggle: showThemeToggleProp, options: optionsProp, optionsStorageKey, collapsed: collapsedProp, onCollapsedChange, linkComponent, activePath, isActive, }: AppShellProps):
|
|
196
|
+
declare function AppShell({ appName, appLogo, logo, navItems, secondaryNavItems, secondaryNavLabel, mobileNavItems, headerSlot, collapsedHeaderSlot, footer, userSlot, children, showThemeToggle: showThemeToggleProp, options: optionsProp, optionsStorageKey, collapsed: collapsedProp, onCollapsedChange, linkComponent, activePath, isActive, }: AppShellProps): React.JSX.Element;
|
|
197
197
|
|
|
198
198
|
export { AppShell as A, type NavItem as N, PageHeader as P, type AppShellOptions as a, type AppShellProps as b, type PageHeaderOptions as c, type PageHeaderProps as d };
|
|
@@ -77,7 +77,8 @@ function useResourcePage(props) {
|
|
|
77
77
|
titleField: titleFieldProp,
|
|
78
78
|
subtitleField: subtitleFieldProp,
|
|
79
79
|
initialFilters,
|
|
80
|
-
onFiltersChange
|
|
80
|
+
onFiltersChange,
|
|
81
|
+
controlled = false
|
|
81
82
|
} = props;
|
|
82
83
|
const [viewMode, setViewMode] = useViewPreference(
|
|
83
84
|
resource ?? "resource-page",
|
|
@@ -136,6 +137,7 @@ function useResourcePage(props) {
|
|
|
136
137
|
[searchFieldsProp, rawData]
|
|
137
138
|
);
|
|
138
139
|
const filteredData = useMemo(() => {
|
|
140
|
+
if (controlled) return rawData;
|
|
139
141
|
let result = rawData;
|
|
140
142
|
if (search) {
|
|
141
143
|
result = result.filter(
|
|
@@ -152,7 +154,7 @@ function useResourcePage(props) {
|
|
|
152
154
|
result = [...result].sort((a, b) => compareItems(a, b, sort));
|
|
153
155
|
}
|
|
154
156
|
return result;
|
|
155
|
-
}, [rawData, search, searchFields, statusFieldName, statusValue, sort]);
|
|
157
|
+
}, [controlled, rawData, search, searchFields, statusFieldName, statusValue, sort]);
|
|
156
158
|
const sortOptions = useMemo(() => {
|
|
157
159
|
const opts = [];
|
|
158
160
|
for (const col of resolvedColumns) {
|
|
@@ -258,7 +260,9 @@ function useResourcePage(props) {
|
|
|
258
260
|
);
|
|
259
261
|
return {
|
|
260
262
|
data: filteredData,
|
|
261
|
-
|
|
263
|
+
// Controlled mode: `filteredData` is one server page, so its length is
|
|
264
|
+
// not the record count — the server's total is.
|
|
265
|
+
total: controlled ? rawTotal : filteredData.length,
|
|
262
266
|
columns: resolvedColumns,
|
|
263
267
|
tableColumns,
|
|
264
268
|
filterConfig,
|
|
@@ -284,5 +288,5 @@ function useResourcePage(props) {
|
|
|
284
288
|
}
|
|
285
289
|
|
|
286
290
|
export { titleCase, useResourcePage };
|
|
287
|
-
//# sourceMappingURL=chunk-
|
|
288
|
-
//# sourceMappingURL=chunk-
|
|
291
|
+
//# sourceMappingURL=chunk-GDFNAHI4.js.map
|
|
292
|
+
//# sourceMappingURL=chunk-GDFNAHI4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/hooks/use-resource-page.ts"],"names":[],"mappings":"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","file":"chunk-GDFNAHI4.js","sourcesContent":["\"use client\";\n\nimport { useState, useMemo, useCallback, useEffect, useRef } from \"react\";\nimport type { ReactNode } from \"react\";\nimport type { DataTableColumn } from \"../components/data/DataTable\";\nimport type { DataFilterConfig } from \"../components/data/DataFilters\";\nimport type { ViewMode } from \"../components/patterns/view-toggle\";\nimport type { Identifier, FilterParam, SortParam } from \"@marktiderman/genesis-core/provider\";\nimport { useViewPreference } from \"./use-view-preference\";\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\n/** Actions available inside column render functions. */\nexport interface ResourceActions<T> {\n update: (id: Identifier, data: Partial<T>) => Promise<T>;\n remove: (id: Identifier) => Promise<void>;\n refetch: () => void;\n}\n\n/** Column definition — full config form. */\nexport interface ResourceColumnDef<T> {\n key: string;\n header?: string;\n sortable?: boolean;\n hideBelow?: \"sm\" | \"md\" | \"lg\" | \"xl\";\n render?: (\n item: T,\n value: unknown,\n actions: ResourceActions<T>,\n ) => ReactNode;\n}\n\nexport interface UseResourcePageProps<T> {\n /** Resource name — used for view-preference storage key. */\n resource?: string;\n /** The data to display (pre-fetched or from useResource). */\n data: T[];\n /** Total count of records (before client-side filtering). */\n total: number;\n /** Column definitions. Strings are expanded to `{ key, header, sortable }`. Omit to auto-generate from data keys. */\n columns?: Array<string | ResourceColumnDef<T>>;\n /** Status filter config. String = field name (options auto-derived). Object = explicit field + options. */\n statusFilter?: string | { field: string; options?: string[] };\n /** Fields to search across. Defaults to all string-valued fields in data. */\n searchFields?: string[];\n /** Initial filter params applied on mount. */\n initialFilters?: FilterParam[];\n /** Callback when filters change. */\n onFiltersChange?: (filters: FilterParam[]) => void;\n /** Default view mode. */\n defaultView?: ViewMode;\n /** Detail interaction mode. */\n detail?: \"panel\" | \"modal\" | \"route\" | \"none\";\n /** Field used for the detail title. Defaults to first column key. */\n titleField?: string;\n /** Field used for the detail subtitle. Defaults to second column key. */\n subtitleField?: string;\n /**\n * Server-controlled mode. The `data` handed in has ALREADY been searched,\n * sorted and paginated by the server, so this hook must not do any of it a\n * second time — `data` is returned untouched and `total` is the server's\n * `total` rather than the length of a client-filtered slice.\n *\n * Search / sort / status state is still tracked (the filter UI stays\n * controlled and `onFiltersChange` still fires) — only the client-side\n * transform of `data` is skipped. Defaults to `false`, so every existing\n * caller keeps its current client-side behavior.\n */\n controlled?: boolean;\n}\n\nexport interface UseResourcePageReturn<T> {\n /** Filtered and sorted data for display. */\n data: T[];\n /** Total count (of the filtered set; the server's `total` when `controlled`). */\n total: number;\n /** Resolved column definitions. */\n columns: ResourceColumnDef<T>[];\n /** Columns mapped to DataTable format. */\n tableColumns: DataTableColumn<T>[];\n /** Ready-to-spread filter config for DataFilters. */\n filterConfig: {\n search: string;\n onSearchChange: (value: string) => void;\n sort: string;\n onSortChange: (sort: string) => void;\n sortOptions: Array<{ value: string; label: string }>;\n statusChips?: {\n options: string[];\n selected: string[];\n onChange: (values: string[]) => void;\n };\n };\n viewMode: ViewMode;\n setViewMode: (mode: ViewMode) => void;\n search: string;\n setSearch: (value: string) => void;\n sort: SortParam | null;\n setSort: (sort: SortParam | null) => void;\n statusValue: string[];\n setStatusValue: (values: string[]) => void;\n selectedItem: T | null;\n setSelectedItem: (item: T | null) => void;\n detailOpen: boolean;\n setDetailOpen: (open: boolean) => void;\n handleRowClick: (item: T) => void;\n titleField: string;\n subtitleField: string;\n statusOptions: string[];\n hasActiveFilters: boolean;\n clearFilters: () => void;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Convert a key string to title case.\n * \"created_at\" → \"Created At\"\n * \"firstName\" → \"First Name\"\n */\nexport function titleCase(str: string): string {\n // Split on underscores, hyphens, and camelCase boundaries\n const words = str\n .replace(/([a-z])([A-Z])/g, \"$1 $2\") // camelCase → camel Case\n .replace(/[_-]/g, \" \") // snake_case / kebab-case → spaces\n .split(/\\s+/)\n .filter(Boolean);\n\n return words\n .map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())\n .join(\" \");\n}\n\n/**\n * Resolve column shorthand into full ResourceColumnDef objects.\n * - undefined + data present → auto-generate from Object.keys(data[0]), skip \"id\"\n * - undefined + empty data → empty array\n * - string items → { key, header: titleCase(key), sortable: true }\n * - object items → defaults applied\n */\nfunction resolveColumns<T>(\n columns: Array<string | ResourceColumnDef<T>> | undefined,\n data: T[],\n): ResourceColumnDef<T>[] {\n if (columns === undefined) {\n if (data.length === 0) return [];\n const keys = Object.keys(data[0] as Record<string, unknown>).filter(\n (k) => k !== \"id\",\n );\n return keys.map((key) => ({\n key,\n header: titleCase(key),\n sortable: true,\n }));\n }\n\n return columns.map((col) => {\n if (typeof col === \"string\") {\n return { key: col, header: titleCase(col), sortable: true };\n }\n return {\n ...col,\n header: col.header ?? titleCase(col.key),\n sortable: col.sortable ?? true,\n };\n });\n}\n\n/**\n * Derive the list of string fields in a data set (for default search).\n */\nfunction deriveStringFields<T>(data: T[]): string[] {\n if (data.length === 0) return [];\n const first = data[0] as Record<string, unknown>;\n return Object.keys(first).filter((k) => typeof first[k] === \"string\");\n}\n\n/**\n * Derive unique status options from a data array.\n */\nfunction deriveStatusOptions<T>(data: T[], field: string): string[] {\n const values = new Set<string>();\n for (const item of data) {\n const val = (item as Record<string, unknown>)[field];\n if (val != null) values.add(String(val));\n }\n return Array.from(values).sort();\n}\n\n/**\n * Client-side text search across multiple fields.\n */\nfunction matchesSearch<T>(\n item: T,\n query: string,\n fields: string[],\n): boolean {\n if (!query) return true;\n const lower = query.toLowerCase();\n const record = item as Record<string, unknown>;\n return fields.some((f) => {\n const val = record[f];\n return val != null && String(val).toLowerCase().includes(lower);\n });\n}\n\n/**\n * Client-side sort comparator.\n */\nfunction compareItems<T>(a: T, b: T, sort: SortParam): number {\n const ra = a as Record<string, unknown>;\n const rb = b as Record<string, unknown>;\n const va = ra[sort.field];\n const vb = rb[sort.field];\n const sa = va == null ? \"\" : String(va);\n const sb = vb == null ? \"\" : String(vb);\n const cmp = sa.localeCompare(sb, undefined, {\n numeric: true,\n sensitivity: \"base\",\n });\n return sort.order === \"asc\" ? cmp : -cmp;\n}\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\nexport function useResourcePage<T = Record<string, unknown>>(\n props: UseResourcePageProps<T>,\n): UseResourcePageReturn<T> {\n const {\n resource,\n data: rawData,\n total: rawTotal,\n columns: columnsProp,\n statusFilter: statusFilterProp,\n searchFields: searchFieldsProp,\n defaultView = \"table\",\n detail = \"panel\",\n titleField: titleFieldProp,\n subtitleField: subtitleFieldProp,\n initialFilters,\n onFiltersChange,\n controlled = false,\n } = props;\n\n // -- View mode --\n const [viewMode, setViewMode] = useViewPreference(\n resource ?? \"resource-page\",\n defaultView,\n );\n\n // -- Derive initial values from initialFilters --\n const initialSearch = useMemo(() => {\n if (!initialFilters) return \"\";\n const searchFilter = initialFilters.find(\n (f) => f.operator === \"contains\" && f.field === \"__search__\",\n );\n return searchFilter && \"value\" in searchFilter ? String(searchFilter.value ?? \"\") : \"\";\n }, []); // eslint-disable-line react-hooks/exhaustive-deps -- only on mount\n\n const initialSort = useMemo(() => {\n if (!initialFilters) return null;\n const sortFilter = initialFilters.find(\n (f) => f.field === \"__sort__\",\n );\n if (!sortFilter || !(\"value\" in sortFilter) || typeof sortFilter.value !== \"string\") return null;\n const [field, order] = sortFilter.value.split(\":\");\n if (!field || (order !== \"asc\" && order !== \"desc\")) return null;\n return { field, order } as SortParam;\n }, []); // eslint-disable-line react-hooks/exhaustive-deps -- only on mount\n\n const initialStatus = useMemo(() => {\n if (!initialFilters) return [];\n const statusFilter = initialFilters.find(\n (f) => f.operator === \"in\" && f.field === \"__status__\",\n );\n if (!statusFilter || statusFilter.operator !== \"in\") return [];\n return statusFilter.value.map((v) => String(v));\n }, []); // eslint-disable-line react-hooks/exhaustive-deps -- only on mount\n\n // -- Search --\n const [search, setSearch] = useState(initialSearch);\n\n // -- Sort --\n const [sort, setSort] = useState<SortParam | null>(initialSort);\n\n // -- Status filter --\n const [statusValue, setStatusValue] = useState<string[]>(initialStatus);\n\n // -- Detail panel --\n const [selectedItem, setSelectedItem] = useState<T | null>(null);\n const [detailOpen, setDetailOpen] = useState(false);\n\n // -- Resolve columns --\n const resolvedColumns = useMemo(\n () => resolveColumns(columnsProp, rawData),\n [columnsProp, rawData],\n );\n\n // -- Title / subtitle fields --\n const titleField =\n titleFieldProp ?? resolvedColumns[0]?.key ?? \"name\";\n const subtitleField =\n subtitleFieldProp ?? resolvedColumns[1]?.key ?? \"\";\n\n // -- Status filter config --\n const statusFieldName = useMemo(() => {\n if (!statusFilterProp) return null;\n return typeof statusFilterProp === \"string\"\n ? statusFilterProp\n : statusFilterProp.field;\n }, [statusFilterProp]);\n\n const statusOptions = useMemo(() => {\n if (!statusFilterProp) return [];\n if (typeof statusFilterProp === \"object\" && statusFilterProp.options) {\n return statusFilterProp.options;\n }\n if (!statusFieldName) return [];\n return deriveStatusOptions(rawData, statusFieldName);\n }, [statusFilterProp, statusFieldName, rawData]);\n\n // -- Search fields --\n const searchFields = useMemo(\n () => searchFieldsProp ?? deriveStringFields(rawData),\n [searchFieldsProp, rawData],\n );\n\n // -- Client-side filtering + sorting --\n // Skipped entirely in controlled mode: the server already applied the\n // search, the sort and the page window, so re-running them here would\n // filter the current page against a query the server has already honoured\n // (and re-sort a slice of rows that is not the full result set).\n const filteredData = useMemo(() => {\n if (controlled) return rawData;\n\n let result = rawData;\n\n // Text search\n if (search) {\n result = result.filter((item) =>\n matchesSearch(item, search, searchFields),\n );\n }\n\n // Status filter\n if (statusFieldName && statusValue.length > 0) {\n result = result.filter((item) => {\n const val = (item as Record<string, unknown>)[statusFieldName];\n return val != null && statusValue.includes(String(val));\n });\n }\n\n // Sort\n if (sort) {\n result = [...result].sort((a, b) => compareItems(a, b, sort));\n }\n\n return result;\n }, [controlled, rawData, search, searchFields, statusFieldName, statusValue, sort]);\n\n // -- Sort options (built from resolved columns) --\n const sortOptions = useMemo(() => {\n const opts: Array<{ value: string; label: string }> = [];\n for (const col of resolvedColumns) {\n if (col.sortable !== false) {\n opts.push({\n value: `${col.key}:asc`,\n label: `${col.header ?? titleCase(col.key)} (A-Z)`,\n });\n opts.push({\n value: `${col.key}:desc`,\n label: `${col.header ?? titleCase(col.key)} (Z-A)`,\n });\n }\n }\n return opts;\n }, [resolvedColumns]);\n\n // -- Sort string for DataFilters select --\n const sortString = sort ? `${sort.field}:${sort.order}` : \"\";\n\n const handleSortChange = useCallback((value: string) => {\n if (!value) {\n setSort(null);\n return;\n }\n const [field, order] = value.split(\":\");\n setSort({ field, order: order as \"asc\" | \"desc\" });\n }, []);\n\n // -- Map to DataTableColumn --\n const tableColumns: DataTableColumn<T>[] = useMemo(\n () =>\n resolvedColumns.map((col) => ({\n key: col.key,\n header: col.header ?? titleCase(col.key),\n sortable: col.sortable ?? true,\n hideBelow: col.hideBelow,\n render: col.render\n ? (item: T, value: unknown) =>\n col.render!(item, value, {\n update: async () => {\n throw new Error(\n \"ResourceActions.update not available in server-data mode\",\n );\n },\n remove: async () => {\n throw new Error(\n \"ResourceActions.remove not available in server-data mode\",\n );\n },\n refetch: () => {\n /* no-op in server-data mode */\n },\n })\n : undefined,\n })),\n [resolvedColumns],\n );\n\n // -- Active filters check --\n const hasActiveFilters =\n search !== \"\" || statusValue.length > 0 || sort !== null;\n\n const clearFilters = useCallback(() => {\n setSearch(\"\");\n setStatusValue([]);\n setSort(null);\n }, []);\n\n // -- Notify parent of filter changes --\n const onFiltersChangeRef = useRef(onFiltersChange);\n onFiltersChangeRef.current = onFiltersChange;\n\n useEffect(() => {\n if (!onFiltersChangeRef.current) return;\n const filters: FilterParam[] = [];\n if (search) {\n filters.push({ field: \"__search__\", operator: \"contains\", value: search });\n }\n if (sort) {\n filters.push({ field: \"__sort__\", operator: \"eq\", value: `${sort.field}:${sort.order}` });\n }\n if (statusValue.length > 0) {\n filters.push({ field: \"__status__\", operator: \"in\", value: statusValue });\n }\n onFiltersChangeRef.current(filters);\n }, [search, sort, statusValue]);\n\n // -- Row click --\n const handleRowClick = useCallback(\n (item: T) => {\n if (detail === \"none\") return;\n setSelectedItem(item);\n setDetailOpen(true);\n },\n [detail],\n );\n\n // -- Filter config (ready to spread into DataFilters) --\n const filterConfig = useMemo(\n () => ({\n search,\n onSearchChange: setSearch,\n sort: sortString,\n onSortChange: handleSortChange,\n sortOptions,\n ...(statusFieldName && statusOptions.length > 0\n ? {\n statusChips: {\n options: statusOptions,\n selected: statusValue,\n onChange: setStatusValue,\n },\n }\n : {}),\n }),\n [\n search,\n sortString,\n handleSortChange,\n sortOptions,\n statusFieldName,\n statusOptions,\n statusValue,\n ],\n );\n\n return {\n data: filteredData,\n // Controlled mode: `filteredData` is one server page, so its length is\n // not the record count — the server's total is.\n total: controlled ? rawTotal : filteredData.length,\n columns: resolvedColumns,\n tableColumns,\n filterConfig,\n viewMode,\n setViewMode,\n search,\n setSearch,\n sort,\n setSort,\n statusValue,\n setStatusValue,\n selectedItem,\n setSelectedItem,\n detailOpen,\n setDetailOpen,\n handleRowClick,\n titleField,\n subtitleField,\n statusOptions,\n hasActiveFilters,\n clearFilters,\n };\n}\n"]}
|
|
@@ -1,21 +1,20 @@
|
|
|
1
|
-
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
1
|
import * as React from 'react';
|
|
3
2
|
import { ReactNode, RefObject } from 'react';
|
|
4
|
-
import { c as PageHeaderOptions, A as AppShell$1, P as PageHeader$1 } from '../../AppShell-
|
|
5
|
-
export { a as AppShellOptions, b as AppShellProps, N as NavItem, d as PageHeaderProps } from '../../AppShell-
|
|
6
|
-
import { V as ViewMode } from '../../view-toggle-
|
|
7
|
-
export { a as ViewToggle } from '../../view-toggle-
|
|
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';
|
|
5
|
+
import { V as ViewMode } from '../../view-toggle-CahQ87PW.js';
|
|
6
|
+
export { a as ViewToggle } from '../../view-toggle-CahQ87PW.js';
|
|
8
7
|
import { LucideIcon } from 'lucide-react';
|
|
9
8
|
import { Density } from '../../hooks/use-view-settings.js';
|
|
10
|
-
import { R as ResourceActions, a as ResourceColumnDef } from '../../use-resource-page-
|
|
11
|
-
export { D as DataTable, c as DataTableColumn, d as DataTableProps, L as LinkCell, T as ThumbnailCell } from '../../use-resource-page-
|
|
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';
|
|
12
11
|
export { EmptyState } from '../patterns/empty-state.js';
|
|
13
|
-
export { V as ViewSettings } from '../../view-settings
|
|
12
|
+
export { V as ViewSettings } from '../../view-settings-kHKCHMjj.js';
|
|
14
13
|
import { L as LinkComponent, N as NavigateFn } from '../../navigation-CpOEyUSb.js';
|
|
15
14
|
export { D as DefaultLink, G as GenesisLinkProps, d as defaultNavigate, i as isPathActive } from '../../navigation-CpOEyUSb.js';
|
|
16
15
|
import { UseFormReturn, Control, FieldErrors } from 'react-hook-form';
|
|
17
16
|
import { WizardStep, UseResourceOptions } from '@marktiderman/genesis-core/hooks';
|
|
18
|
-
import { FilterParam, BaseRecord, Identifier } from '@marktiderman/genesis-core/provider';
|
|
17
|
+
import { FilterParam, SortParam, BaseRecord, Identifier } from '@marktiderman/genesis-core/provider';
|
|
19
18
|
import { ResourceFormFieldDef } from '@marktiderman/genesis-core';
|
|
20
19
|
export { FieldType, ResourceFormFieldDef, SelectOption } from '@marktiderman/genesis-core';
|
|
21
20
|
export { CommandPalette, CommandPaletteItem, CommandPaletteProps, useCommandPaletteHotkey } from '../ui/command-palette.js';
|
|
@@ -74,7 +73,7 @@ interface DataPageShellProps {
|
|
|
74
73
|
headerOptionsStorageKey?: string;
|
|
75
74
|
children: ReactNode;
|
|
76
75
|
}
|
|
77
|
-
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):
|
|
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;
|
|
78
77
|
|
|
79
78
|
interface FilterOption {
|
|
80
79
|
value: string;
|
|
@@ -92,7 +91,7 @@ interface FilterComboboxProps {
|
|
|
92
91
|
/** Optional map of value -> display label */
|
|
93
92
|
labelMap?: Record<string, string>;
|
|
94
93
|
}
|
|
95
|
-
declare function FilterCombobox({ label, icon: Icon, options, selected, onChange, searchPlaceholder, allLabel, labelMap, }: FilterComboboxProps):
|
|
94
|
+
declare function FilterCombobox({ label, icon: Icon, options, selected, onChange, searchPlaceholder, allLabel, labelMap, }: FilterComboboxProps): React.JSX.Element;
|
|
96
95
|
|
|
97
96
|
interface DataFilterConfig {
|
|
98
97
|
key: string;
|
|
@@ -142,7 +141,7 @@ interface DataFiltersProps {
|
|
|
142
141
|
/** Expose the search input ref for keyboard nav "/" shortcut */
|
|
143
142
|
searchInputRef?: React.RefObject<HTMLInputElement | null>;
|
|
144
143
|
}
|
|
145
|
-
declare function DataFilters({ search, onSearchChange, searchPlaceholder, sort, onSortChange, sortOptions, filters, statusChips, savedViews, onLoadView, onSaveView, onDeleteView, canSaveViews, onViewSaved, onClearAll, searchInputRef, }: DataFiltersProps):
|
|
144
|
+
declare function DataFilters({ search, onSearchChange, searchPlaceholder, sort, onSortChange, sortOptions, filters, statusChips, savedViews, onLoadView, onSaveView, onDeleteView, canSaveViews, onViewSaved, onClearAll, searchInputRef, }: DataFiltersProps): React.JSX.Element;
|
|
146
145
|
|
|
147
146
|
interface DataGridProps<T> {
|
|
148
147
|
items: T[];
|
|
@@ -166,13 +165,36 @@ interface DataGridProps<T> {
|
|
|
166
165
|
/** Keyboard navigation: container ref for scroll management */
|
|
167
166
|
keyboardContainerRef?: RefObject<HTMLDivElement | null>;
|
|
168
167
|
}
|
|
169
|
-
declare function DataGrid<T>({ items, viewMode, batchSize, gridCols, renderCard, renderTable, renderListItem, staggerAnimation, getKey, focusedIndex, keyboardContainerRef, }: DataGridProps<T>):
|
|
168
|
+
declare function DataGrid<T>({ items, viewMode, batchSize, gridCols, renderCard, renderTable, renderListItem, staggerAnimation, getKey, focusedIndex, keyboardContainerRef, }: DataGridProps<T>): React.JSX.Element;
|
|
170
169
|
|
|
170
|
+
/**
|
|
171
|
+
* One button in the bulk action bar.
|
|
172
|
+
*
|
|
173
|
+
* @stability Stable
|
|
174
|
+
*/
|
|
171
175
|
interface BulkAction {
|
|
172
176
|
label: string;
|
|
173
177
|
icon: LucideIcon;
|
|
174
178
|
onClick: () => void;
|
|
175
179
|
variant?: "default" | "destructive" | "outline" | "secondary" | "ghost" | "link";
|
|
180
|
+
/**
|
|
181
|
+
* Makes this action inert: it renders with the Button primitive's native
|
|
182
|
+
* `disabled` attribute, so it is neither clickable nor focusable, and it
|
|
183
|
+
* picks up the disabled styling.
|
|
184
|
+
*
|
|
185
|
+
* The case this exists for is an in-flight bulk mutation. A consumer that
|
|
186
|
+
* fires a move/duplicate/delete over the selected ids wants every bulk
|
|
187
|
+
* action dead until it settles, so a user cannot double-fire a destructive
|
|
188
|
+
* operation on the same selection. Without this field the only guard
|
|
189
|
+
* available is an early return inside `onClick` — which does stop the
|
|
190
|
+
* second call, but leaves the button looking live, so the affordance lies
|
|
191
|
+
* about what pressing it will do. Concretely, that is what blocks a real
|
|
192
|
+
* consumer's hand-rolled bulk bar from migrating to `DataBulkBar`: it
|
|
193
|
+
* would have to give up its double-submit guard to do so.
|
|
194
|
+
*
|
|
195
|
+
* Optional and defaulted off — omitting it behaves exactly as before.
|
|
196
|
+
*/
|
|
197
|
+
disabled?: boolean;
|
|
176
198
|
}
|
|
177
199
|
interface DataBulkBarProps {
|
|
178
200
|
selected: Set<string>;
|
|
@@ -181,7 +203,7 @@ interface DataBulkBarProps {
|
|
|
181
203
|
onClearSelection: () => void;
|
|
182
204
|
actions: BulkAction[];
|
|
183
205
|
}
|
|
184
|
-
declare function DataBulkBar({ selected, totalCount, onToggleAll, onClearSelection, actions, }: DataBulkBarProps):
|
|
206
|
+
declare function DataBulkBar({ selected, totalCount, onToggleAll, onClearSelection, actions, }: DataBulkBarProps): React.JSX.Element | null;
|
|
185
207
|
|
|
186
208
|
/**
|
|
187
209
|
* DateCell -- shows relative date by default, formatted date otherwise.
|
|
@@ -202,7 +224,7 @@ declare function CurrencyCell(currency?: string, locale?: string): (_item: unkno
|
|
|
202
224
|
/**
|
|
203
225
|
* BadgeCell -- status badge with configurable color map.
|
|
204
226
|
*/
|
|
205
|
-
declare function BadgeCell(colorMap?: Record<string, "default" | "secondary" | "destructive" | "outline" | "success" | "warning" | "info" | "muted">): (_item: unknown, value: unknown) =>
|
|
227
|
+
declare function BadgeCell(colorMap?: Record<string, "default" | "secondary" | "destructive" | "outline" | "success" | "warning" | "info" | "muted">): (_item: unknown, value: unknown) => React.JSX.Element | "—";
|
|
206
228
|
|
|
207
229
|
type StatCardColor = "primary" | "success" | "warning" | "info" | "destructive" | "muted";
|
|
208
230
|
interface StatCardProps {
|
|
@@ -224,7 +246,7 @@ interface StatCardProps {
|
|
|
224
246
|
};
|
|
225
247
|
className?: string;
|
|
226
248
|
}
|
|
227
|
-
declare function StatCard({ label, value, icon: Icon, color, href, linkComponent: Link, trend, className, }: StatCardProps):
|
|
249
|
+
declare function StatCard({ label, value, icon: Icon, color, href, linkComponent: Link, trend, className, }: StatCardProps): React.JSX.Element;
|
|
228
250
|
|
|
229
251
|
type ColumnStatus = "neutral" | "warning" | "success" | "info" | "destructive" | "primary";
|
|
230
252
|
interface KanbanColumn<T> {
|
|
@@ -244,7 +266,7 @@ interface KanbanBoardProps<T> {
|
|
|
244
266
|
emptyMessage?: string;
|
|
245
267
|
className?: string;
|
|
246
268
|
}
|
|
247
|
-
declare function KanbanBoard<T>({ columns, renderCard, emptyMessage, className, }: KanbanBoardProps<T>):
|
|
269
|
+
declare function KanbanBoard<T>({ columns, renderCard, emptyMessage, className, }: KanbanBoardProps<T>): React.JSX.Element;
|
|
248
270
|
|
|
249
271
|
interface DataListProps<T> {
|
|
250
272
|
items: T[];
|
|
@@ -252,7 +274,7 @@ interface DataListProps<T> {
|
|
|
252
274
|
emptyMessage?: string;
|
|
253
275
|
className?: string;
|
|
254
276
|
}
|
|
255
|
-
declare function DataList<T>({ items, renderItem, emptyMessage, className, }: DataListProps<T>):
|
|
277
|
+
declare function DataList<T>({ items, renderItem, emptyMessage, className, }: DataListProps<T>): React.JSX.Element;
|
|
256
278
|
|
|
257
279
|
/**
|
|
258
280
|
* Props for {@link DetailPanel} — a shell-agnostic record detail view.
|
|
@@ -305,7 +327,7 @@ interface DetailPanelProps<T> {
|
|
|
305
327
|
*/
|
|
306
328
|
layout?: "inline" | "dialog" | "sheet";
|
|
307
329
|
}
|
|
308
|
-
declare function DetailPanel<T extends Record<string, unknown>>({ item, open, onClose, title, subtitle, onEdit, onDelete, children, fields, width, layout, }: DetailPanelProps<T>):
|
|
330
|
+
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;
|
|
309
331
|
|
|
310
332
|
/**
|
|
311
333
|
* Row-bound actions handed to `renderCard`, on top of the resource-wide
|
|
@@ -333,6 +355,26 @@ interface ResourceCardActions<T> extends ResourceActions<T> {
|
|
|
333
355
|
* data-fetching, filtering, and layout; see also the built-in
|
|
334
356
|
* `useKeyboardNavigation` wiring (`keyboardNavigation` prop) and `gridCols`.
|
|
335
357
|
*
|
|
358
|
+
* Three levels of control, and each option is available at all three:
|
|
359
|
+
*
|
|
360
|
+
* 1. **The primitive offers it** — table / grid / list views, search, sort,
|
|
361
|
+
* status chips, column visibility and resizing, density, page size,
|
|
362
|
+
* detail panel / modal / route, built-in create and edit forms.
|
|
363
|
+
* 2. **The instance turns it on or off** — every one of those is a prop, so
|
|
364
|
+
* a given page decides which affordances it offers at all.
|
|
365
|
+
* 3. **The end user picks their own, and it sticks** — for the options left
|
|
366
|
+
* on at level 2, the user's choice persists to their own storage:
|
|
367
|
+
* `viewSettingsKey` for page size and density, `columnVisibilityKey` and
|
|
368
|
+
* `resizeKey` for column layout, `savedViews` (pair with `useSavedViews`)
|
|
369
|
+
* for named filter sets. Persistence needs a key; without one the choice
|
|
370
|
+
* lasts only for the session.
|
|
371
|
+
*
|
|
372
|
+
* A server-paginated list drives the same component through the controlled
|
|
373
|
+
* props (`page`, `perPage`, `onPageChange`, `onSortChange`, `onSearchChange`
|
|
374
|
+
* and friends) — supplying any of them stops ResourcePage from searching,
|
|
375
|
+
* sorting or paging the `data` a second time. See that block in
|
|
376
|
+
* {@link ResourcePageProps}.
|
|
377
|
+
*
|
|
336
378
|
* @stability Beta
|
|
337
379
|
*/
|
|
338
380
|
interface ResourcePageProps<T extends Record<string, unknown> = Record<string, unknown>> {
|
|
@@ -340,7 +382,17 @@ interface ResourcePageProps<T extends Record<string, unknown> = Record<string, u
|
|
|
340
382
|
resource?: string;
|
|
341
383
|
/** Server-data mode: pre-fetched data array. */
|
|
342
384
|
data?: T[];
|
|
343
|
-
/**
|
|
385
|
+
/**
|
|
386
|
+
* Server-data mode: total record count (before client-side filtering).
|
|
387
|
+
*
|
|
388
|
+
* Required whenever `onPageChange` is supplied. It cannot be derived from
|
|
389
|
+
* `data`, which holds only the current page. Omitted, `displayTotal` falls
|
|
390
|
+
* back to `0`, so the range span reads "No records" and `Next` stays
|
|
391
|
+
* disabled while the rows render directly below it. Rather than show a
|
|
392
|
+
* pager that contradicts the list, the component refuses to render the
|
|
393
|
+
* pager and shows an error instead. See {@link perPage} and
|
|
394
|
+
* {@link onPageChange}.
|
|
395
|
+
*/
|
|
344
396
|
total?: number;
|
|
345
397
|
title: string;
|
|
346
398
|
/** Page subtitle shown below the title. */
|
|
@@ -358,6 +410,16 @@ interface ResourcePageProps<T extends Record<string, unknown> = Record<string, u
|
|
|
358
410
|
label: string;
|
|
359
411
|
render?: (value: unknown, item: T) => ReactNode;
|
|
360
412
|
}>;
|
|
413
|
+
/**
|
|
414
|
+
* Adds a status-chip filter. There is no dedicated status-change
|
|
415
|
+
* callback: chip clicks update the same filter state that already
|
|
416
|
+
* reports through `onFiltersChange`, whether or not the page is in
|
|
417
|
+
* server-controlled mode. A caller combining `statusFilter` with server
|
|
418
|
+
* pagination (`onPageChange` et al.) must supply `onFiltersChange` — see
|
|
419
|
+
* that prop — or status changes will update the chip UI without ever
|
|
420
|
+
* reaching the server. In server-controlled mode a status change also
|
|
421
|
+
* resets to page 1 via `onPageChange(1)` — see {@link onPageChange}.
|
|
422
|
+
*/
|
|
361
423
|
statusFilter?: string | {
|
|
362
424
|
field: string;
|
|
363
425
|
options?: string[];
|
|
@@ -366,6 +428,123 @@ interface ResourcePageProps<T extends Record<string, unknown> = Record<string, u
|
|
|
366
428
|
searchPlaceholder?: string;
|
|
367
429
|
initialFilters?: FilterParam[];
|
|
368
430
|
onFiltersChange?: (filters: FilterParam[]) => void;
|
|
431
|
+
/**
|
|
432
|
+
* Extra multi-select filter comboboxes, handed straight to `DataFilters`
|
|
433
|
+
* and rendered beside the status chips. Each config owns its own
|
|
434
|
+
* `selected` array and `onChange` — ResourcePage never reads or writes
|
|
435
|
+
* them, because it cannot know what a caller's "Grade" or "Subject"
|
|
436
|
+
* combobox means against the row shape. That is deliberate rather than a
|
|
437
|
+
* gap: the built-in machinery (`searchFields`, `statusFilter`) covers the
|
|
438
|
+
* two shapes it *can* interpret, and this prop covers everything else by
|
|
439
|
+
* getting out of the way.
|
|
440
|
+
*
|
|
441
|
+
* Two consequences of that hands-off stance, stated rather than hidden:
|
|
442
|
+
* - These selections are invisible to `page.hasActiveFilters`, so the
|
|
443
|
+
* header's "Clear filters" affordance does not appear for them alone,
|
|
444
|
+
* and `onClearFilters` does not reset them. DataFilters' own "Clear all"
|
|
445
|
+
* does call each config's `onChange([])`, so wire real clearing there.
|
|
446
|
+
* - In client-side mode the rows are NOT re-filtered by these selections.
|
|
447
|
+
* A caller using them is expected to filter its own `data` (or ask the
|
|
448
|
+
* server to), exactly as it already does in server-controlled mode.
|
|
449
|
+
*/
|
|
450
|
+
filters?: DataFilterConfig[];
|
|
451
|
+
/** Current page, 1-based. Server-controlled mode only. */
|
|
452
|
+
page?: number;
|
|
453
|
+
/**
|
|
454
|
+
* Rows per page the server was asked for. Required whenever
|
|
455
|
+
* `onPageChange` is supplied: pager math (total pages, range start, Next)
|
|
456
|
+
* cannot be inferred from `data.length`, because a short final page is
|
|
457
|
+
* smaller than the real page size. Wins over the per-user page size
|
|
458
|
+
* persisted by `viewSettingsKey` — an explicit prop always beats a stored
|
|
459
|
+
* preference.
|
|
460
|
+
*
|
|
461
|
+
* Required whenever `onPageChange` is supplied. A short final page (the
|
|
462
|
+
* server's last page returning fewer rows than its page size) is
|
|
463
|
+
* indistinguishable from a small page size — there is no correct way to
|
|
464
|
+
* infer the divisor from the rows handed back. Rather than guess and
|
|
465
|
+
* render a wrong range or a stuck Next button, the component refuses to
|
|
466
|
+
* render the pager and shows an error instead when `onPageChange` is
|
|
467
|
+
* present without `perPage`.
|
|
468
|
+
*
|
|
469
|
+
* `total` is required on the same terms — see {@link total}. The error
|
|
470
|
+
* names every missing prop, not just the first.
|
|
471
|
+
*/
|
|
472
|
+
perPage?: number;
|
|
473
|
+
/**
|
|
474
|
+
* Called with the next 1-based page number. The pager only renders when
|
|
475
|
+
* this is supplied AND both `perPage` and `total` are supplied — see
|
|
476
|
+
* {@link perPage} and {@link total}. Supplying this without either one
|
|
477
|
+
* renders an error naming the missing props instead of a pager.
|
|
478
|
+
*
|
|
479
|
+
* Status changes reach the server through `onFiltersChange` — not a
|
|
480
|
+
* dedicated callback here — because `statusFilter` state is tracked by
|
|
481
|
+
* the same filter machinery that already calls `onFiltersChange`
|
|
482
|
+
* regardless of controlled mode (see {@link statusFilter}). In
|
|
483
|
+
* server-controlled mode a status change also calls `onPageChange(1)`
|
|
484
|
+
* directly, since the previous page number is meaningless against a new
|
|
485
|
+
* filtered result set.
|
|
486
|
+
*/
|
|
487
|
+
onPageChange?: (page: number) => void;
|
|
488
|
+
/** Called when the user picks a different page size in the header's view settings. */
|
|
489
|
+
onPerPageChange?: (perPage: number) => void;
|
|
490
|
+
/** Called when the user changes the sort, with `null` when the sort is cleared. */
|
|
491
|
+
onSortChange?: (sort: SortParam | null) => void;
|
|
492
|
+
/** Called when the user changes the search text (including clearing it). */
|
|
493
|
+
onSearchChange?: (search: string) => void;
|
|
494
|
+
/**
|
|
495
|
+
* Server-data mode: the caller's fetch is in flight, so render the shell's
|
|
496
|
+
* loading skeleton instead of the (as yet meaningless) `data`. Ignored in
|
|
497
|
+
* client-side mode, where `useResource`'s own loading state is used.
|
|
498
|
+
*/
|
|
499
|
+
loading?: boolean;
|
|
500
|
+
/**
|
|
501
|
+
* Server-data mode: the caller's fetch failed, so render the shell's error
|
|
502
|
+
* panel instead of an empty table. Takes effect even when `data` is
|
|
503
|
+
* non-empty — stale rows beside no indication of failure are exactly the
|
|
504
|
+
* silent-failure this prop exists to prevent. Ignored in client-side mode,
|
|
505
|
+
* where `useResource`'s own error state is used.
|
|
506
|
+
*/
|
|
507
|
+
error?: boolean;
|
|
508
|
+
/**
|
|
509
|
+
* Message shown in the error panel, in place of the shell's generic
|
|
510
|
+
* "Failed to load …". Forwarded in both modes — it is inert unless an
|
|
511
|
+
* error state is actually showing — so a client-side page can also give
|
|
512
|
+
* its failure a human sentence.
|
|
513
|
+
*/
|
|
514
|
+
errorMessage?: string;
|
|
515
|
+
/**
|
|
516
|
+
* Server-data mode: invoked by the error panel's Retry button. The button
|
|
517
|
+
* only appears when this is supplied, so a caller with no way to retry
|
|
518
|
+
* simply omits it and gets an error panel without a dead control. In
|
|
519
|
+
* client-side mode the button is already wired to `useResource`'s own
|
|
520
|
+
* `refetch` and this prop is ignored.
|
|
521
|
+
*/
|
|
522
|
+
onRetry?: () => void;
|
|
523
|
+
/** Saved filter views to offer. Pair with `useSavedViews` for per-user storage. */
|
|
524
|
+
savedViews?: SavedViewItem[];
|
|
525
|
+
onLoadView?: (filters: Record<string, unknown>) => void;
|
|
526
|
+
onSaveView?: (name: string) => void;
|
|
527
|
+
onDeleteView?: (id: string) => void;
|
|
528
|
+
/** Show the "save current view" affordance. */
|
|
529
|
+
canSaveViews?: boolean;
|
|
530
|
+
/** Show the column-visibility dropdown. DataTable's default is `true`. */
|
|
531
|
+
columnVisibility?: boolean;
|
|
532
|
+
/** Persist key for the user's column-visibility picks. */
|
|
533
|
+
columnVisibilityKey?: string;
|
|
534
|
+
/** Allow drag-resizing columns. DataTable's default is `true`. */
|
|
535
|
+
resizableColumns?: boolean;
|
|
536
|
+
/** Persist key for the user's column widths. */
|
|
537
|
+
resizeKey?: string;
|
|
538
|
+
/** Keep the table header visible while scrolling. DataTable's default is `true`. */
|
|
539
|
+
stickyHeader?: boolean;
|
|
540
|
+
/** Keep the first column visible while scrolling horizontally. */
|
|
541
|
+
stickyFirstColumn?: boolean;
|
|
542
|
+
/**
|
|
543
|
+
* Page-size choices offered by `DataTable`'s own pager. Client-side mode
|
|
544
|
+
* only — server-controlled mode switches that pager off. (The header's
|
|
545
|
+
* view-settings popover has its own fixed 10/25/50/100 list.)
|
|
546
|
+
*/
|
|
547
|
+
pageSizeOptions?: number[];
|
|
369
548
|
onCreate?: () => void;
|
|
370
549
|
onEdit?: (item: T) => void;
|
|
371
550
|
onDelete?: (item: T) => void;
|
|
@@ -389,6 +568,13 @@ interface ResourcePageProps<T extends Record<string, unknown> = Record<string, u
|
|
|
389
568
|
icon?: LucideIcon;
|
|
390
569
|
variant?: "default" | "destructive";
|
|
391
570
|
onAction: (ids: string[]) => void | Promise<void>;
|
|
571
|
+
/**
|
|
572
|
+
* Forwarded straight to the rendered bulk button (see `BulkAction`).
|
|
573
|
+
* Exists so a consumer can kill every bulk action while one of them is
|
|
574
|
+
* in flight — an early return inside `onAction` blocks the second call
|
|
575
|
+
* but leaves the button looking live, which misstates what it will do.
|
|
576
|
+
*/
|
|
577
|
+
disabled?: boolean;
|
|
392
578
|
}>;
|
|
393
579
|
/**
|
|
394
580
|
* Override the default card renderer used for grid view (and, absent a
|
|
@@ -405,6 +591,23 @@ interface ResourcePageProps<T extends Record<string, unknown> = Record<string, u
|
|
|
405
591
|
* call `actions.open()` from wherever your card should activate.
|
|
406
592
|
*/
|
|
407
593
|
renderCard?: (item: T, index: number, actions: ResourceCardActions<T>) => ReactNode;
|
|
594
|
+
/**
|
|
595
|
+
* Replace the built-in `DataTable` that table view renders, keeping every
|
|
596
|
+
* other piece of the page — header, counts, view settings, filters,
|
|
597
|
+
* bulk bar, detail panel, form, pager — exactly as it is. The counterpart
|
|
598
|
+
* to `renderCard`, which does the same for grid and list view.
|
|
599
|
+
*
|
|
600
|
+
* Receives the rows for the current view, which in server-controlled mode
|
|
601
|
+
* is precisely the page the server returned. Ownership is total: row
|
|
602
|
+
* click-through, selection, density, column visibility and resizing are
|
|
603
|
+
* all things the built-in table wires for you and a custom table must
|
|
604
|
+
* wire for itself. That is the trade this prop makes, and it is why
|
|
605
|
+
* `renderCard` remains the lighter option when only the *cells* need to
|
|
606
|
+
* change — a `ResourceColumnDef.render` is lighter still.
|
|
607
|
+
*
|
|
608
|
+
* Omitted, the default `DataTable` render is used unchanged.
|
|
609
|
+
*/
|
|
610
|
+
renderTable?: (items: T[]) => ReactNode;
|
|
408
611
|
/**
|
|
409
612
|
* Tailwind grid-column classes for the card grid, passed through to
|
|
410
613
|
* `DataGrid`'s `gridCols` — e.g. `"grid-cols-1 sm:grid-cols-2 lg:grid-cols-3"`.
|
|
@@ -447,7 +650,7 @@ interface ResourcePageProps<T extends Record<string, unknown> = Record<string, u
|
|
|
447
650
|
*/
|
|
448
651
|
viewSettingsKey?: string;
|
|
449
652
|
}
|
|
450
|
-
declare function ResourcePage<T extends Record<string, unknown> = Record<string, unknown>>(props: ResourcePageProps<T>):
|
|
653
|
+
declare function ResourcePage<T extends Record<string, unknown> = Record<string, unknown>>(props: ResourcePageProps<T>): React.JSX.Element;
|
|
451
654
|
|
|
452
655
|
interface ResourceDetailPageProps<T extends BaseRecord = BaseRecord> {
|
|
453
656
|
/** Resource name, resolved through the mounted `DataProvider`. */
|
|
@@ -526,7 +729,7 @@ interface ResourceDetailPageProps<T extends BaseRecord = BaseRecord> {
|
|
|
526
729
|
*
|
|
527
730
|
* @stability Beta
|
|
528
731
|
*/
|
|
529
|
-
declare function ResourceDetailPage<T extends BaseRecord = BaseRecord>({ resource, id, titleField, subtitleField, select, breadcrumb, actions, sidebar, children, className, testID, }: ResourceDetailPageProps<T>):
|
|
732
|
+
declare function ResourceDetailPage<T extends BaseRecord = BaseRecord>({ resource, id, titleField, subtitleField, select, breadcrumb, actions, sidebar, children, className, testID, }: ResourceDetailPageProps<T>): React.JSX.Element;
|
|
530
733
|
|
|
531
734
|
interface ResourceFormProps {
|
|
532
735
|
resource: string;
|
|
@@ -550,7 +753,7 @@ interface ResourceFormProps {
|
|
|
550
753
|
/** Number of columns for the form layout. Default 1. */
|
|
551
754
|
columns?: 1 | 2;
|
|
552
755
|
}
|
|
553
|
-
declare function ResourceForm(props: ResourceFormProps):
|
|
756
|
+
declare function ResourceForm(props: ResourceFormProps): React.JSX.Element | null;
|
|
554
757
|
|
|
555
758
|
interface RelationFieldProps {
|
|
556
759
|
field: ResourceFormFieldDef;
|
|
@@ -558,7 +761,7 @@ interface RelationFieldProps {
|
|
|
558
761
|
onChange: (value: string) => void;
|
|
559
762
|
disabled?: boolean;
|
|
560
763
|
}
|
|
561
|
-
declare function RelationField({ field, value, onChange, disabled, }: RelationFieldProps):
|
|
764
|
+
declare function RelationField({ field, value, onChange, disabled, }: RelationFieldProps): React.JSX.Element;
|
|
562
765
|
|
|
563
766
|
interface FileUploadFieldProps {
|
|
564
767
|
field: ResourceFormFieldDef;
|
|
@@ -566,14 +769,14 @@ interface FileUploadFieldProps {
|
|
|
566
769
|
onChange: (value: File | File[] | null) => void;
|
|
567
770
|
disabled?: boolean;
|
|
568
771
|
}
|
|
569
|
-
declare function FileUploadField({ field, value, onChange, disabled, }: FileUploadFieldProps):
|
|
772
|
+
declare function FileUploadField({ field, value, onChange, disabled, }: FileUploadFieldProps): React.JSX.Element;
|
|
570
773
|
|
|
571
774
|
declare function FieldArrayRenderer({ field, control, action, watchedValues, }: {
|
|
572
775
|
field: ResourceFormFieldDef;
|
|
573
776
|
control: Control<Record<string, unknown>>;
|
|
574
777
|
action: "create" | "edit";
|
|
575
778
|
watchedValues?: Record<string, unknown>;
|
|
576
|
-
}):
|
|
779
|
+
}): React.JSX.Element;
|
|
577
780
|
|
|
578
781
|
interface WizardFormProps {
|
|
579
782
|
steps: WizardStep[];
|
|
@@ -585,7 +788,7 @@ interface WizardFormProps {
|
|
|
585
788
|
action: "create" | "edit";
|
|
586
789
|
onInvalid?: (errors: FieldErrors) => void;
|
|
587
790
|
}
|
|
588
|
-
declare function WizardForm({ steps, form, fields, onSubmit, isSaving, renderField, action, onInvalid, }: WizardFormProps):
|
|
791
|
+
declare function WizardForm({ steps, form, fields, onSubmit, isSaving, renderField, action, onInvalid, }: WizardFormProps): React.JSX.Element;
|
|
589
792
|
|
|
590
793
|
/**
|
|
591
794
|
* @deprecated Import `AppShell` from `@marktiderman/genesis-ui/layout` instead.
|