@cosmicdrift/kumiko-renderer 0.389.0 → 0.390.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 (230) hide show
  1. package/dist/app/projection-detail-body.js +59 -394
  2. package/dist/app/projection-detail-header-model.d.ts +11 -0
  3. package/dist/app/projection-detail-header-model.js +33 -0
  4. package/dist/app/projection-detail-header-region.d.ts +28 -0
  5. package/dist/app/projection-detail-header-region.js +81 -0
  6. package/dist/app/projection-detail-header.d.ts +29 -0
  7. package/dist/app/projection-detail-header.js +84 -0
  8. package/dist/app/projection-detail-navigation.d.ts +13 -0
  9. package/dist/app/projection-detail-navigation.js +91 -0
  10. package/dist/app/projection-detail-states.d.ts +22 -0
  11. package/dist/app/projection-detail-states.js +26 -0
  12. package/dist/app/projection-drawer-host.d.ts +12 -0
  13. package/dist/app/projection-drawer-host.js +10 -0
  14. package/dist/app/projection-list-body.d.ts +1 -1
  15. package/dist/app/projection-list-body.js +69 -189
  16. package/dist/app/projection-list-render-props.d.ts +56 -0
  17. package/dist/app/projection-list-render-props.js +47 -0
  18. package/dist/app/projection-list-render.d.ts +26 -0
  19. package/dist/app/projection-list-render.js +20 -0
  20. package/dist/app/projection-list-row-click.d.ts +12 -0
  21. package/dist/app/projection-list-row-click.js +14 -0
  22. package/dist/app/use-projection-detail-actions.d.ts +35 -0
  23. package/dist/app/use-projection-detail-actions.js +156 -0
  24. package/dist/app/use-projection-detail-data.d.ts +19 -0
  25. package/dist/app/use-projection-detail-data.js +47 -0
  26. package/dist/app/use-projection-detail-layout.d.ts +17 -0
  27. package/dist/app/use-projection-detail-layout.js +23 -0
  28. package/dist/app/use-projection-list-actions.d.ts +31 -0
  29. package/dist/app/use-projection-list-actions.js +73 -0
  30. package/dist/app/use-projection-list-data.d.ts +20 -0
  31. package/dist/app/use-projection-list-data.js +26 -0
  32. package/dist/app/use-projection-list-definitions.d.ts +11 -0
  33. package/dist/app/use-projection-list-definitions.js +20 -0
  34. package/dist/app/use-projection-list-query.d.ts +28 -0
  35. package/dist/app/use-projection-list-query.js +83 -0
  36. package/dist/components/render-edit-banners.d.ts +12 -0
  37. package/dist/components/render-edit-banners.js +90 -0
  38. package/dist/components/render-edit-draft-save-helpers.d.ts +19 -0
  39. package/dist/components/render-edit-draft-save-helpers.js +42 -0
  40. package/dist/components/render-edit-field-grid.d.ts +4 -0
  41. package/dist/components/render-edit-field-grid.js +40 -0
  42. package/dist/components/render-edit-fields-section.d.ts +4 -0
  43. package/dist/components/render-edit-fields-section.js +76 -0
  44. package/dist/components/render-edit-form-actions.d.ts +4 -0
  45. package/dist/components/render-edit-form-actions.js +66 -0
  46. package/dist/components/render-edit-header-actions.d.ts +11 -0
  47. package/dist/components/render-edit-header-actions.js +120 -0
  48. package/dist/components/render-edit-layout-flags.d.ts +29 -0
  49. package/dist/components/render-edit-layout-flags.js +106 -0
  50. package/dist/components/render-edit-layout.d.ts +4 -0
  51. package/dist/components/render-edit-layout.js +79 -0
  52. package/dist/components/render-edit-render-model.d.ts +63 -0
  53. package/dist/components/render-edit-render-model.js +14 -0
  54. package/dist/components/render-edit-section-kinds.d.ts +4 -0
  55. package/dist/components/render-edit-section-kinds.js +115 -0
  56. package/dist/components/render-edit-submit-result.d.ts +8 -0
  57. package/dist/components/render-edit-submit-result.js +77 -0
  58. package/dist/components/render-edit-submit.d.ts +7 -0
  59. package/dist/components/render-edit-submit.js +113 -0
  60. package/dist/components/render-edit-titles.d.ts +17 -0
  61. package/dist/components/render-edit-titles.js +99 -0
  62. package/dist/components/render-edit-wizard-navigation.d.ts +11 -0
  63. package/dist/components/render-edit-wizard-navigation.js +115 -0
  64. package/dist/components/render-edit.d.ts +2 -2
  65. package/dist/components/render-edit.js +63 -1128
  66. package/dist/components/render-field-choice-inputs.d.ts +7 -0
  67. package/dist/components/render-field-choice-inputs.js +52 -0
  68. package/dist/components/render-field-control.d.ts +12 -0
  69. package/dist/components/render-field-control.js +93 -0
  70. package/dist/components/render-field-display-text.d.ts +4 -0
  71. package/dist/components/render-field-display-text.js +48 -0
  72. package/dist/components/render-field-fill-action.d.ts +13 -0
  73. package/dist/components/render-field-fill-action.js +63 -0
  74. package/dist/components/render-field-input-shared.d.ts +39 -0
  75. package/dist/components/render-field-input-shared.js +20 -0
  76. package/dist/components/render-field-input.d.ts +3 -0
  77. package/dist/components/render-field-input.js +34 -0
  78. package/dist/components/render-field-meta.d.ts +6 -0
  79. package/dist/components/render-field-meta.js +51 -0
  80. package/dist/components/render-field-read-only.d.ts +11 -0
  81. package/dist/components/render-field-read-only.js +75 -0
  82. package/dist/components/render-field-reference.d.ts +15 -0
  83. package/dist/components/render-field-reference.js +103 -0
  84. package/dist/components/render-field-scalar-inputs.d.ts +10 -0
  85. package/dist/components/render-field-scalar-inputs.js +35 -0
  86. package/dist/components/render-field-text-inputs.d.ts +3 -0
  87. package/dist/components/render-field-text-inputs.js +34 -0
  88. package/dist/components/render-field-values.d.ts +18 -0
  89. package/dist/components/render-field-values.js +111 -0
  90. package/dist/components/render-field-write-only.d.ts +9 -0
  91. package/dist/components/render-field-write-only.js +29 -0
  92. package/dist/components/render-field.d.ts +2 -2
  93. package/dist/components/render-field.js +39 -670
  94. package/dist/components/render-list-header-slot.d.ts +5 -0
  95. package/dist/components/render-list-header-slot.js +30 -0
  96. package/dist/components/render-list-reference-bridge.d.ts +3 -0
  97. package/dist/components/render-list-reference-bridge.js +25 -0
  98. package/dist/components/render-list-reference-columns.d.ts +25 -0
  99. package/dist/components/render-list-reference-columns.js +137 -0
  100. package/dist/components/render-list-table-props.d.ts +52 -0
  101. package/dist/components/render-list-table-props.js +53 -0
  102. package/dist/components/render-list-toolbar-model.d.ts +42 -0
  103. package/dist/components/render-list-toolbar-model.js +82 -0
  104. package/dist/components/render-list-toolbar.d.ts +38 -0
  105. package/dist/components/render-list-toolbar.js +40 -0
  106. package/dist/components/render-list-types.d.ts +113 -0
  107. package/dist/components/render-list-types.js +1 -0
  108. package/dist/components/render-list.d.ts +4 -113
  109. package/dist/components/render-list.js +91 -307
  110. package/dist/components/use-reference-input-state.d.ts +20 -0
  111. package/dist/components/use-reference-input-state.js +85 -0
  112. package/dist/components/use-render-edit-controlled-sync.d.ts +3 -0
  113. package/dist/components/use-render-edit-controlled-sync.js +26 -0
  114. package/dist/components/use-render-edit-draft-save.d.ts +29 -0
  115. package/dist/components/use-render-edit-draft-save.js +141 -0
  116. package/dist/components/use-render-edit-form.d.ts +10 -0
  117. package/dist/components/use-render-edit-form.js +101 -0
  118. package/dist/components/use-render-edit-sections.d.ts +15 -0
  119. package/dist/components/use-render-edit-sections.js +100 -0
  120. package/dist/components/use-render-edit-state.d.ts +65 -0
  121. package/dist/components/use-render-edit-state.js +158 -0
  122. package/dist/components/use-render-edit-steps.d.ts +13 -0
  123. package/dist/components/use-render-edit-steps.js +52 -0
  124. package/dist/components/use-render-list-columns.d.ts +18 -0
  125. package/dist/components/use-render-list-columns.js +41 -0
  126. package/dist/components/use-render-list-search.d.ts +4 -0
  127. package/dist/components/use-render-list-search.js +50 -0
  128. package/dist/primitives.d.ts +1 -1
  129. package/dist/web/app/dashboard-body.d.ts +1 -1
  130. package/dist/web/app/dashboard-body.js +42 -246
  131. package/dist/web/app/dashboard-header.d.ts +34 -0
  132. package/dist/web/app/dashboard-header.js +58 -0
  133. package/dist/web/app/dashboard-screen-controls.d.ts +11 -0
  134. package/dist/web/app/dashboard-screen-controls.js +42 -0
  135. package/dist/web/app/dashboard-screen-tiles.d.ts +8 -0
  136. package/dist/web/app/dashboard-screen-tiles.js +42 -0
  137. package/dist/web/app/dashboard-visibility.d.ts +42 -0
  138. package/dist/web/app/dashboard-visibility.js +102 -0
  139. package/dist/web/app/use-dashboard-screen-state.d.ts +35 -0
  140. package/dist/web/app/use-dashboard-screen-state.js +69 -0
  141. package/dist/web/primitives/data-table/actions-column.js +6 -5
  142. package/dist/web/primitives/data-table/card-body.d.ts +6 -0
  143. package/dist/web/primitives/data-table/card-body.js +32 -0
  144. package/dist/web/primitives/data-table/card-row.d.ts +5 -0
  145. package/dist/web/primitives/data-table/card-row.js +31 -0
  146. package/dist/web/primitives/data-table/card-view.d.ts +3 -0
  147. package/dist/web/primitives/data-table/card-view.js +13 -0
  148. package/dist/web/primitives/data-table/column-resize-handle.js +3 -1
  149. package/dist/web/primitives/data-table/data-cell-props.d.ts +7 -0
  150. package/dist/web/primitives/data-table/data-cell-props.js +8 -0
  151. package/dist/web/primitives/data-table/row-render-parts.d.ts +8 -0
  152. package/dist/web/primitives/data-table/row-render-parts.js +35 -0
  153. package/dist/web/primitives/data-table/table-body.d.ts +3 -0
  154. package/dist/web/primitives/data-table/table-body.js +32 -0
  155. package/dist/web/primitives/data-table/table-content.d.ts +3 -0
  156. package/dist/web/primitives/data-table/table-content.js +41 -0
  157. package/dist/web/primitives/data-table/table-data-cell.d.ts +4 -0
  158. package/dist/web/primitives/data-table/table-data-cell.js +25 -0
  159. package/dist/web/primitives/data-table/table-header-row.d.ts +3 -0
  160. package/dist/web/primitives/data-table/table-header-row.js +21 -0
  161. package/dist/web/primitives/data-table/table-view.d.ts +3 -0
  162. package/dist/web/primitives/data-table/table-view.js +25 -0
  163. package/dist/web/primitives/data-table/toolbar.d.ts +6 -0
  164. package/dist/web/primitives/data-table/toolbar.js +53 -0
  165. package/dist/web/primitives/data-table/use-data-table-state.d.ts +44 -0
  166. package/dist/web/primitives/data-table/use-data-table-state.js +123 -0
  167. package/dist/web/primitives/data-table.d.ts +3 -4
  168. package/dist/web/primitives/data-table.js +32 -252
  169. package/dist/web/primitives/file-value-list.js +1 -1
  170. package/dist/web/primitives/form-footer-parts.d.ts +25 -0
  171. package/dist/web/primitives/form-footer-parts.js +55 -0
  172. package/dist/web/primitives/form.js +33 -43
  173. package/dist/web/primitives/input-choice-kinds.d.ts +4 -0
  174. package/dist/web/primitives/input-choice-kinds.js +67 -0
  175. package/dist/web/primitives/input-file-kinds.d.ts +3 -0
  176. package/dist/web/primitives/input-file-kinds.js +6 -0
  177. package/dist/web/primitives/input-number-kinds.d.ts +4 -0
  178. package/dist/web/primitives/input-number-kinds.js +16 -0
  179. package/dist/web/primitives/input-optional-props.d.ts +20 -0
  180. package/dist/web/primitives/input-optional-props.js +24 -0
  181. package/dist/web/primitives/input-select-presentation.d.ts +13 -0
  182. package/dist/web/primitives/input-select-presentation.js +19 -0
  183. package/dist/web/primitives/input-shared.d.ts +22 -0
  184. package/dist/web/primitives/input-shared.js +10 -0
  185. package/dist/web/primitives/input-temporal-kinds.d.ts +5 -0
  186. package/dist/web/primitives/input-temporal-kinds.js +10 -0
  187. package/dist/web/primitives/input-text-kinds.d.ts +7 -0
  188. package/dist/web/primitives/input-text-kinds.js +33 -0
  189. package/dist/web/primitives/input.js +35 -161
  190. package/dist/web/widgets/chart-data.d.ts +35 -0
  191. package/dist/web/widgets/chart-data.js +37 -0
  192. package/dist/web/widgets/chart-markers.d.ts +26 -0
  193. package/dist/web/widgets/chart-markers.js +64 -0
  194. package/dist/web/widgets/chart-parts.d.ts +20 -0
  195. package/dist/web/widgets/chart-parts.js +15 -0
  196. package/dist/web/widgets/charts.d.ts +5 -78
  197. package/dist/web/widgets/charts.js +7 -485
  198. package/dist/web/widgets/drawer-panel-class.d.ts +3 -0
  199. package/dist/web/widgets/drawer-panel-class.js +40 -0
  200. package/dist/web/widgets/drawer-resize-controls.d.ts +11 -0
  201. package/dist/web/widgets/drawer-resize-controls.js +12 -0
  202. package/dist/web/widgets/drawer-sizing.d.ts +19 -0
  203. package/dist/web/widgets/drawer-sizing.js +60 -0
  204. package/dist/web/widgets/drawer.js +17 -116
  205. package/dist/web/widgets/stacked-area-brush.d.ts +13 -0
  206. package/dist/web/widgets/stacked-area-brush.js +91 -0
  207. package/dist/web/widgets/stacked-area-chart.d.ts +6 -0
  208. package/dist/web/widgets/stacked-area-chart.js +45 -0
  209. package/dist/web/widgets/stacked-area-chrome.d.ts +7 -0
  210. package/dist/web/widgets/stacked-area-chrome.js +77 -0
  211. package/dist/web/widgets/stacked-area-geometry.d.ts +54 -0
  212. package/dist/web/widgets/stacked-area-geometry.js +83 -0
  213. package/dist/web/widgets/stacked-area-model.d.ts +11 -0
  214. package/dist/web/widgets/stacked-area-model.js +1 -0
  215. package/dist/web/widgets/stacked-area-plot.d.ts +3 -0
  216. package/dist/web/widgets/stacked-area-plot.js +51 -0
  217. package/dist/web/widgets/stacked-area-props.d.ts +83 -0
  218. package/dist/web/widgets/stacked-area-props.js +16 -0
  219. package/dist/web/widgets/stacked-area-svg.d.ts +3 -0
  220. package/dist/web/widgets/stacked-area-svg.js +38 -0
  221. package/dist/web/widgets/stacked-area-tooltip.d.ts +3 -0
  222. package/dist/web/widgets/stacked-area-tooltip.js +86 -0
  223. package/dist/web/widgets/use-drawer-resize.d.ts +22 -0
  224. package/dist/web/widgets/use-drawer-resize.js +52 -0
  225. package/dist/web/widgets/use-stacked-area-tooltip.d.ts +9 -0
  226. package/dist/web/widgets/use-stacked-area-tooltip.js +34 -0
  227. package/dist/web/widgets/use-stacked-area-window.d.ts +20 -0
  228. package/dist/web/widgets/use-stacked-area-window.js +76 -0
  229. package/package.json +9 -9
  230. package/src/changes.json +54 -0
