@rebasepro/ui 0.17.3 → 0.18.1

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 (127) hide show
  1. package/README.md +4 -0
  2. package/dist/components/MultiSelect.d.ts +10 -0
  3. package/dist/index.es.js +2 -1
  4. package/dist/index.es.js.map +1 -1
  5. package/package.json +32 -23
  6. package/src/components/Alert.tsx +0 -118
  7. package/src/components/Autocomplete.tsx +0 -93
  8. package/src/components/Avatar.tsx +0 -73
  9. package/src/components/Badge.tsx +0 -44
  10. package/src/components/BooleanSwitch.tsx +0 -116
  11. package/src/components/BooleanSwitchWithLabel.tsx +0 -120
  12. package/src/components/Button.tsx +0 -110
  13. package/src/components/Card.tsx +0 -43
  14. package/src/components/CenteredView.tsx +0 -35
  15. package/src/components/Checkbox.tsx +0 -129
  16. package/src/components/Chip.tsx +0 -131
  17. package/src/components/CircularProgress.tsx +0 -53
  18. package/src/components/CircularProgressCenter.tsx +0 -27
  19. package/src/components/Collapse.tsx +0 -66
  20. package/src/components/ColorPicker.tsx +0 -140
  21. package/src/components/Container.tsx +0 -51
  22. package/src/components/DateTimeField.tsx +0 -363
  23. package/src/components/DebouncedTextField.tsx +0 -76
  24. package/src/components/Dialog.tsx +0 -159
  25. package/src/components/DialogActions.tsx +0 -29
  26. package/src/components/DialogContent.tsx +0 -27
  27. package/src/components/DialogTitle.tsx +0 -41
  28. package/src/components/ErrorBoundary.tsx +0 -236
  29. package/src/components/ExpandablePanel.tsx +0 -130
  30. package/src/components/FileUpload.tsx +0 -103
  31. package/src/components/FilterChip.tsx +0 -89
  32. package/src/components/IconButton.tsx +0 -101
  33. package/src/components/InfoLabel.tsx +0 -23
  34. package/src/components/InputLabel.tsx +0 -40
  35. package/src/components/Label.tsx +0 -32
  36. package/src/components/LoadingButton.tsx +0 -32
  37. package/src/components/Markdown.tsx +0 -55
  38. package/src/components/Menu.tsx +0 -114
  39. package/src/components/Menubar.tsx +0 -355
  40. package/src/components/MultiSelect.tsx +0 -433
  41. package/src/components/Paper.tsx +0 -23
  42. package/src/components/Popover.tsx +0 -162
  43. package/src/components/RadioGroup.tsx +0 -74
  44. package/src/components/ResizablePanels.tsx +0 -189
  45. package/src/components/SearchBar.tsx +0 -136
  46. package/src/components/Select.tsx +0 -326
  47. package/src/components/Separator.tsx +0 -28
  48. package/src/components/Sheet.tsx +0 -135
  49. package/src/components/Skeleton.tsx +0 -54
  50. package/src/components/Slider.tsx +0 -110
  51. package/src/components/Table.tsx +0 -178
  52. package/src/components/Tabs.tsx +0 -185
  53. package/src/components/TextField.tsx +0 -270
  54. package/src/components/TextareaAutosize.tsx +0 -368
  55. package/src/components/ToggleButtonGroup.tsx +0 -69
  56. package/src/components/Tooltip.tsx +0 -153
  57. package/src/components/Typography.tsx +0 -132
  58. package/src/components/VirtualTable/VirtualTable.performance.test.tsx +0 -90
  59. package/src/components/VirtualTable/VirtualTable.tsx +0 -700
  60. package/src/components/VirtualTable/VirtualTableCell.tsx +0 -59
  61. package/src/components/VirtualTable/VirtualTableHeader.tsx +0 -327
  62. package/src/components/VirtualTable/VirtualTableHeaderRow.tsx +0 -250
  63. package/src/components/VirtualTable/VirtualTableProps.tsx +0 -339
  64. package/src/components/VirtualTable/VirtualTableRow.tsx +0 -51
  65. package/src/components/VirtualTable/fields/VirtualTableDateField.tsx +0 -44
  66. package/src/components/VirtualTable/fields/VirtualTableInput.tsx +0 -89
  67. package/src/components/VirtualTable/fields/VirtualTableNumberInput.tsx +0 -71
  68. package/src/components/VirtualTable/fields/VirtualTableSelect.tsx +0 -107
  69. package/src/components/VirtualTable/fields/VirtualTableSwitch.tsx +0 -28
  70. package/src/components/VirtualTable/index.tsx +0 -10
  71. package/src/components/VirtualTable/selection/SelectionContext.tsx +0 -36
  72. package/src/components/VirtualTable/selection/SelectionStore.ts +0 -54
  73. package/src/components/VirtualTable/types.tsx +0 -47
  74. package/src/components/common/SelectInputLabel.tsx +0 -23
  75. package/src/components/index.tsx +0 -62
  76. package/src/hooks/PortalContainerContext.tsx +0 -50
  77. package/src/hooks/index.ts +0 -7
  78. package/src/hooks/useDebounceCallback.tsx +0 -21
  79. package/src/hooks/useDebounceValue.tsx +0 -20
  80. package/src/hooks/useDebouncedCallback.ts +0 -39
  81. package/src/hooks/useInjectStyles.tsx +0 -31
  82. package/src/hooks/useOutsideAlerter.tsx +0 -46
  83. package/src/icons/GitHubIcon.tsx +0 -21
  84. package/src/icons/HandleIcon.tsx +0 -23
  85. package/src/icons/Icon.tsx +0 -33
  86. package/src/icons/LucideIconByName.tsx +0 -117
  87. package/src/icons/cool_icon_keys.ts +0 -12
  88. package/src/icons/icon_keys.ts +0 -1936
  89. package/src/icons/index.ts +0 -179
  90. package/src/index.css +0 -169
  91. package/src/index.ts +0 -47
  92. package/src/scripts/fixIconSizes.cjs +0 -92
  93. package/src/scripts/fixIconSizesToConst.cjs +0 -106
  94. package/src/scripts/generateIconKeys.ts +0 -41
  95. package/src/scripts/generateIcons.ts +0 -4
  96. package/src/scripts/saveIconFiles.ts +0 -89
  97. package/src/styles.ts +0 -141
  98. package/src/theme.css +0 -168
  99. package/src/util/chip_colors.ts +0 -242
  100. package/src/util/cls.ts +0 -8
  101. package/src/util/debounce.ts +0 -28
  102. package/src/util/hash.ts +0 -11
  103. package/src/util/index.ts +0 -4
  104. package/src/util/key_to_icon_component.ts +0 -11
  105. package/src/util/lazy_chunk.ts +0 -101
  106. package/src/views/CardView.tsx +0 -281
  107. package/src/views/CollectionView/CollectionCardView.tsx +0 -265
  108. package/src/views/CollectionView/CollectionKanbanView.tsx +0 -246
  109. package/src/views/CollectionView/CollectionListView.tsx +0 -240
  110. package/src/views/CollectionView/CollectionTableView.tsx +0 -229
  111. package/src/views/CollectionView/CollectionView.tsx +0 -450
  112. package/src/views/CollectionView/CollectionViewToolbar.tsx +0 -242
  113. package/src/views/CollectionView/CollectionViewTypes.ts +0 -181
  114. package/src/views/CollectionView/DefaultCellRenderer.tsx +0 -404
  115. package/src/views/CollectionView/index.ts +0 -39
  116. package/src/views/CollectionView/resolveTitleProperty.ts +0 -52
  117. package/src/views/CollectionView/utils.ts +0 -14
  118. package/src/views/Kanban/Board.tsx +0 -384
  119. package/src/views/Kanban/BoardColumn.tsx +0 -138
  120. package/src/views/Kanban/BoardColumnTitle.tsx +0 -47
  121. package/src/views/Kanban/BoardSortableList.tsx +0 -182
  122. package/src/views/Kanban/board_types.ts +0 -80
  123. package/src/views/Kanban/index.tsx +0 -5
  124. package/src/views/Kanban/placement.ts +0 -62
  125. package/src/views/KanbanView.tsx +0 -32
  126. package/src/views/ListView.tsx +0 -296
  127. package/src/views/index.ts +0 -5
