@marktiderman/genesis-ui 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/LICENSE +21 -0
  2. package/dist/{AppShell-BJarYZzz.d.ts → AppShell-sQT0UbXg.d.ts} +4 -4
  3. package/dist/{chunk-Q4I4SFBD.js → chunk-GDFNAHI4.js} +9 -5
  4. package/dist/chunk-GDFNAHI4.js.map +1 -0
  5. package/dist/components/data/index.d.ts +134 -27
  6. package/dist/components/data/index.js +234 -18
  7. package/dist/components/data/index.js.map +1 -1
  8. package/dist/components/layout/index.d.ts +2 -3
  9. package/dist/components/patterns/empty-state.d.ts +4 -5
  10. package/dist/components/patterns/form-field.d.ts +3 -4
  11. package/dist/components/patterns/index.d.ts +2 -3
  12. package/dist/components/patterns/page-loading.d.ts +2 -2
  13. package/dist/components/patterns/settings-row.d.ts +1 -2
  14. package/dist/components/patterns/status-badge.d.ts +1 -2
  15. package/dist/components/patterns/toggle-row.d.ts +1 -2
  16. package/dist/components/patterns/user-avatar.d.ts +2 -2
  17. package/dist/components/ui/accessible-icon.d.ts +1 -2
  18. package/dist/components/ui/alert-dialog.d.ts +2 -3
  19. package/dist/components/ui/badge.d.ts +1 -2
  20. package/dist/components/ui/breadcrumb.d.ts +2 -3
  21. package/dist/components/ui/button-group.d.ts +1 -2
  22. package/dist/components/ui/calendar.d.ts +2 -3
  23. package/dist/components/ui/card.d.ts +6 -7
  24. package/dist/components/ui/carousel.d.ts +2 -3
  25. package/dist/components/ui/chart.d.ts +4 -5
  26. package/dist/components/ui/command-palette.d.ts +1 -2
  27. package/dist/components/ui/command.d.ts +2 -3
  28. package/dist/components/ui/context-menu.d.ts +1 -2
  29. package/dist/components/ui/date-picker.d.ts +2 -2
  30. package/dist/components/ui/dialog.d.ts +2 -3
  31. package/dist/components/ui/drawer.d.ts +3 -4
  32. package/dist/components/ui/dropdown-menu.d.ts +1 -2
  33. package/dist/components/ui/kbd.d.ts +1 -2
  34. package/dist/components/ui/menubar.d.ts +1 -2
  35. package/dist/components/ui/pagination.d.ts +5 -6
  36. package/dist/components/ui/password-toggle-field.d.ts +2 -3
  37. package/dist/components/ui/progress.d.ts +1 -2
  38. package/dist/components/ui/resizable.d.ts +2 -3
  39. package/dist/components/ui/separator.d.ts +1 -2
  40. package/dist/components/ui/sheet.d.ts +2 -3
  41. package/dist/components/ui/skeleton.d.ts +1 -2
  42. package/dist/components/ui/sonner.d.ts +1 -2
  43. package/dist/components/ui/spinner.d.ts +1 -2
  44. package/dist/components/ui/tooltip.d.ts +1 -2
  45. package/dist/components/ui/typography.d.ts +9 -10
  46. package/dist/hooks/index.d.ts +2 -3
  47. package/dist/hooks/index.js +1 -1
  48. package/dist/hooks/use-resource-page.d.ts +2 -3
  49. package/dist/hooks/use-resource-page.js +1 -1
  50. package/dist/hooks/use-view-preference.d.ts +2 -2
  51. package/dist/index.d.ts +0 -1
  52. package/dist/{use-resource-page-CPs6wjKt.d.ts → use-resource-page-DTQS011Q.d.ts} +18 -6
  53. package/dist/{view-settings--KXNT_jE.d.ts → view-settings-kHKCHMjj.d.ts} +2 -2
  54. package/dist/{view-toggle-DDe_Luj2.d.ts → view-toggle-CahQ87PW.d.ts} +2 -2
  55. package/package.json +15 -15
  56. 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 react_jsx_runtime from 'react/jsx-runtime';
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-DDe_Luj2.js';
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): react_jsx_runtime.JSX.Element;
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): react_jsx_runtime.JSX.Element;
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
- total: filteredData.length,
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-Q4I4SFBD.js.map
288
- //# sourceMappingURL=chunk-Q4I4SFBD.js.map
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-BJarYZzz.js';
5
- export { a as AppShellOptions, b as AppShellProps, N as NavItem, d as PageHeaderProps } from '../../AppShell-BJarYZzz.js';
6
- import { V as ViewMode } from '../../view-toggle-DDe_Luj2.js';
7
- export { a as ViewToggle } from '../../view-toggle-DDe_Luj2.js';
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-CPs6wjKt.js';
11
- export { D as DataTable, c as DataTableColumn, d as DataTableProps, L as LinkCell, T as ThumbnailCell } from '../../use-resource-page-CPs6wjKt.js';
9
+ import { R as ResourceActions, a as ResourceColumnDef } from '../../use-resource-page-DTQS011Q.js';
10
+ export { D as DataTable, c as DataTableColumn, d as DataTableProps, L as LinkCell, T as ThumbnailCell } from '../../use-resource-page-DTQS011Q.js';
12
11
  export { EmptyState } from '../patterns/empty-state.js';