@@ -0,0 +1,21 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cn } from "../../lib/cn.js";
3
+ import { TableHead, TableHeader, TableRow } from "../../ui/table.js";
4
+ import { NUMERIC_COLUMN_TYPES } from "../data-table-cell.js";
5
+ import { ActionsHeaderCell } from "./actions-column.js";
6
+ import { ColumnResizeHandle } from "./column-resize-handle.js";
7
+ import { SortableHeader } from "./sortable-header.js";
8
+ import { EXPAND_TOGGLE_CELL_CLASS } from "./table-chrome.js";
9
+ import { EXPAND_COLUMN_KEY, SELECT_COLUMN_KEY } from "./use-column-pins.js";
10
+ function renderColumnHeader(m, col) {
11
+ const { sizing, sort, onSortChange } = m;
12
+ const headerPin = m.pins.headerCell(col.field);
13
+ return (_jsx(SortableHeader, { field: col.field, label: col.label, sortable: col.sortable === true, highlighted: col.highlighted === true, numeric: NUMERIC_COLUMN_TYPES.has(col.type), fitContent: col.fileDisplay !== undefined, className: cn(m.density.header, headerPin.className), style: headerPin.style, columnField: headerPin.columnField, trailing: sizing && _jsx(ColumnResizeHandle, { field: col.field, label: col.label, sizing: sizing }), ...(sort !== undefined && sort !== null && { sort }), ...(onSortChange !== undefined && { onSortChange }) }, col.field));
14
+ }
15
+ function renderExpandHeaderCell(m) {
16
+ const expandHeaderPin = m.pins.headerCell(EXPAND_COLUMN_KEY);
17
+ return (_jsx(TableHead, { "data-testid": "column-expand", className: cn(EXPAND_TOGGLE_CELL_CLASS, expandHeaderPin.className), style: expandHeaderPin.style }));
18
+ }
19
+ export function renderTableHeader(m) {
20
+ return (_jsx(TableHeader, { className: "bg-muted", children: _jsxs(TableRow, { ref: m.headerRowRef, className: "hover:bg-transparent", children: [m.selection.headerCell(m.pins.headerCell(SELECT_COLUMN_KEY)), m.expansion !== undefined && renderExpandHeaderCell(m), m.columns.map((col) => renderColumnHeader(m, col)), m.hasTableActions && (_jsx(ActionsHeaderCell, { label: m.tableTranslate?.("kumiko.list.actions") ?? "Actions", style: m.pins.actionsHeaderStyle, absorbsLeftover: m.sizing !== undefined }))] }) }));
21
+ }
@@ -0,0 +1,3 @@
1
+ import type { ReactNode } from "react";
2
+ import type { DataTableModel } from "./use-data-table-state.ts";
3
+ export declare function renderTableView(m: DataTableModel): ReactNode;
@@ -0,0 +1,25 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cn } from "../../lib/cn.js";
3
+ import { Table } from "../../ui/table.js";
4
+ import { ColumnWidthGroup } from "./column-resize-handle.js";
5
+ import { renderTableBody } from "./table-body.js";
6
+ import { FILL_TABLE_CLASS, FlushTable } from "./table-chrome.js";
7
+ import { renderTableHeader } from "./table-header-row.js";
8
+ import { TotalsFooterRow } from "./totals-footer.js";
9
+ function renderColumnWidths(m) {
10
+ const { sizing } = m;
11
+ if (sizing === undefined)
12
+ return null;
13
+ return (_jsx(ColumnWidthGroup, { fields: m.columns.map((col) => col.field), sizing: sizing, hasExpandColumn: m.expansion !== undefined, hasSelectColumn: m.selection.active, hasActionsColumn: m.hasTableActions }));
14
+ }
15
+ function renderTotalsRow(m) {
16
+ const { totals } = m;
17
+ if (totals === undefined)
18
+ return null;
19
+ return (_jsx(TotalsFooterRow, { totals: totals, columns: m.columns, cellPin: m.pins.bodyCell, leadingCells: { select: m.selection.active, expand: m.expansion !== undefined }, actionsCell: m.hasTableActions ? { fillsHeight: m.fillsHeight } : undefined, cellClassName: m.density.cell }));
20
+ }
21
+ export function renderTableView(m) {
22
+ const { fillsHeight, chromeless, sizing, testId } = m;
23
+ const TableRoot = fillsHeight ? FlushTable : Table;
24
+ return (_jsx("div", { className: cn(!fillsHeight && "overflow-hidden", !fillsHeight && chromeless !== true && "rounded-lg border bg-card"), children: _jsxs(TableRoot, { "data-testid": testId, className: fillsHeight ? FILL_TABLE_CLASS : undefined, ...(sizing !== undefined && { style: sizing.tableStyle }), children: [renderColumnWidths(m), renderTableHeader(m), renderTableBody(m), renderTotalsRow(m)] }) }));
25
+ }
@@ -0,0 +1,6 @@
1
+ import type { ReactNode } from "react";
2
+ import type { DataTableModel } from "./use-data-table-state.ts";
3
+ export declare function buildModelFacetCluster(m: DataTableModel): ReactNode;
4
+ export declare function hasToolbarContent(m: DataTableModel, facetCluster: ReactNode): boolean;
5
+ export declare function renderToolbar(m: DataTableModel, facetCluster: ReactNode): ReactNode;
6
+ export declare function renderToolbarDescription(m: DataTableModel, hasToolbar: boolean): ReactNode;
@@ -0,0 +1,53 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Search, SlidersHorizontal } from "lucide-react";
3
+ import { UNIFORM_BAR_CONTROLS_CLASSES } from "../../layout/uniform-bar-controls.js";
4
+ import { cn } from "../../lib/cn.js";
5
+ import { Button as UiButton } from "../../ui/button.js";
6
+ import { buildFacetCluster } from "./facet-cluster.js";
7
+ import { ToolbarEndCluster, toolbarEndHasContent } from "./toolbar-end-cluster.js";
8
+ export function buildModelFacetCluster(m) {
9
+ return buildFacetCluster({
10
+ filterFacets: m.filterFacets,
11
+ filterValues: m.filterValues,
12
+ onFilterChange: m.onFilterChange,
13
+ onFilterReset: m.onFilterReset,
14
+ dateRangeFacets: m.dateRangeFacets,
15
+ onDateRangeChange: m.onDateRangeChange,
16
+ rangeFacets: m.rangeFacets,
17
+ onRangeChange: m.onRangeChange,
18
+ resetAll: m.resetAll,
19
+ notice: m.notice,
20
+ dense: m.fillsHeight,
21
+ });
22
+ }
23
+ export function hasToolbarContent(m, facetCluster) {
24
+ return (m.toolbarStart !== undefined ||
25
+ facetCluster !== undefined ||
26
+ toolbarEndHasContent(m.view, m.exportList, m.toolbarEnd));
27
+ }
28
+ function toolbarClassName({ fillsHeight, isNarrow }) {
29
+ if (!fillsHeight)
30
+ return cn("flex flex-wrap items-center", UNIFORM_BAR_CONTROLS_CLASSES, "gap-3");
31
+ return cn("flex flex-wrap items-center", UNIFORM_BAR_CONTROLS_CLASSES, isNarrow ? "shrink-0 gap-2 p-3" : "min-h-13 shrink-0 gap-2 px-6 py-2 md:py-0");
32
+ }
33
+ // min-w-48: without a floor, flex shrinks the search instead of wrapping the facet cluster onto its own line.
34
+ function renderToolbarStart(m, toolbarStart) {
35
+ return (_jsxs("div", { className: cn(m.fillsHeight
36
+ ? "relative w-full md:w-[300px] max-md:flex-1 [&_input]:border-border-strong [&_input]:pl-8 [&_input]:max-md:h-11"
37
+ : "flex-1 min-w-48 max-w-sm"), children: [m.fillsHeight && (_jsx(Search, { className: "pointer-events-none absolute left-2.5 top-1/2 z-10 size-4 -translate-y-1/2 text-muted-foreground", "aria-hidden": "true" })), toolbarStart] }));
38
+ }
39
+ function renderFacetToggle(m, facetCluster) {
40
+ const { testId, facetsOpenNarrow, setFacetsOpenNarrow } = m;
41
+ return (_jsxs(_Fragment, { children: [_jsx(UiButton, { type: "button", variant: "outline", size: "icon", className: "size-11", "aria-label": m.tableTranslate?.("kumiko.list.filter.toggle") ?? "Filter", "aria-expanded": facetsOpenNarrow, "data-testid": testId !== undefined ? `${testId}-filter-toggle` : "filter-toggle", onClick: () => setFacetsOpenNarrow((open) => !open), children: _jsx(SlidersHorizontal, { className: "size-4", "aria-hidden": "true" }) }), facetsOpenNarrow && _jsx("div", { className: "w-full", children: facetCluster })] }));
42
+ }
43
+ export function renderToolbar(m, facetCluster) {
44
+ const { testId, toolbarStart } = m;
45
+ const collapsesFacetsBehindButton = m.isNarrow && m.fillsHeight && facetCluster !== undefined;
46
+ return (_jsxs("div", { "data-testid": testId !== undefined ? `${testId}-toolbar` : "render-list-toolbar", className: toolbarClassName(m), children: [toolbarStart !== undefined && renderToolbarStart(m, toolbarStart), collapsesFacetsBehindButton ? renderFacetToggle(m, facetCluster) : facetCluster, _jsx(ToolbarEndCluster, { view: m.view, columnLabels: new Map(m.schemaColumns.map((col) => [col.field, col.label])), exportList: m.exportList, toolbarEnd: m.toolbarEnd })] }));
47
+ }
48
+ export function renderToolbarDescription(m, hasToolbar) {
49
+ const { testId, toolbarDescription, fillsHeight, isNarrow } = m;
50
+ if (toolbarDescription === undefined)
51
+ return null;
52
+ return (_jsx("p", { "data-testid": testId !== undefined ? `${testId}-description` : "render-list-description", className: cn("m-0 pb-2 text-sm text-muted-foreground", fillsHeight && (isNarrow ? "px-3" : "px-6"), fillsHeight && !hasToolbar && "pt-4"), children: toolbarDescription }));
53
+ }
@@ -0,0 +1,44 @@
1
+ import { type Dispatch, type SetStateAction } from "react";
2
+ import type { ListColumnViewModel, ListRowViewModel } from "../../../headless/index.ts";
3
+ import { type DataTableProps, useOptionalLocale, useOptionalTranslation } from "../../../index.ts";
4
+ import { densityClasses } from "./density.ts";
5
+ import { buildDataTableItems } from "./group-band.tsx";
6
+ import { useBulkSelection } from "./use-bulk-selection.tsx";
7
+ import { type ColumnPins } from "./use-column-pins.ts";
8
+ import { useColumnSizing } from "./use-column-sizing.ts";
9
+ import { useMeasuredColumnWidths } from "./use-measured-column-widths.ts";
10
+ import { useRowKeyboardNavigation } from "./use-row-keyboard-navigation.ts";
11
+ type RowExpansion = {
12
+ readonly renderExpandedRow: NonNullable<DataTableProps["renderExpandedRow"]>;
13
+ readonly onToggleRowExpanded: NonNullable<DataTableProps["onToggleRowExpanded"]>;
14
+ };
15
+ export type DataTableModel = Omit<DataTableProps, "columns"> & {
16
+ readonly schemaColumns: readonly ListColumnViewModel[];
17
+ readonly columns: ColumnPins<ListColumnViewModel>["columns"];
18
+ readonly tableTranslate: ReturnType<typeof useOptionalTranslation>;
19
+ readonly tableLocale: ReturnType<typeof useOptionalLocale>;
20
+ readonly isNarrow: boolean;
21
+ readonly density: ReturnType<typeof densityClasses>;
22
+ readonly facetsOpenNarrow: boolean;
23
+ readonly setFacetsOpenNarrow: Dispatch<SetStateAction<boolean>>;
24
+ readonly menuActions: DataTableProps["rowActions"];
25
+ readonly tableActions: DataTableProps["rowActions"];
26
+ readonly rowClickLabel: string | undefined;
27
+ readonly rowOpener: (row: ListRowViewModel) => (() => void) | undefined;
28
+ readonly hasTableActions: boolean;
29
+ readonly isEmpty: boolean;
30
+ readonly rowTestId: (row: ListRowViewModel) => string;
31
+ readonly expansion: RowExpansion | undefined;
32
+ readonly fillsHeight: boolean;
33
+ readonly selection: ReturnType<typeof useBulkSelection>;
34
+ readonly headerRowRef: ReturnType<typeof useMeasuredColumnWidths>["headerRowRef"];
35
+ readonly pins: ColumnPins<ListColumnViewModel>;
36
+ readonly sizing: ReturnType<typeof useColumnSizing>;
37
+ readonly fullColSpan: number;
38
+ readonly expansionDomId: (row: ListRowViewModel) => string;
39
+ readonly tableItems: ReturnType<typeof buildDataTableItems>;
40
+ readonly keyboardRow: ReturnType<typeof useRowKeyboardNavigation>;
41
+ readonly columnOf: (field: string) => ListColumnViewModel | undefined;
42
+ };
43
+ export declare function useDataTableState(props: DataTableProps): DataTableModel;
44
+ export {};
@@ -0,0 +1,123 @@
1
+ import { useId, useState } from "react";
2
+ import { useOptionalLocale, useOptionalTranslation } from "../../../index.js";
3
+ import { useIsNarrowViewport } from "../use-narrow-viewport.js";
4
+ import { isPinningActive, isResizingActive } from "./column-layout-mode.js";
5
+ import { densityClasses } from "./density.js";
6
+ import { buildDataTableItems } from "./group-band.js";
7
+ import { useBulkSelection } from "./use-bulk-selection.js";
8
+ import { useColumnPins } from "./use-column-pins.js";
9
+ import { useColumnSizing } from "./use-column-sizing.js";
10
+ import { useMeasuredColumnWidths } from "./use-measured-column-widths.js";
11
+ import { useRowKeyboardNavigation } from "./use-row-keyboard-navigation.js";
12
+ import { useViewColumns } from "./use-view-columns.js";
13
+ function resolveRowActions({ rowActions, onRowClick, rowActionMode, onCellChange, }) {
14
+ // The row click already runs rowClick actions, so with a clickable row they
15
+ // must not reappear in the kebab (a list whose only action is the rowClick
16
+ // one gets no actions column at all).
17
+ const menuActions = rowActions !== undefined && onRowClick !== undefined
18
+ ? rowActions.filter((action) => action.rowClick !== true)
19
+ : rowActions;
20
+ // The table keeps every action in "inline" mode, and with editable cells the
21
+ // first cell is not a keyboard link (FirstCellLink is off), so the kebab is
22
+ // then the only keyboard route to the rowClick action.
23
+ const tableActions = rowActionMode === "inline" || onCellChange !== undefined ? rowActions : menuActions;
24
+ const rowClickAction = rowActions?.find((action) => action.rowClick === true);
25
+ const rowClickIsHidden = (row) => rowClickAction?.isVisible !== undefined && !rowClickAction.isVisible(row);
26
+ const rowOpener = (row) => onRowClick !== undefined && !rowClickIsHidden(row) ? () => onRowClick(row) : undefined;
27
+ return { menuActions, tableActions, rowClickLabel: rowClickAction?.label, rowOpener };
28
+ }
29
+ function resolveRowExpansion({ renderExpandedRow, onToggleRowExpanded, }) {
30
+ if (renderExpandedRow === undefined || onToggleRowExpanded === undefined)
31
+ return undefined;
32
+ return { renderExpandedRow, onToggleRowExpanded };
33
+ }
34
+ function isEmptyTable({ rows, serverGroups }) {
35
+ return rows.length === 0 && (serverGroups?.groups.length ?? 0) === 0;
36
+ }
37
+ export function useDataTableState(props) {
38
+ const { columns: schemaColumns, rows, view, scrollBody, serverGroups, bulk, getRowTestId, } = props;
39
+ // One locale/translate subscription per table — not per cell.
40
+ // Optional hooks: a bare DataTable outside LocaleProvider must not crash.
41
+ const tableTranslate = useOptionalTranslation();
42
+ const tableLocale = useOptionalLocale();
43
+ // Below 768px a table scrolls its columns out of reach with no visible
44
+ // affordance. Cards replace the table entirely below the breakpoint — same
45
+ // single-mount pattern as EmbeddedListInput/embedded-list-input.tsx.
46
+ const isNarrow = useIsNarrowViewport();
47
+ const viewColumns = useViewColumns(schemaColumns, view, isNarrow);
48
+ const density = densityClasses(view, scrollBody === true);
49
+ const [facetsOpenNarrow, setFacetsOpenNarrow] = useState(false);
50
+ const { menuActions, tableActions, rowClickLabel, rowOpener } = resolveRowActions(props);
51
+ const hasTableActions = tableActions !== undefined && tableActions.length > 0;
52
+ const isEmpty = isEmptyTable(props);
53
+ const expansionIdPrefix = useId();
54
+ const rowTestId = (row) => getRowTestId?.(row) ?? `row-${row.id}`;
55
+ const expansion = resolveRowExpansion(props);
56
+ const isRowExpandable = expansion !== undefined;
57
+ const fillsHeight = scrollBody === true;
58
+ const selection = useBulkSelection({
59
+ bulk,
60
+ rows,
61
+ titleField: schemaColumns[0]?.field,
62
+ rowTestId,
63
+ flush: fillsHeight,
64
+ });
65
+ const { headerRowRef, measured } = useMeasuredColumnWidths(isPinningActive(view, isNarrow) || isResizingActive(view, isNarrow));
66
+ const pins = useColumnPins({
67
+ columns: viewColumns,
68
+ view,
69
+ isNarrow,
70
+ measured,
71
+ hasActionsColumn: hasTableActions,
72
+ hasExpandColumn: isRowExpandable,
73
+ hasSelectColumn: selection.active,
74
+ fillsHeight,
75
+ });
76
+ const columns = pins.columns;
77
+ const sizing = useColumnSizing({
78
+ columns,
79
+ view,
80
+ isNarrow,
81
+ measured,
82
+ hasExpandColumn: isRowExpandable,
83
+ hasSelectColumn: selection.active,
84
+ });
85
+ const fullColSpan = columns.length + [isRowExpandable, hasTableActions, selection.active].filter(Boolean).length;
86
+ const tableItems = buildDataTableItems(rows, serverGroups);
87
+ const keyboardRow = useRowKeyboardNavigation({
88
+ view,
89
+ isNarrow,
90
+ rowIds: tableItems.flatMap((item) => (item.kind === "row" ? [item.row.id] : [])),
91
+ onSelect: selection.onKeyboardSelect,
92
+ });
93
+ const schemaColumnByField = new Map(schemaColumns.map((col) => [col.field, col]));
94
+ return {
95
+ ...props,
96
+ schemaColumns,
97
+ columns,
98
+ tableTranslate,
99
+ tableLocale,
100
+ isNarrow,
101
+ density,
102
+ facetsOpenNarrow,
103
+ setFacetsOpenNarrow,
104
+ menuActions,
105
+ tableActions,
106
+ rowClickLabel,
107
+ rowOpener,
108
+ hasTableActions,
109
+ isEmpty,
110
+ rowTestId,
111
+ expansion,
112
+ fillsHeight,
113
+ selection,
114
+ headerRowRef,
115
+ pins,
116
+ sizing,
117
+ fullColSpan,
118
+ expansionDomId: (row) => `${expansionIdPrefix}-${row.id}`,
119
+ tableItems,
120
+ keyboardRow,
121
+ columnOf: (field) => schemaColumnByField.get(field),
122
+ };
123
+ }
@@ -1,4 +1,3 @@
1
- import { type ReactNode } from "react";
2
- import { type DataTableProps } from "../../index.ts";
3
- import "../react-css-custom-properties.ts";
4
- export declare function DefaultDataTable({ columns: schemaColumns, rows, onRowClick, sort, onSortChange, emptyState, toolbarStart, toolbarDescription, toolbarEnd, pager, onReachEnd, loadingMore, hasMore, itemNoun, rowActions, rowActionMode, bulk, rowTone, filterFacets, filterValues, onFilterChange, onFilterReset, dateRangeFacets, onDateRangeChange, testId, onCellChange, getRowTestId, getCellTestId, chromeless, scrollBody, screenPadding, expandedRowIds, onToggleRowExpanded, renderExpandedRow, view, resetAll, exportList, serverGroups, totals, notice, rangeFacets, onRangeChange, }: DataTableProps): ReactNode;
1
+ import type { ReactNode } from "react";
2
+ import type { DataTableProps } from "../../index.ts";
3
+ export declare function DefaultDataTable(props: DataTableProps): ReactNode;
@@ -1,257 +1,37 @@
1
- import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { cardColumnRoles } from "@cosmicdrift/kumiko-framework/ui-types";
3
- import { ChevronRight, Search, SlidersHorizontal } from "lucide-react";
4
- import { Fragment, useId, useState } from "react";
5
- import { useOptionalLocale, useOptionalTranslation } from "../../index.js";
6
- import { UNIFORM_BAR_CONTROLS_CLASSES } from "../layout/uniform-bar-controls.js";
1
+ import { jsxs as _jsxs } from "react/jsx-runtime";
7
2
  import { cn } from "../lib/cn.js";