@@ -1,229 +0,0 @@
1
- "use client";
2
- import React, { useCallback, useMemo } from "react";
3
- import { VirtualTable } from "../../components/VirtualTable/VirtualTable";
4
- import type { VirtualTableColumn, CellRendererParams, OnVirtualTableColumnResizeParams } from "../../components/VirtualTable/VirtualTableProps";
5
- import { DefaultCellRenderer } from "./DefaultCellRenderer";
6
- import { cls } from "../../util";
7
- import type {
8
- CollectionPropertyConfig,
9
- CollectionDataController,
10
- CellRendererOverride,
11
- CollectionViewSize,
12
- CollectionSelectionController,
13
- CellRendererProps,
14
- } from "./CollectionViewTypes";
15
-
16
- import { getValueInPath } from "./utils";
17
-
18
- const DEFAULT_COLUMN_WIDTHS: Record<string, number> = {
19
- string: 200,
20
- number: 140,
21
- date: 160,
22
- boolean: 120,
23
- array: 200,
24
- map: 200,
25
- reference: 200,
26
- relation: 200,
27
- geopoint: 180,
28
- };
29
- const FALLBACK_COLUMN_WIDTH = 200;
30
-
31
- const SIZE_TO_ROW_HEIGHT: Record<CollectionViewSize, number> = {
32
- xs: 36,
33
- s: 44,
34
- m: 52,
35
- l: 64,
36
- xl: 80,
37
- };
38
-
39
- const SORTABLE_TYPES = new Set(["string", "number", "date"]);
40
-
41
- type RendererSize = "tiny" | "small" | "medium" | "large";
42
-
43
- const VIEW_SIZE_TO_RENDERER_SIZE: Record<CollectionViewSize, RendererSize> = {
44
- xs: "tiny",
45
- s: "small",
46
- m: "medium",
47
- l: "large",
48
- xl: "large",
49
- };
50
-
51
- /**
52
- * Props for the {@link CollectionTableView} component.
53
- * @group Collection View
54
- */
55
- export type CollectionTableViewProps<T = Record<string, unknown>> = {
56
- dataController: CollectionDataController<T>;
57
- properties: Record<string, CollectionPropertyConfig>;
58
- propertiesOrder?: string[];
59
- displayedColumnIds?: string[];
60
- idProperty?: string;
61
- titleProperty?: string;
62
- onRowClick?: (row: T) => void;
63
- cellRenderer?: CellRendererOverride<T>;
64
- size?: CollectionViewSize;
65
- selectionEnabled?: boolean;
66
- selectionController?: CollectionSelectionController<T>;
67
- highlightedItems?: T[];
68
- emptyComponent?: React.ReactNode;
69
- hoverRow?: boolean;
70
- className?: string;
71
- onColumnResize?: (params: { key: string; width: number }) => void;
72
- onColumnsOrderChange?: (keys: string[]) => void;
73
- };
74
-
75
- /**
76
- * Headless table view that wraps {@link VirtualTable}.
77
- *
78
- * Converts property configurations into virtual table columns and wires up
79
- * sorting, pagination, and cell rendering without coupling to any data layer.
80
- *
81
- * @group Collection View
82
- */
83
- export function CollectionTableView<T extends Record<string, unknown> = Record<string, unknown>>({
84
- dataController,
85
- properties,
86
- propertiesOrder,
87
- displayedColumnIds,
88
- onRowClick,
89
- cellRenderer,
90
- size = "m",
91
- emptyComponent,
92
- hoverRow = true,
93
- className,
94
- onColumnResize,
95
- onColumnsOrderChange,
96
- }: CollectionTableViewProps<T>) {
97
- // ── Build columns ────────────────────────────────────────────────
98
- const columns = useMemo<VirtualTableColumn[]>(() => {
99
- const order = propertiesOrder ?? Object.keys(properties);
100
- const filteredKeys = displayedColumnIds
101
- ? order.filter((key) => displayedColumnIds.includes(key))
102
- : order.filter((key) => {
103
- const prop = properties[key];
104
- return prop && !prop.hideFromCollection;
105
- });
106
-
107
- return filteredKeys
108
- .map((key): VirtualTableColumn | null => {
109
- const prop = properties[key];
110
- if (!prop) return null;
111
- return {
112
- key,
113
- width: prop.columnWidth || DEFAULT_COLUMN_WIDTHS[prop.type] || FALLBACK_COLUMN_WIDTH,
114
- title: prop.name,
115
- sortable: SORTABLE_TYPES.has(prop.type),
116
- resizable: true,
117
- };
118
- })
119
- .filter((col): col is VirtualTableColumn => col !== null);
120
- }, [properties, propertiesOrder, displayedColumnIds]);
121
-
122
- // ── Row height ───────────────────────────────────────────────────
123
- const rowHeight = SIZE_TO_ROW_HEIGHT[size];
124
- const rendererSize = VIEW_SIZE_TO_RENDERER_SIZE[size];
125
-
126
- // ── Cell renderer ────────────────────────────────────────────────
127
- const tableCellRenderer = useCallback(
128
- (params: CellRendererParams<Record<string, unknown>>): React.ReactNode => {
129
- if (!params.rowData) return null;
130
-
131
- const propertyConfig = properties[params.column.key];
132
- if (!propertyConfig) return null;
133
-
134
- const value = getValueInPath(
135
- params.rowData,
136
- params.column.key
137
- );
138
-
139
- const cellProps: CellRendererProps<T> = {
140
- row: params.rowData as T,
141
- propertyKey: params.column.key,
142
- property: propertyConfig,
143
- value,
144
- size: rendererSize,
145
- width: params.width,
146
- };
147
-
148
- // Try custom renderer first
149
- if (cellRenderer) {
150
- const custom = cellRenderer(cellProps);
151
- if (custom !== undefined) {
152
- return (
153
- <div className="flex items-center h-full px-1 overflow-hidden">
154
- {custom}
155
- </div>
156
- );
157
- }
158
- }
159
-
160
- return (
161
- <div className="flex items-center h-full px-1 overflow-hidden">
162
- <DefaultCellRenderer {...cellProps} />
163
- </div>
164
- );
165
- },
166
- [properties, cellRenderer, rendererSize]
167
- );
168
-
169
- // ── Row click ────────────────────────────────────────────────────
170
- const handleRowClick = useCallback(
171
- ({ rowData }: { rowData: Record<string, unknown> }) => {
172
- onRowClick?.(rowData as T);
173
- },
174
- [onRowClick]
175
- );
176
-
177
- // ── Pagination ──────────────────────────────────────────────────
178
- const handleEndReached = useCallback(() => {
179
- if (dataController.paginationEnabled && !dataController.noMoreToLoad) {
180
- const currentCount = dataController.itemCount ?? dataController.pageSize ?? 50;
181
- const pageSize = dataController.pageSize ?? 50;
182
- dataController.setItemCount?.(currentCount + pageSize);
183
- }
184
- }, [
185
- dataController.paginationEnabled,
186
- dataController.noMoreToLoad,
187
- dataController.itemCount,
188
- dataController.pageSize,
189
- dataController.setItemCount,
190
- ]);
191
-
192
- // ── Column resize ───────────────────────────────────────────────
193
- const handleColumnResize = useCallback(
194
- (params: OnVirtualTableColumnResizeParams) => {
195
- onColumnResize?.({ key: params.key, width: params.width });
196
- },
197
- [onColumnResize]
198
- );
199
-
200
- // ── Column reorder ──────────────────────────────────────────────
201
- const handleColumnsOrderChange = useCallback(
202
- (newColumns: VirtualTableColumn[]) => {
203
- onColumnsOrderChange?.(newColumns.map((col) => col.key));
204
- },
205
- [onColumnsOrderChange]
206
- );
207
-
208
- return (
209
- <VirtualTable
210
- data={dataController.data}
211
- columns={columns}
212
- cellRenderer={tableCellRenderer}
213
- rowHeight={rowHeight}
214
- sortBy={dataController.sortBy}
215
- onSortByUpdate={dataController.setSortBy}
216
- onRowClick={handleRowClick}
217
- onEndReached={handleEndReached}
218
- onColumnResize={handleColumnResize}
219
- onColumnsOrderChange={handleColumnsOrderChange}
220
- loading={dataController.loading}
221
- emptyComponent={emptyComponent}
222
- error={dataController.error}
223
- hoverRow={hoverRow}
224
- className={cls("h-full", className)}
225
- onScroll={dataController.onScroll}
226
- initialScroll={dataController.initialScroll}
227
- />
228
- );
229
- }
@@ -1,450 +0,0 @@
1
- import React, { useCallback, useMemo, useState } from "react";
2
- import { cls } from "../../util";
3
- import { Typography } from "../../components/Typography";
4
- import { CollectionViewToolbar } from "./CollectionViewToolbar";
5
- import { CollectionTableView } from "./CollectionTableView";
6
- import { CollectionCardView } from "./CollectionCardView";
7
- import { CollectionListView } from "./CollectionListView";
8
- import { CollectionKanbanView } from "./CollectionKanbanView";
9
- import type {
10
- CollectionViewMode,
11
- CollectionViewSize,
12
- CollectionPropertyConfig,
13
- CollectionDataController,
14
- CellRendererOverride,
15
- CollectionCustomViewEntry,
16
- CollectionSelectionController,
17
- KanbanPropertyOption
18
- } from "./CollectionViewTypes";
19
-
20
- /**
21
- * Props for the headless {@link CollectionView} component.
22
- *
23
- * This component is data-agnostic — it renders collection data based on
24
- * property configurations and callbacks, with ZERO coupling to any entity
25
- * or data layer.
26
- *
27
- * @group Collection View
28
- */
29
- export interface CollectionViewProps<T = Record<string, unknown>> {
30
- // ── Data ──────────────────────────────────────────────────────────
31
-
32
- /**
33
- * Data controller providing rows, loading state, pagination,
34
- * filter/sort/search state and setters.
35
- */
36
- dataController: CollectionDataController<T>;
37
-
38
- /**
39
- * Property definitions keyed by property key.
40
- * Drives column rendering, cell formatting, and filter UI.
41
- */
42
- properties: Record<string, CollectionPropertyConfig>;
43
-
44
- /**
45
- * Display order of properties. If omitted, uses Object.keys(properties).
46
- */
47
- propertiesOrder?: string[];
48
-
49
- /**
50
- * Subset of property keys to actually display as columns.
51
- * If omitted, all non-hidden properties are shown.
52
- */
53
- displayedColumnIds?: string[];
54
-
55
- // ── Identity ──────────────────────────────────────────────────────
56
-
57
- /** Property key used as row identifier. Default: "id" */
58
- idProperty?: string;
59
-
60
- /** Property key used as the display title in card/list views */
61
- titleProperty?: string;
62
-
63
- // ── View configuration ────────────────────────────────────────────
64
-
65
- /** Title displayed above the collection. `false` to hide. */
66
- title?: string | false;
67
-
68
- /** Controlled view mode. If omitted, internal state is used. */
69
- viewMode?: CollectionViewMode;
70
-
71
- /** Default view mode when uncontrolled. Default: "list" */
72
- defaultViewMode?: CollectionViewMode;
73
-
74
- /** Which view modes are available in the toggle */
75
- enabledViews?: CollectionViewMode[];
76
-
77
- /** Callback when view mode changes */
78
- onViewModeChange?: (mode: CollectionViewMode) => void;
79
-
80
- // ── Size ──────────────────────────────────────────────────────────
81
-
82
- /** Controlled size. If omitted, internal state is used. */
83
- size?: CollectionViewSize;
84
-
85
- /** Default size. Default: "m" */
86
- defaultSize?: CollectionViewSize;
87
-
88
- /** Callback when size changes */
89
- onSizeChange?: (size: CollectionViewSize) => void;
90
-
91
- // ── Kanban ─────────────────────────────────────────────────────────
92
-
93
- /** Property key for kanban column grouping */
94
- kanbanProperty?: string;
95
-
96
- /** Available kanban property options (shown in view toggle) */
97
- kanbanPropertyOptions?: KanbanPropertyOption[];
98
-
99
- /** Callback when kanban column property changes */
100
- onKanbanPropertyChange?: (property: string) => void;
101
-
102
- // ── Interactions ──────────────────────────────────────────────────
103
-
104
- /** Called when a row is clicked */
105
- onRowClick?: (row: T) => void;
106
-
107
- /** Called when the "create" button is clicked */
108
- onRowCreate?: (defaults?: Record<string, unknown>) => void;
109
-
110
- /** Whether creation is allowed (shows the + button) */
111
- canCreate?: boolean;
112
-
113
- // ── Selection ─────────────────────────────────────────────────────
114
-
115
- /** Enable row selection checkboxes */
116
- selectionEnabled?: boolean;
117
-
118
- /** Selection controller */
119
- selectionController?: CollectionSelectionController<T>;
120
-
121
- /** Highlighted items (e.g. recently clicked) */
122
- highlightedItems?: T[];
123
-
124
- /** Callback for bulk delete */
125
- onMultipleDelete?: (rows: T[]) => void;
126
-
127
- // ── Custom rendering ──────────────────────────────────────────────
128
-
129
- /**
130
- * Custom cell renderer override. Return undefined to use default.
131
- * This is how the connected wrapper injects PropertyPreview.
132
- */
133
- cellRenderer?: CellRendererOverride<T>;
134
-
135
- /** Custom empty state component */
136
- emptyComponent?: React.ReactNode;
137
-
138
- /**
139
- * Additional ways to render these rows, keyed by view mode. A key here can
140
- * be named by `viewMode`, `defaultViewMode` and `enabledViews`, and gets
141
- * its own entry in the view switcher.
142
- *
143
- * A custom view is another rendering of the *same* `dataController`, so it
144
- * inherits the toolbar's search, the loading state and selection. A
145
- * component that fetches its own data does not want to be a view mode —
146
- * the toolbar above it would be describing a query it does not render.
147
- *
148
- * @example
149
- * ```tsx
150
- * <CollectionView
151
- * customViews={{ map: { name: "Map", Component: MapView } }}
152
- * enabledViews={["table", "map"]}
153
- * defaultViewMode="map"
154
- * />
155
- * ```
156
- */
157
- customViews?: Record<string, CollectionCustomViewEntry<T>>;
158
-
159
- // ── Toolbar ───────────────────────────────────────────────────────
160
-
161
- /** Actions rendered at the start (left) of the toolbar */
162
- toolbarActionsStart?: React.ReactNode;
163
-
164
- /** Actions rendered at the end (right) of the toolbar */
165
- toolbarActionsEnd?: React.ReactNode;
166
-
167
- /** Hide the built-in toolbar entirely */
168
- hideToolbar?: boolean;
169
-
170
- // ── Table-specific ────────────────────────────────────────────────
171
-
172
- /** Enable row hover highlight in table view */
173
- hoverRow?: boolean;
174
-
175
- /** Callback when table columns are resized */
176
- onColumnResize?: (params: { key: string; width: number }) => void;
177
-
178
- /** Callback when table columns are reordered */
179
- onColumnsOrderChange?: (keys: string[]) => void;
180
-
181
- // ── Style ─────────────────────────────────────────────────────────
182
-
183
- /** CSS class for the outer container */
184
- className?: string;
185
- }
186
-
187
- const DEFAULT_ENABLED_VIEWS: CollectionViewMode[] = ["list", "table", "cards", "kanban"];
188
-
189
- /**
190
- * A data-agnostic collection view component that orchestrates table, card,
191
- * list, and kanban views — driven entirely by props.
192
- *
193
- * Zero imports from any entity or data layer. This component lives in
194
- * `@rebasepro/ui` and can be used with any data source.
195
- *
196
- * @example
197
- * ```tsx
198
- * import { CollectionView } from "@rebasepro/ui";
199
- *
200
- * <CollectionView
201
- * dataController={{ data: myRows, loading: false, noMoreToLoad: true }}
202
- * properties={{
203
- * name: { type: "string", name: "Name" },
204
- * email: { type: "string", name: "Email" },
205
- * }}
206
- * title="Contacts"
207
- * onRowClick={(row) => openDetail(row.id)}
208
- * />
209
- * ```
210
- *
211
- * @group Collection View
212
- */
213
- export function CollectionView<T extends Record<string, unknown> = Record<string, unknown>>({
214
- dataController,
215
- properties,
216
- propertiesOrder,
217
- displayedColumnIds,
218
- idProperty = "id",
219
- titleProperty,
220
- title,
221
- viewMode: viewModeProp,
222
- defaultViewMode = "list",
223
- enabledViews = DEFAULT_ENABLED_VIEWS,
224
- onViewModeChange: onViewModeChangeProp,
225
- size: sizeProp,
226
- defaultSize = "m",
227
- onSizeChange: onSizeChangeProp,
228
- kanbanProperty: kanbanPropertyProp,
229
- kanbanPropertyOptions,
230
- onKanbanPropertyChange: onKanbanPropertyChangeProp,
231
- onRowClick,
232
- onRowCreate,
233
- canCreate = true,
234
- selectionEnabled,
235
- selectionController,
236
- highlightedItems,
237
- onMultipleDelete,
238
- cellRenderer,
239
- emptyComponent,
240
- customViews,
241
- toolbarActionsStart,
242
- toolbarActionsEnd,
243
- hideToolbar,
244
- hoverRow = true,
245
- onColumnResize,
246
- onColumnsOrderChange,
247
- className
248
- }: CollectionViewProps<T>) {
249
-
250
- // ── View mode state (uncontrolled with controlled override) ───────
251
-
252
- const [internalViewMode, setInternalViewMode] = useState<CollectionViewMode>(defaultViewMode);
253
- const viewMode = viewModeProp ?? internalViewMode;
254
-
255
- const onViewModeChange = useCallback((mode: CollectionViewMode) => {
256
- setInternalViewMode(mode);
257
- onViewModeChangeProp?.(mode);
258
- }, [onViewModeChangeProp]);
259
-
260
- // ── Size state (uncontrolled with controlled override) ────────────
261
-
262
- const [internalSize, setInternalSize] = useState<CollectionViewSize>(defaultSize);
263
- const size = sizeProp ?? internalSize;
264
-
265
- const onSizeChange = useCallback((s: CollectionViewSize) => {
266
- setInternalSize(s);
267
- onSizeChangeProp?.(s);
268
- }, [onSizeChangeProp]);
269
-
270
- // ── Kanban property state ─────────────────────────────────────────
271
-
272
- const [internalKanbanProperty, setInternalKanbanProperty] = useState<string | undefined>(kanbanPropertyProp);
273
- const kanbanProperty = kanbanPropertyProp ?? internalKanbanProperty;
274
-
275
- const onKanbanPropertyChange = useCallback((property: string) => {
276
- setInternalKanbanProperty(property);
277
- onKanbanPropertyChangeProp?.(property);
278
- }, [onKanbanPropertyChangeProp]);
279
-
280
- // Auto-detect kanban property if not provided
281
- const resolvedKanbanProperty = useMemo(() => {
282
- if (kanbanProperty) return kanbanProperty;
283
- // Find first enum property
284
- const order = propertiesOrder ?? Object.keys(properties);
285
- for (const key of order) {
286
- const prop = properties[key];
287
- if (prop && prop.type === "string" && prop.enum) {
288
- return key;
289
- }
290
- }
291
- return undefined;
292
- }, [kanbanProperty, properties, propertiesOrder]);
293
-
294
- // Build kanban property options from properties if not provided
295
- const resolvedKanbanPropertyOptions = useMemo((): KanbanPropertyOption[] => {
296
- if (kanbanPropertyOptions) return kanbanPropertyOptions;
297
- const options: KanbanPropertyOption[] = [];
298
- const order = propertiesOrder ?? Object.keys(properties);
299
- for (const key of order) {
300
- const prop = properties[key];
301
- if (prop && prop.type === "string" && prop.enum) {
302
- options.push({ key, label: prop.name });
303
- }
304
- }
305
- return options;
306
- }, [kanbanPropertyOptions, properties, propertiesOrder]);
307
-
308
- // ── Render ────────────────────────────────────────────────────────
309
-
310
- const sharedProps = {
311
- dataController,
312
- properties,
313
- propertiesOrder,
314
- idProperty,
315
- titleProperty,
316
- onRowClick,
317
- cellRenderer,
318
- selectionEnabled,
319
- selectionController,
320
- highlightedItems,
321
- emptyComponent,
322
- };
323
-
324
- // Resolved once so both the switcher and the render path agree. A custom
325
- // view keyed as a built-in is ignored rather than allowed to shadow it —
326
- // the switch below checks the built-ins first, so honouring such a key
327
- // here would put the switcher and the canvas out of step.
328
- const customViewEntries = useMemo(() => {
329
- if (!customViews) return [];
330
- return Object.entries(customViews)
331
- .filter(([key]) => !DEFAULT_ENABLED_VIEWS.includes(key))
332
- .map(([key, entry]) => {
333
- const isBare = typeof entry === "function";
334
- return {
335
- key,
336
- name: isBare ? key : (entry.name ?? key),
337
- icon: isBare ? undefined : entry.icon,
338
- Component: isBare ? entry : entry.Component
339
- };
340
- });
341
- }, [customViews]);
342
-
343
- function renderView() {
344
- const custom = customViewEntries.find((entry) => entry.key === viewMode);
345
- if (custom) {
346
- const { Component } = custom;
347
- return (
348
- <Component
349
- {...sharedProps}
350
- onRowCreate={canCreate ? onRowCreate : undefined}
351
- canCreate={canCreate}
352
- size={size}
353
- />
354
- );
355
- }
356
-
357
- switch (viewMode) {
358
- case "table":
359
- return (
360
- <CollectionTableView<T>
361
- {...sharedProps}
362
- displayedColumnIds={displayedColumnIds}
363
- size={size}
364
- hoverRow={hoverRow}
365
- onColumnResize={onColumnResize}
366
- onColumnsOrderChange={onColumnsOrderChange}
367
- />
368
- );
369
- case "cards":
370
- return (
371
- <CollectionCardView<T>
372
- {...sharedProps}
373
- size={size}
374
- />
375
- );
376
- case "kanban":
377
- return resolvedKanbanProperty ? (
378
- <CollectionKanbanView<T>
379
- {...sharedProps}
380
- kanbanProperty={resolvedKanbanProperty}
381
- // `canCreate` is documented as "whether creation is
382
- // allowed (shows the + button)" and was destructured
383
- // and read nowhere, so the button showed regardless.
384
- // The kanban view draws its add affordance exactly when
385
- // it has a callback, so withholding the callback is
386
- // what the prop means.
387
- onRowCreate={canCreate ? onRowCreate : undefined}
388
- />
389
- ) : (
390
- <div className="flex items-center justify-center p-8">
391
- <Typography variant="body2" color="secondary">
392
- No enum property available for kanban grouping.
393
- </Typography>
394
- </div>
395
- );
396
- case "list":
397
- default:
398
- return (
399
- <CollectionListView<T>
400
- {...sharedProps}
401
- size={size}
402
- />
403
- );
404
- }
405
- }
406
-
407
- return (
408
- <div
409
- className={cls(
410
- "overflow-hidden h-full w-full rounded-md flex flex-col",
411
- "dark:bg-surface-800",
412
- className
413
- )}
414
- >
415
- {/* Error banner */}
416
- {dataController.error && dataController.data.length > 0 && (
417
- <div className="flex items-center gap-4 px-4 py-2 bg-red-50 dark:bg-red-900/20 border-b border-red-200 dark:border-red-800 flex-shrink-0">
418
- <Typography variant="body2" className="text-red-700 dark:text-red-300 flex-1">
419
- <strong>Warning:</strong> {dataController.error.message || "Failed to update data."}
420
- </Typography>
421
- </div>
422
- )}
423
-
424
- {/* Toolbar */}
425
- {!hideToolbar && (
426
- <CollectionViewToolbar
427
- viewMode={viewMode}
428
- onViewModeChange={enabledViews.length > 1 ? onViewModeChange : undefined}
429
- enabledViews={enabledViews}
430
- customViews={customViewEntries}
431
- size={size}
432
- onSizeChange={onSizeChange}
433
- searchString={dataController.searchString}
434
- onSearchChange={dataController.setSearchString}
435
- loading={dataController.loading}
436
- actionsStart={toolbarActionsStart}
437
- actionsEnd={toolbarActionsEnd}
438
- kanbanPropertyOptions={viewMode === "kanban" ? resolvedKanbanPropertyOptions : undefined}
439
- selectedKanbanProperty={resolvedKanbanProperty}
440
- onKanbanPropertyChange={onKanbanPropertyChange}
441
- />
442
- )}
443
-
444
- {/* View */}
445
- <div className="flex-1 overflow-hidden">
446
- {renderView()}
447
- </div>
448
- </div>
449
- );
450
- }