13
- export { V as ViewSettings } from '../../view-settings--KXNT_jE.js';
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): react_jsx_runtime.JSX.Element;
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): react_jsx_runtime.JSX.Element;
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): react_jsx_runtime.JSX.Element;
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,7 +165,7 @@ 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>): react_jsx_runtime.JSX.Element;
168
+ declare function DataGrid<T>({ items, viewMode, batchSize, gridCols, renderCard, renderTable, renderListItem, staggerAnimation, getKey, focusedIndex, keyboardContainerRef, }: DataGridProps<T>): React.JSX.Element;
170
169
 
171
170
  interface BulkAction {
172
171
  label: string;
@@ -181,7 +180,7 @@ interface DataBulkBarProps {
181
180
  onClearSelection: () => void;
182
181
  actions: BulkAction[];
183
182
  }
184
- declare function DataBulkBar({ selected, totalCount, onToggleAll, onClearSelection, actions, }: DataBulkBarProps): react_jsx_runtime.JSX.Element | null;
183
+ declare function DataBulkBar({ selected, totalCount, onToggleAll, onClearSelection, actions, }: DataBulkBarProps): React.JSX.Element | null;
185
184
 
186
185
  /**
187
186
  * DateCell -- shows relative date by default, formatted date otherwise.
@@ -202,7 +201,7 @@ declare function CurrencyCell(currency?: string, locale?: string): (_item: unkno
202
201
  /**
203
202
  * BadgeCell -- status badge with configurable color map.
204
203
  */
205
- declare function BadgeCell(colorMap?: Record<string, "default" | "secondary" | "destructive" | "outline" | "success" | "warning" | "info" | "muted">): (_item: unknown, value: unknown) => react_jsx_runtime.JSX.Element | "—";
204
+ declare function BadgeCell(colorMap?: Record<string, "default" | "secondary" | "destructive" | "outline" | "success" | "warning" | "info" | "muted">): (_item: unknown, value: unknown) => React.JSX.Element | "—";
206
205
 
207
206
  type StatCardColor = "primary" | "success" | "warning" | "info" | "destructive" | "muted";
208
207
  interface StatCardProps {
@@ -224,7 +223,7 @@ interface StatCardProps {
224
223
  };
225
224
  className?: string;
226
225
  }
227
- declare function StatCard({ label, value, icon: Icon, color, href, linkComponent: Link, trend, className, }: StatCardProps): react_jsx_runtime.JSX.Element;
226
+ declare function StatCard({ label, value, icon: Icon, color, href, linkComponent: Link, trend, className, }: StatCardProps): React.JSX.Element;
228
227
 
229
228
  type ColumnStatus = "neutral" | "warning" | "success" | "info" | "destructive" | "primary";
230
229
  interface KanbanColumn<T> {
@@ -244,7 +243,7 @@ interface KanbanBoardProps<T> {
244
243
  emptyMessage?: string;
245
244
  className?: string;
246
245
  }
247
- declare function KanbanBoard<T>({ columns, renderCard, emptyMessage, className, }: KanbanBoardProps<T>): react_jsx_runtime.JSX.Element;
246
+ declare function KanbanBoard<T>({ columns, renderCard, emptyMessage, className, }: KanbanBoardProps<T>): React.JSX.Element;
248
247
 
249
248
  interface DataListProps<T> {
250
249
  items: T[];
@@ -252,7 +251,7 @@ interface DataListProps<T> {
252
251
  emptyMessage?: string;
253
252
  className?: string;
254
253
  }
255
- declare function DataList<T>({ items, renderItem, emptyMessage, className, }: DataListProps<T>): react_jsx_runtime.JSX.Element;
254
+ declare function DataList<T>({ items, renderItem, emptyMessage, className, }: DataListProps<T>): React.JSX.Element;
256
255
 
257
256
  /**
258
257
  * Props for {@link DetailPanel} — a shell-agnostic record detail view.
@@ -305,7 +304,7 @@ interface DetailPanelProps<T> {
305
304
  */
306
305
  layout?: "inline" | "dialog" | "sheet";
307
306
  }
308
- declare function DetailPanel<T extends Record<string, unknown>>({ item, open, onClose, title, subtitle, onEdit, onDelete, children, fields, width, layout, }: DetailPanelProps<T>): react_jsx_runtime.JSX.Element | null;
307
+ 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
308
 
310
309
  /**
311
310
  * Row-bound actions handed to `renderCard`, on top of the resource-wide
@@ -333,6 +332,26 @@ interface ResourceCardActions<T> extends ResourceActions<T> {
333
332
  * data-fetching, filtering, and layout; see also the built-in
334
333
  * `useKeyboardNavigation` wiring (`keyboardNavigation` prop) and `gridCols`.
335
334
  *
335
+ * Three levels of control, and each option is available at all three:
336
+ *
337
+ * 1. **The primitive offers it** — table / grid / list views, search, sort,
338
+ * status chips, column visibility and resizing, density, page size,
339
+ * detail panel / modal / route, built-in create and edit forms.
340
+ * 2. **The instance turns it on or off** — every one of those is a prop, so
341
+ * a given page decides which affordances it offers at all.
342
+ * 3. **The end user picks their own, and it sticks** — for the options left
343
+ * on at level 2, the user's choice persists to their own storage:
344
+ * `viewSettingsKey` for page size and density, `columnVisibilityKey` and
345
+ * `resizeKey` for column layout, `savedViews` (pair with `useSavedViews`)
346
+ * for named filter sets. Persistence needs a key; without one the choice
347
+ * lasts only for the session.
348
+ *
349
+ * A server-paginated list drives the same component through the controlled
350
+ * props (`page`, `perPage`, `onPageChange`, `onSortChange`, `onSearchChange`
351
+ * and friends) — supplying any of them stops ResourcePage from searching,
352
+ * sorting or paging the `data` a second time. See that block in
353
+ * {@link ResourcePageProps}.
354
+ *
336
355
  * @stability Beta
337
356
  */
338
357
  interface ResourcePageProps<T extends Record<string, unknown> = Record<string, unknown>> {
@@ -340,7 +359,17 @@ interface ResourcePageProps<T extends Record<string, unknown> = Record<string, u
340
359
  resource?: string;
341
360
  /** Server-data mode: pre-fetched data array. */
342
361
  data?: T[];
343
- /** Server-data mode: total record count (before client-side filtering). */
362
+ /**
363
+ * Server-data mode: total record count (before client-side filtering).
364
+ *
365
+ * Required whenever `onPageChange` is supplied. It cannot be derived from
366
+ * `data`, which holds only the current page. Omitted, `displayTotal` falls
367
+ * back to `0`, so the range span reads "No records" and `Next` stays
368
+ * disabled while the rows render directly below it. Rather than show a
369
+ * pager that contradicts the list, the component refuses to render the
370
+ * pager and shows an error instead. See {@link perPage} and
371
+ * {@link onPageChange}.
372
+ */
344
373
  total?: number;
345
374
  title: string;
346
375
  /** Page subtitle shown below the title. */
@@ -358,6 +387,16 @@ interface ResourcePageProps<T extends Record<string, unknown> = Record<string, u
358
387
  label: string;
359
388
  render?: (value: unknown, item: T) => ReactNode;
360
389
  }>;
390
+ /**
391
+ * Adds a status-chip filter. There is no dedicated status-change
392
+ * callback: chip clicks update the same filter state that already
393
+ * reports through `onFiltersChange`, whether or not the page is in
394
+ * server-controlled mode. A caller combining `statusFilter` with server
395
+ * pagination (`onPageChange` et al.) must supply `onFiltersChange` — see
396
+ * that prop — or status changes will update the chip UI without ever
397
+ * reaching the server. In server-controlled mode a status change also
398
+ * resets to page 1 via `onPageChange(1)` — see {@link onPageChange}.
399
+ */
361
400
  statusFilter?: string | {
362
401
  field: string;
363
402
  options?: string[];
@@ -366,6 +405,74 @@ interface ResourcePageProps<T extends Record<string, unknown> = Record<string, u
366
405
  searchPlaceholder?: string;
367
406
  initialFilters?: FilterParam[];
368
407
  onFiltersChange?: (filters: FilterParam[]) => void;
408
+ /** Current page, 1-based. Server-controlled mode only. */
409
+ page?: number;
410
+ /**
411
+ * Rows per page the server was asked for. Required whenever
412
+ * `onPageChange` is supplied: pager math (total pages, range start, Next)
413
+ * cannot be inferred from `data.length`, because a short final page is
414
+ * smaller than the real page size. Wins over the per-user page size
415
+ * persisted by `viewSettingsKey` — an explicit prop always beats a stored
416
+ * preference.
417
+ *
418
+ * Required whenever `onPageChange` is supplied. A short final page (the
419
+ * server's last page returning fewer rows than its page size) is
420
+ * indistinguishable from a small page size — there is no correct way to
421
+ * infer the divisor from the rows handed back. Rather than guess and
422
+ * render a wrong range or a stuck Next button, the component refuses to
423
+ * render the pager and shows an error instead when `onPageChange` is
424
+ * present without `perPage`.
425
+ *
426
+ * `total` is required on the same terms — see {@link total}. The error
427
+ * names every missing prop, not just the first.
428
+ */
429
+ perPage?: number;
430
+ /**
431
+ * Called with the next 1-based page number. The pager only renders when
432
+ * this is supplied AND both `perPage` and `total` are supplied — see
433
+ * {@link perPage} and {@link total}. Supplying this without either one
434
+ * renders an error naming the missing props instead of a pager.
435
+ *
436
+ * Status changes reach the server through `onFiltersChange` — not a
437
+ * dedicated callback here — because `statusFilter` state is tracked by
438
+ * the same filter machinery that already calls `onFiltersChange`
439
+ * regardless of controlled mode (see {@link statusFilter}). In
440
+ * server-controlled mode a status change also calls `onPageChange(1)`
441
+ * directly, since the previous page number is meaningless against a new
442
+ * filtered result set.
443
+ */
444
+ onPageChange?: (page: number) => void;
445
+ /** Called when the user picks a different page size in the header's view settings. */
446
+ onPerPageChange?: (perPage: number) => void;
447
+ /** Called when the user changes the sort, with `null` when the sort is cleared. */
448
+ onSortChange?: (sort: SortParam | null) => void;
449
+ /** Called when the user changes the search text (including clearing it). */
450
+ onSearchChange?: (search: string) => void;
451
+ /** Saved filter views to offer. Pair with `useSavedViews` for per-user storage. */
452
+ savedViews?: SavedViewItem[];
453
+ onLoadView?: (filters: Record<string, unknown>) => void;
454
+ onSaveView?: (name: string) => void;
455
+ onDeleteView?: (id: string) => void;
456
+ /** Show the "save current view" affordance. */
457
+ canSaveViews?: boolean;
458
+ /** Show the column-visibility dropdown. DataTable's default is `true`. */
459
+ columnVisibility?: boolean;
460
+ /** Persist key for the user's column-visibility picks. */
461
+ columnVisibilityKey?: string;
462
+ /** Allow drag-resizing columns. DataTable's default is `true`. */
463
+ resizableColumns?: boolean;
464
+ /** Persist key for the user's column widths. */
465
+ resizeKey?: string;
466
+ /** Keep the table header visible while scrolling. DataTable's default is `true`. */
467
+ stickyHeader?: boolean;
468
+ /** Keep the first column visible while scrolling horizontally. */
469
+ stickyFirstColumn?: boolean;
470
+ /**
471
+ * Page-size choices offered by `DataTable`'s own pager. Client-side mode
472
+ * only — server-controlled mode switches that pager off. (The header's
473
+ * view-settings popover has its own fixed 10/25/50/100 list.)
474
+ */
475
+ pageSizeOptions?: number[];
369
476
  onCreate?: () => void;
370
477
  onEdit?: (item: T) => void;
371
478
  onDelete?: (item: T) => void;
@@ -447,7 +554,7 @@ interface ResourcePageProps<T extends Record<string, unknown> = Record<string, u
447
554
  */
448
555
  viewSettingsKey?: string;
449
556
  }
450
- declare function ResourcePage<T extends Record<string, unknown> = Record<string, unknown>>(props: ResourcePageProps<T>): react_jsx_runtime.JSX.Element;
557
+ declare function ResourcePage<T extends Record<string, unknown> = Record<string, unknown>>(props: ResourcePageProps<T>): React.JSX.Element;
451
558
 
452
559
  interface ResourceDetailPageProps<T extends BaseRecord = BaseRecord> {
453
560
  /** Resource name, resolved through the mounted `DataProvider`. */
@@ -526,7 +633,7 @@ interface ResourceDetailPageProps<T extends BaseRecord = BaseRecord> {
526
633
  *
527
634
  * @stability Beta
528
635
  */
529
- declare function ResourceDetailPage<T extends BaseRecord = BaseRecord>({ resource, id, titleField, subtitleField, select, breadcrumb, actions, sidebar, children, className, testID, }: ResourceDetailPageProps<T>): react_jsx_runtime.JSX.Element;
636
+ declare function ResourceDetailPage<T extends BaseRecord = BaseRecord>({ resource, id, titleField, subtitleField, select, breadcrumb, actions, sidebar, children, className, testID, }: ResourceDetailPageProps<T>): React.JSX.Element;
530
637
 
531
638
  interface ResourceFormProps {
532
639
  resource: string;
@@ -550,7 +657,7 @@ interface ResourceFormProps {
550
657
  /** Number of columns for the form layout. Default 1. */
551
658
  columns?: 1 | 2;
552
659
  }
553
- declare function ResourceForm(props: ResourceFormProps): react_jsx_runtime.JSX.Element | null;
660
+ declare function ResourceForm(props: ResourceFormProps): React.JSX.Element | null;
554
661
 
555
662
  interface RelationFieldProps {
556
663
  field: ResourceFormFieldDef;
@@ -558,7 +665,7 @@ interface RelationFieldProps {
558
665
  onChange: (value: string) => void;
559
666
  disabled?: boolean;
560
667
  }
561
- declare function RelationField({ field, value, onChange, disabled, }: RelationFieldProps): react_jsx_runtime.JSX.Element;
668
+ declare function RelationField({ field, value, onChange, disabled, }: RelationFieldProps): React.JSX.Element;
562
669
 
563
670
  interface FileUploadFieldProps {
564
671
  field: ResourceFormFieldDef;
@@ -566,14 +673,14 @@ interface FileUploadFieldProps {
566
673
  onChange: (value: File | File[] | null) => void;
567
674
  disabled?: boolean;
568
675
  }
569
- declare function FileUploadField({ field, value, onChange, disabled, }: FileUploadFieldProps): react_jsx_runtime.JSX.Element;
676
+ declare function FileUploadField({ field, value, onChange, disabled, }: FileUploadFieldProps): React.JSX.Element;
570
677
 
571
678
  declare function FieldArrayRenderer({ field, control, action, watchedValues, }: {
572
679
  field: ResourceFormFieldDef;
573
680
  control: Control<Record<string, unknown>>;
574
681
  action: "create" | "edit";
575
682
  watchedValues?: Record<string, unknown>;
576
- }): react_jsx_runtime.JSX.Element;
683
+ }): React.JSX.Element;
577
684
 
578
685
  interface WizardFormProps {
579
686
  steps: WizardStep[];
@@ -585,7 +692,7 @@ interface WizardFormProps {
585
692
  action: "create" | "edit";
586
693
  onInvalid?: (errors: FieldErrors) => void;
587
694
  }
588
- declare function WizardForm({ steps, form, fields, onSubmit, isSaving, renderField, action, onInvalid, }: WizardFormProps): react_jsx_runtime.JSX.Element;
695
+ declare function WizardForm({ steps, form, fields, onSubmit, isSaving, renderField, action, onInvalid, }: WizardFormProps): React.JSX.Element;
589
696
 
590
697
  /**
591
698
  * @deprecated Import `AppShell` from `@marktiderman/genesis-ui/layout` instead.