8
- import { Button as UiButton } from "../ui/button.js";
9
- import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../ui/table.js";
10
- import { CardLeadingImage } from "./file-thumbnail.js";
3
+ import { renderTableContent } from "./data-table/table-content.js";
4
+ import { buildModelFacetCluster, hasToolbarContent, renderToolbar, renderToolbarDescription, } from "./data-table/toolbar.js";
5
+ import { useDataTableState } from "./data-table/use-data-table-state.js";
11
6
  import { screenPaddingClassName } from "./layout.js";
12
- import { useIsNarrowViewport } from "./use-narrow-viewport.js";
13
- import "../react-css-custom-properties.js";
14
- import { ActionsBodyCell, ActionsHeaderCell } from "./data-table/actions-column.js";
15
- import { CardSortSelect } from "./data-table/card-sort-select.js";
16
- import { isPinningActive, isResizingActive } from "./data-table/column-layout-mode.js";
17
- import { ColumnResizeHandle, ColumnWidthGroup } from "./data-table/column-resize-handle.js";
18
- import { densityClasses } from "./data-table/density.js";
19
- import { buildFacetCluster } from "./data-table/facet-cluster.js";
20
- import { bandClassName, bandReactKey, bandTestId, buildDataTableItems, GroupBand, } from "./data-table/group-band.js";
21
- import { SortableHeader } from "./data-table/sortable-header.js";
22
- import { EXPAND_TOGGLE_CELL_CLASS, EXPANSION_CELL_CLASS, EXPANSION_CONTENT_CLASS, FILL_TABLE_CLASS, FlushTable, RowExpandToggle, } from "./data-table/table-chrome.js";
23
- import { DataTableFooter, showFullTextOnHover } from "./data-table/table-footer.js";
24
- import { ToolbarEndCluster, toolbarEndHasContent } from "./data-table/toolbar-end-cluster.js";
25
- import { TotalsCard, TotalsFooterRow } from "./data-table/totals-footer.js";
26
- import { useBulkSelection } from "./data-table/use-bulk-selection.js";
27
- import { EXPAND_COLUMN_KEY, SELECT_COLUMN_KEY, useColumnPins, } from "./data-table/use-column-pins.js";
28
- import { useColumnSizing } from "./data-table/use-column-sizing.js";
29
- import { useMeasuredColumnWidths } from "./data-table/use-measured-column-widths.js";
30
- import { useRowKeyboardNavigation } from "./data-table/use-row-keyboard-navigation.js";
31
- import { useViewColumns } from "./data-table/use-view-columns.js";
32
- import { cellLinkProp, cellTitle, DataTableCell, FIT_CONTENT_CELL_CLASS, FirstCellLink, isBadgeColumn, isEmptyCellValue, isUnlabeledFalse, NUMERIC_COLUMN_TYPES, rowToneClass, showsBareCheckMark, TABULAR_COLUMN_TYPES, } from "./data-table-cell.js";
33
- import { InfiniteSentinel, Pager } from "./pager.js";
34
- import { RowActionsCell } from "./row-actions.js";
35
- export function DefaultDataTable({ columns: schemaColumns, rows, onRowClick, sort, onSortChange, emptyState, toolbarStart, toolbarDescription, toolbarEnd, pager, onReachEnd, loadingMore, hasMore, itemNoun, rowActions, rowActionMode, bulk, rowTone, filterFacets, filterValues, onFilterChange, onFilterReset, dateRangeFacets, onDateRangeChange, testId, onCellChange, getRowTestId, getCellTestId, chromeless, scrollBody, screenPadding, expandedRowIds, onToggleRowExpanded, renderExpandedRow, view, resetAll, exportList, serverGroups, totals, notice, rangeFacets, onRangeChange, }) {
36
- // One locale/translate subscription per table — not per cell.
37
- // Optional hooks: a bare DataTable outside LocaleProvider must not crash.
38
- const tableTranslate = useOptionalTranslation();
39
- const tableLocale = useOptionalLocale();
40
- // Below 768px a table scrolls its columns out of reach with no visible
41
- // affordance. Cards replace the table entirely below the breakpoint — same
42
- // single-mount pattern as EmbeddedListInput/embedded-list-input.tsx.
43
- const isNarrow = useIsNarrowViewport();
44
- const viewColumns = useViewColumns(schemaColumns, view, isNarrow);
45
- const density = densityClasses(view, scrollBody === true);
46
- const [facetsOpenNarrow, setFacetsOpenNarrow] = useState(false);
47
- // Toolbar wrapper: shared container for toolbar + table so the two belong
48
- // together visually. The toolbar is NOT sticky: lists typically scroll with
49
- // the page container, not internally. Sticky would compete with the topbar.
50
- // The row click already runs rowClick actions, so with a clickable row they
51
- // must not reappear in the kebab (a list whose only action is the rowClick
52
- // one gets no actions column at all).
53
- const rowIsLink = onRowClick !== undefined;
54
- const menuActions = rowActions !== undefined && rowIsLink
55
- ? rowActions.filter((action) => action.rowClick !== true)
56
- : rowActions;
57
- // The table keeps every action in "inline" mode, and with editable cells the
58
- // first cell is not a keyboard link (FirstCellLink is off), so the kebab is
59
- // then the only keyboard route to the rowClick action.
60
- const tableActions = rowActionMode === "inline" || onCellChange !== undefined ? rowActions : menuActions;
61
- const rowClickAction = rowActions?.find((action) => action.rowClick === true);
62
- const rowClickLabel = rowClickAction?.label;
63
- const rowClickIsHidden = (row) => rowClickAction?.isVisible !== undefined && !rowClickAction.isVisible(row);
64
- const hasTableActions = tableActions !== undefined && tableActions.length > 0;
65
- const isEmpty = rows.length === 0 && (serverGroups?.groups.length ?? 0) === 0;
66
- const expansionIdPrefix = useId();
67
- const rowTestId = (row) => getRowTestId?.(row) ?? `row-${row.id}`;
68
- const isRowExpandable = renderExpandedRow !== undefined && onToggleRowExpanded !== undefined;
69
- const fillsHeight = scrollBody === true;
70
- const selection = useBulkSelection({
71
- bulk,
72
- rows,
73
- titleField: schemaColumns[0]?.field,
74
- rowTestId,
75
- flush: fillsHeight,
76
- });
77
- const { headerRowRef, measured } = useMeasuredColumnWidths(isPinningActive(view, isNarrow) || isResizingActive(view, isNarrow));
78
- const pins = useColumnPins({
79
- columns: viewColumns,
80
- view,
81
- isNarrow,
82
- measured,
83
- hasActionsColumn: hasTableActions,
84
- hasExpandColumn: isRowExpandable,
85
- hasSelectColumn: selection.active,
86
- fillsHeight,
87
- });
88
- const columns = pins.columns;
89
- const sizing = useColumnSizing({
90
- columns,
91
- view,
92
- isNarrow,
93
- measured,
94
- hasExpandColumn: isRowExpandable,
95
- hasSelectColumn: selection.active,
96
- });
97
- const fullColSpan = columns.length + [isRowExpandable, hasTableActions, selection.active].filter(Boolean).length;
98
- const expansionDomId = (row) => `${expansionIdPrefix}-${row.id}`;
99
- // The expand toggle of a row, shared by table and card layout. Named after
100
- // the first data column, the one a user recognizes the row by.
101
- function renderExpandToggle(row) {
102
- if (!isRowExpandable)
103
- return null;
104
- const expanded = expandedRowIds?.has(row.id) === true;
105
- const firstColumn = columns[0];
106
- const title = (firstColumn !== undefined ? cellTitle(row.values[firstColumn.field]) : undefined) ?? "";
107
- const labelKey = expanded ? "kumiko.list.row.collapse" : "kumiko.list.row.expand";
108
- return (_jsx(RowExpandToggle, { expanded: expanded, controlsId: expansionDomId(row), label: tableTranslate?.(labelKey, { title }) ?? `${expanded ? "Collapse" : "Expand"} ${title}`, onToggle: () => onToggleRowExpanded(row.id), testId: `${rowTestId(row)}-toggle` }));
109
- }
110
- function renderExpansion(row, as, colSpan) {
111
- if (!isRowExpandable || expandedRowIds?.has(row.id) !== true)
112
- return null;
113
- const content = renderExpandedRow(row);
114
- if (as === "item") {
115
- return (_jsx("li", { id: expansionDomId(row), "data-testid": `${rowTestId(row)}-expansion`, className: "border-b border-border-row bg-muted px-4 pt-1 pb-3.5", children: content }));
116
- }
117
- return (_jsx(TableRow, { id: expansionDomId(row), "data-testid": `${rowTestId(row)}-expansion`, className: "hover:bg-transparent", children: _jsx(TableCell, { colSpan: colSpan, className: EXPANSION_CELL_CLASS, children: _jsx("div", { className: EXPANSION_CONTENT_CLASS, children: content }) }) }));
118
- }
119
- const TableRoot = fillsHeight ? FlushTable : Table;
120
- const emptyBlock = (_jsx("div", { "data-testid": testId !== undefined ? `${testId}-empty` : "render-list-empty", className: cn("flex flex-col items-center justify-center p-12 text-sm text-muted-foreground gap-3", chromeless !== true && !fillsHeight && "rounded-md border border-dashed"), children: emptyState ?? _jsx("span", { children: tableTranslate?.("kumiko.list.no-entries") ?? "No entries." }) }));
121
- const tableItems = buildDataTableItems(rows, serverGroups);
122
- const keyboardRow = useRowKeyboardNavigation({
123
- view,
124
- isNarrow,
125
- rowIds: tableItems.flatMap((item) => (item.kind === "row" ? [item.row.id] : [])),
126
- onSelect: selection.onKeyboardSelect,
127
- });
128
- const schemaColumnByField = new Map(schemaColumns.map((col) => [col.field, col]));
129
- const columnOf = (field) => schemaColumnByField.get(field);
130
- function renderBand(item) {
131
- return _jsx(GroupBand, { item: item, columnOf: columnOf });
132
- }
133
- function tableInner() {
134
- if (isEmpty)
135
- return emptyBlock;
136
- const expandHeaderPin = pins.headerCell(EXPAND_COLUMN_KEY);
137
- const expandBodyPin = pins.bodyCell(EXPAND_COLUMN_KEY);
138
- return (_jsx("div", { className: cn(!fillsHeight && "overflow-hidden", !fillsHeight && chromeless !== true && "rounded-lg border bg-card"), children: _jsxs(TableRoot, { "data-testid": testId, className: fillsHeight ? FILL_TABLE_CLASS : undefined, ...(sizing !== undefined && { style: sizing.tableStyle }), children: [sizing !== undefined && (_jsx(ColumnWidthGroup, { fields: columns.map((col) => col.field), sizing: sizing, hasExpandColumn: isRowExpandable, hasSelectColumn: selection.active, hasActionsColumn: hasTableActions })), _jsx(TableHeader, { className: "bg-muted", children: _jsxs(TableRow, { ref: headerRowRef, className: "hover:bg-transparent", children: [selection.headerCell(pins.headerCell(SELECT_COLUMN_KEY)), isRowExpandable && (_jsx(TableHead, { "data-testid": "column-expand", className: cn(EXPAND_TOGGLE_CELL_CLASS, expandHeaderPin.className), style: expandHeaderPin.style })), columns.map((col) => {
139
- const headerPin = pins.headerCell(col.field);
140
- return (_jsx(SortableHeader, { field: col.field, label: col.label, sortable: col.sortable === true, highlighted: col.highlighted === true, numeric: NUMERIC_COLUMN_TYPES.has(col.type), fitContent: col.fileDisplay !== undefined, className: cn(density.header, headerPin.className), style: headerPin.style, columnField: headerPin.columnField, trailing: sizing && (_jsx(ColumnResizeHandle, { field: col.field, label: col.label, sizing: sizing })), ...(sort !== undefined && sort !== null && { sort }), ...(onSortChange !== undefined && { onSortChange }) }, col.field));
141
- }), hasTableActions && (_jsx(ActionsHeaderCell, { label: tableTranslate?.("kumiko.list.actions") ?? "Actions", style: pins.actionsHeaderStyle, absorbsLeftover: sizing !== undefined }))] }) }), _jsx(TableBody, { children: tableItems.map((item) => {
142
- if (item.kind !== "row") {
143
- return (_jsx(TableRow, { "data-testid": bandTestId(item), children: _jsx(TableCell, { colSpan: fullColSpan, className: bandClassName(item), children: renderBand(item) }) }, bandReactKey(item)));
144
- }
145
- const row = item.row;
146
- const opensOnClick = rowIsLink && !rowClickIsHidden(row);
147
- const keyboard = keyboardRow(row.id, opensOnClick ? () => onRowClick(row) : undefined);
148
- return (_jsxs(Fragment, { children: [_jsxs(TableRow, { "data-testid": rowTestId(row), "data-tone": rowTone?.(row), onClick: opensOnClick ? () => onRowClick(row) : undefined, className: cn(rowToneClass(rowTone?.(row)), opensOnClick && "cursor-pointer", fillsHeight && "h-10 border-border-row hover:bg-muted", density.row, keyboard.className), ...keyboard.props, children: [selection.bodyCell(row, pins.bodyCell(SELECT_COLUMN_KEY)), isRowExpandable && (_jsx(TableCell, { "data-testid": getCellTestId?.(row, "expand") ?? `cell-${row.id}-expand`, className: cn(EXPAND_TOGGLE_CELL_CLASS, expandBodyPin.className), style: expandBodyPin.style, children: renderExpandToggle(row) })), columns.map((col, colIndex) => {
149
- const bodyPin = pins.bodyCell(col.field);
150
- return (_jsx(TableCell, { "data-testid": getCellTestId?.(row, col.field) ?? `cell-${row.id}-${col.field}`, "data-highlighted": col.highlighted === true ? "true" : undefined,
151
- // Text wraps instead of truncating: nowrap cells keep their
152
- // full width, so a wide table scrolled sideways and its
153
- // last column slid under the sticky actions column with
154
- // nothing visibly cut off. max-w-xs bounds a single column;
155
- // badges and dates stay nowrap by their own classes.
156
- className: cn(col.fileDisplay === undefined
157
- ? cn(sizing ? "overflow-hidden" : "max-w-xs", "whitespace-normal break-words")
158
- : FIT_CONTENT_CELL_CLASS, colIndex === 0
159
- ? "font-medium text-foreground"
160
- : "text-foreground-secondary", NUMERIC_COLUMN_TYPES.has(col.type) && "text-right", TABULAR_COLUMN_TYPES.has(col.type) && "tabular-nums", col.highlighted === true && "bg-accent/40", density.cell, bodyPin.className), style: bodyPin.style, title: cellTitle(row.values[col.field]), children: _jsx(FirstCellLink, { enabled: colIndex === 0 && opensOnClick && onCellChange === undefined, onOpen: () => onRowClick?.(row), empty: isEmptyCellValue(row.values[col.field]), emptyLabel: rowClickLabel, children: _jsx(DataTableCell, { value: row.values[col.field], row: row.values, field: col.field, type: col.type, renderer: col.renderer, translate: tableTranslate, locale: tableLocale, ...(col.optionLabels !== undefined && {
161
- optionLabels: col.optionLabels,
162
- }), ...(col.optionTones !== undefined && {
163
- optionTones: col.optionTones,
164
- }), ...(col.grouping !== undefined && { grouping: col.grouping }), ...(col.fileDisplay !== undefined && {
165
- fileDisplay: col.fileDisplay,
166
- }), ...(onCellChange !== undefined && {
167
- onChange: (value) => onCellChange(row.id, col.field, value),
168
- }), ...cellLinkProp(col, row, !(colIndex === 0 && opensOnClick && onCellChange === undefined)) }) }) }, col.field));
169
- }), hasTableActions && (_jsx(ActionsBodyCell, { testId: getCellTestId?.(row, "actions") ?? `cell-${row.id}-actions`, fillsHeight: fillsHeight, measureContent: sizing !== undefined, children: _jsx(RowActionsCell, { row: row, actions: tableActions, mode: rowActionMode, rowIsLink: rowIsLink }) }))] }), renderExpansion(row, "row", fullColSpan)] }, row.id));
170
- }) }), totals !== undefined && (_jsx(TotalsFooterRow, { totals: totals, columns: columns, cellPin: pins.bodyCell, leadingCells: { select: selection.active, expand: isRowExpandable }, actionsCell: hasTableActions ? { fillsHeight } : undefined, cellClassName: density.cell }))] }) }));
171
- }
172
- const { image: cardImageColumn, title: cardTitleColumn, status: cardStatusColumn, meta: cardMetaColumns, } = cardColumnRoles(columns);
173
- function cardCell(row, col) {
174
- // A bare check mark in the subtitle line says nothing without its column.
175
- if (showsBareCheckMark(col.type, row.values[col.field], col.renderer))
176
- return col.label;
177
- return (_jsx(DataTableCell, { value: row.values[col.field], row: row.values, field: col.field, type: col.type, renderer: col.renderer, translate: tableTranslate, locale: tableLocale, ...(col.optionLabels !== undefined && { optionLabels: col.optionLabels }), ...(col.optionTones !== undefined && { optionTones: col.optionTones }), ...(col.grouping !== undefined && { grouping: col.grouping }), ...(col.fileDisplay !== undefined && { fileDisplay: col.fileDisplay, compactFile: true }), ...(onCellChange !== undefined && {
178
- onChange: (value) => onCellChange(row.id, col.field, value),
179
- }), ...cellLinkProp(col, row, !(rowIsLink && !rowClickIsHidden(row))) }));
180
- }
181
- function renderCard(row) {
182
- const metaColumns = cardMetaColumns.filter((col) => !isEmptyCellValue(row.values[col.field]) &&
183
- !isUnlabeledFalse(col.type, row.values[col.field], col.renderer));
184
- const showStatus = cardStatusColumn !== undefined && !isEmptyCellValue(row.values[cardStatusColumn.field]);
185
- const hasMenu = menuActions !== undefined && menuActions.length > 0;
186
- const opensOnClick = rowIsLink && !rowClickIsHidden(row);
187
- const body = (_jsxs(_Fragment, { children: [cardImageColumn?.fileDisplay !== undefined && (_jsx("div", { "data-testid": `card-image-${row.id}`, className: "shrink-0", children: _jsx(CardLeadingImage, { value: row.values[cardImageColumn.field], display: cardImageColumn.fileDisplay }) })), _jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-1", children: [_jsxs("div", { className: "flex min-w-0 items-start justify-between gap-2", children: [cardTitleColumn !== undefined && (_jsx("span", { "data-testid": getCellTestId?.(row, cardTitleColumn.field) ??
188
- `cell-${row.id}-${cardTitleColumn.field}`, className: "min-w-0 break-words text-base font-semibold text-foreground", children: cardCell(row, cardTitleColumn) })), showStatus && (_jsx("span", { "data-testid": getCellTestId?.(row, cardStatusColumn.field) ??
189
- `cell-${row.id}-${cardStatusColumn.field}`, className: "shrink-0", children: cardCell(row, cardStatusColumn) }))] }), metaColumns.length > 0 && (_jsx("div", { className: "min-w-0 overflow-x-clip", children: _jsx("div", { "data-testid": `card-meta-${row.id}`, className: "-ms-3 flex min-w-0 flex-wrap items-center text-[13px] leading-5 tabular-nums text-foreground-secondary", children: metaColumns.map((col) => (_jsxs("div", { className: "flex h-5 min-w-0 max-w-full items-center", children: [_jsx("span", { "aria-hidden": "true", className: "w-3 shrink-0 text-center", children: "\u00B7" }), _jsx("div", { "data-testid": getCellTestId?.(row, col.field) ?? `cell-${row.id}-${col.field}`, className: "min-w-0 truncate leading-5", onPointerEnter: showFullTextOnHover, children: isBadgeColumn(col) ? (_jsx("span", { className: "inline-flex align-middle", children: cardCell(row, col) })) : (cardCell(row, col)) })] }, col.field))) }) }))] }), opensOnClick && (_jsx(ChevronRight, { className: "size-4 shrink-0 text-muted-foreground", "aria-hidden": "true" }))] }));
190
- const rowItem = (_jsxs("li", { "data-testid": rowTestId(row), "data-tone": rowTone?.(row), className: cn("flex min-h-[72px] items-center gap-2 border-b border-border-row py-3", rowToneClass(rowTone?.(row)), isRowExpandable || selection.active ? "pl-2" : "pl-4", hasMenu ? "pr-2" : "pr-4", density.card), ...(opensOnClick && { onClick: () => onRowClick(row) }), children: [selection.cardCheckbox(row), renderExpandToggle(row), opensOnClick ? (_jsx("button", { type: "button", className: "flex min-w-0 flex-1 cursor-pointer items-center gap-2 bg-transparent p-0 text-left", onKeyDown: (e) => {
191
- if (e.key !== "Enter")
192
- return;
193
- e.preventDefault();
194
- e.stopPropagation();
195
- onRowClick(row);
196
- }, children: body })) : (body), hasMenu && (_jsx("div", { onClick: (e) => e.stopPropagation(), onKeyDown: (e) => e.stopPropagation(), children: _jsx(RowActionsCell, { row: row, actions: menuActions, mode: rowActionMode, rowIsLink: rowIsLink, compact: true }) }))] }, row.id));
197
- if (!isRowExpandable)
198
- return rowItem;
199
- // The expansion is a sibling <li>, outside the row's click target, so
200
- // clicks in the sub-area never navigate the parent row.
201
- return (_jsxs(Fragment, { children: [rowItem, renderExpansion(row, "item", 0)] }, row.id));
202
- }
203
- function cardsInner() {
204
- if (isEmpty)
205
- return emptyBlock;
206
- return (_jsx("div", { "data-testid": testId, className: "contents", children: _jsxs("div", { "data-testid": testId !== undefined ? `${testId}-cards` : "render-list-cards", className: "flex flex-col", children: [_jsx(CardSortSelect, { columns: columns, sort: sort, onSortChange: onSortChange, testId: testId }), _jsx("ul", { className: "m-0 list-none border-t border-border-row p-0", children: tableItems.map((item) => item.kind === "row" ? (renderCard(item.row)) : (_jsx("li", { "data-testid": bandTestId(item), className: cn("border-b border-border-row", bandClassName(item)), children: renderBand(item) }, bandReactKey(item)))) }), totals !== undefined && _jsx(TotalsCard, { totals: totals, columnOf: columnOf })] }) }));
207
- }
208
- const tableContent = isNarrow ? cardsInner() : tableInner();
209
- // The pager is ALWAYS rendered below the table (even with rows=[]) so the
210
- // user can page back on a filter hit of zero without losing the list.
211
- // Except total === 0: then there is nothing to paginate. Incompatible with
212
- // infinite scroll: the caller sets either pager OR onReachEnd.
213
- const pagerElement = pager !== undefined && pager.total > 0 && serverGroups === undefined ? (_jsx(Pager, { page: pager.page, limit: pager.limit, total: pager.total, onPageChange: pager.onPageChange, ...(pager.pageSizeOptions !== undefined && { pageSizeOptions: pager.pageSizeOptions }), ...(pager.onPageSizeChange !== undefined && { onPageSizeChange: pager.onPageSizeChange }), ...(itemNoun !== undefined && { itemNoun }), testId: testId !== undefined ? `${testId}-pager` : "render-list-pager", pinned: fillsHeight })) : undefined;
214
- const sentinelElement = pagerElement === undefined && onReachEnd !== undefined && serverGroups === undefined ? (_jsx(InfiniteSentinel, { onReachEnd: onReachEnd, loadingMore: loadingMore === true, hasMore: hasMore !== false, rowsCount: rows.length, testId: testId !== undefined ? `${testId}-sentinel` : "render-list-sentinel" })) : undefined;
215
- // With scrollBody the footer (pager, else the entry count) sits OUTSIDE the
216
- // scroll surface so it stays put at any row count; the infinite sentinel
217
- // stays INSIDE, or it would never intersect the scrollport.
218
- const content = fillsHeight ? (_jsxs(_Fragment, { children: [selection.bar, _jsxs("div", { "data-testid": testId !== undefined ? `${testId}-scroll` : "render-list-scroll", className: "min-h-0 flex-1 overflow-auto border-t border-border bg-card", children: [tableContent, sentinelElement] }), pagerElement ?? (_jsx(DataTableFooter, { count: serverGroups?.totalCount ?? rows.length, ...(itemNoun !== undefined && { itemNoun }), testId: testId !== undefined ? `${testId}-footer` : "render-list-footer" }))] })) : (_jsxs(_Fragment, { children: [selection.bar, tableContent, pagerElement, sentinelElement] }));
219
- const facetCluster = buildFacetCluster({
220
- filterFacets,
221
- filterValues,
222
- onFilterChange,
223
- onFilterReset,
224
- dateRangeFacets,
225
- onDateRangeChange,
226
- rangeFacets,
227
- onRangeChange,
228
- resetAll,
229
- notice,
230
- dense: fillsHeight,
231
- });
232
- const columnLabels = new Map(schemaColumns.map((col) => [col.field, col.label]));
233
- const collapsesFacetsBehindButton = isNarrow && fillsHeight && facetCluster !== undefined;
234
- const hasToolbar = toolbarStart !== undefined ||
235
- facetCluster !== undefined ||
236
- toolbarEndHasContent(view, exportList, toolbarEnd);
7
+ // `screenPadding`: on a list screen this wrapper IS the screen container,
8
+ // so it takes the same token as FormScreenShell/PageSection instead of its
9
+ // own inset — a list ends at the same footer distance as a form.
10
+ // Explicit `false` (vs. the default/omitted case) means a host that
11
+ // already pads its own children — a relatedList section nested in a
12
+ // tabs-mode Card — so this wrapper adds no inset of its
13
+ // own, or its content would sit ~24px deeper than sibling banners in the
14
+ // same Card body.
15
+ function screenPaddingOf(screenPadding) {
16
+ if (screenPadding === true)
17
+ return screenPaddingClassName;
18
+ return screenPadding === false ? undefined : "p-6";
19
+ }
20
+ function wrapperClassName({ fillsHeight, screenPadding }) {
21
+ return cn("flex flex-col w-full",
22
+ // scrollBody: the wrapper fills its container so the footer sits at the
23
+ // same spot at 3 and 300 rows; the 24px insets live in toolbar, cells
24
+ // and footer, so no outer padding.
25
+ fillsHeight ? "h-full min-h-0" : cn("gap-4", screenPaddingOf(screenPadding)));
26
+ }
27
+ export function DefaultDataTable(props) {
28
+ const model = useDataTableState(props);
29
+ const facetCluster = buildModelFacetCluster(model);
30
+ const hasToolbar = hasToolbarContent(model, facetCluster);
237
31
  // dashboard-01 pattern: the toolbar (search + facets + "+ New") sits ABOVE
238
32
  // the table in the same padding block; no separate bg bar, no screen title
239
- // (that lives in the shell's breadcrumb).
240
- return (_jsxs("div", { className: cn("flex flex-col w-full",
241
- // scrollBody: the wrapper fills its container so the footer sits at the
242
- // same spot at 3 and 300 rows; the 24px insets live in toolbar, cells
243
- // and footer, so no outer padding.
244
- fillsHeight
245
- ? "h-full min-h-0"
246
- : cn("gap-4", screenPadding === true
247
- ? screenPaddingClassName
248
- : screenPadding === false
249
- ? undefined
250
- : "p-6")), children: [hasToolbar && (_jsxs("div", { "data-testid": testId !== undefined ? `${testId}-toolbar` : "render-list-toolbar", className: cn("flex flex-wrap items-center", UNIFORM_BAR_CONTROLS_CLASSES, fillsHeight
251
- ? isNarrow
252
- ? "shrink-0 gap-2 p-3"
253
- : "min-h-13 shrink-0 gap-2 px-6 py-2 md:py-0"
254
- : "gap-3"), children: [toolbarStart !== undefined && (_jsxs("div", { className: cn(fillsHeight
255
- ? "relative w-full md:w-[300px] max-md:flex-1 [&_input]:border-border-strong [&_input]:pl-8 [&_input]:max-md:h-11"
256
- : "flex-1 min-w-48 max-w-sm"), children: [fillsHeight && (_jsx(Search, { className: "pointer-events-none absolute left-2.5 top-1/2 z-10 size-4 -translate-y-1/2 text-muted-foreground", "aria-hidden": "true" })), toolbarStart] })), collapsesFacetsBehindButton ? (_jsxs(_Fragment, { children: [_jsx(UiButton, { type: "button", variant: "outline", size: "icon", className: "size-11", "aria-label": tableTranslate?.("kumiko.list.filter.toggle") ?? "Filter", "aria-expanded": facetsOpenNarrow, "data-testid": testId !== undefined ? `${testId}-filter-toggle` : "filter-toggle", onClick: () => setFacetsOpenNarrow((open) => !open), children: _jsx(SlidersHorizontal, { className: "size-4", "aria-hidden": "true" }) }), facetsOpenNarrow && _jsx("div", { className: "w-full", children: facetCluster })] })) : (facetCluster), _jsx(ToolbarEndCluster, { view: view, columnLabels: columnLabels, exportList: exportList, toolbarEnd: toolbarEnd })] })), toolbarDescription !== undefined && (_jsx("p", { "data-testid": testId !== undefined ? `${testId}-description` : "render-list-description", className: cn("m-0 pb-2 text-sm text-muted-foreground", fillsHeight && (isNarrow ? "px-3" : "px-6"), fillsHeight && !hasToolbar && "pt-4"), children: toolbarDescription })), content] }));
33
+ // (that lives in the shell's breadcrumb). The toolbar is NOT sticky: lists
34
+ // typically scroll with the page container, not internally. Sticky would
35
+ // compete with the topbar.
36
+ return (_jsxs("div", { className: wrapperClassName(model), children: [hasToolbar && renderToolbar(model, facetCluster), renderToolbarDescription(model, hasToolbar), renderTableContent(model)] }));
257
37
  }
@@ -18,5 +18,5 @@ function FileValueRow({ fileId, previewOpenInitially, testId, }) {
18
18
  export function FileValueList({ fileIds, previewOpenByDefault = false, testId, }) {
19
19
  if (fileIds.length === 0)
20
20
  return null;
21
- return (_jsx("ul", { className: "m-0 flex list-none flex-col gap-3 p-0", "data-testid": testId, children: fileIds.map((fileId, index) => (_jsx(FileValueRow, { fileId: fileId, previewOpenInitially: previewOpenByDefault && index === 0, ...(testId !== undefined && { testId: `${testId}-${fileId}` }) }, fileId))) }));
21
+ return (_jsx("ul", { className: "m-0 flex list-none flex-col gap-3 p-0", "data-testid": testId, children: fileIds.map((fileId, index) => (_jsx(FileValueRow, { fileId: fileId, previewOpenInitially: previewOpenByDefault && index === fileIds.length - 1, ...(testId !== undefined && { testId: `${testId}-${fileId}` }) }, fileId))) }));
22
22
  }