@actabldesign/bellhop-react 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/stencil-generated/components.d.ts +412 -0
- package/components/stencil-generated/components.d.ts.map +1 -0
- package/components/stencil-generated/components.js +708 -0
- package/components/stencil-generated/index.d.ts +7 -0
- package/components/stencil-generated/index.d.ts.map +1 -0
- package/components/stencil-generated/index.js +6 -0
- package/components/table/DataTable.d.ts +137 -0
- package/components/table/DataTable.d.ts.map +1 -0
- package/components/table/DataTable.js +514 -0
- package/components/table/index.d.ts +168 -0
- package/components/table/index.d.ts.map +1 -0
- package/components/table/index.js +95 -0
- package/index.d.ts +14 -0
- package/index.d.ts.map +1 -0
- package/index.js +21 -0
- package/package.json +28 -0
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* BhDataTable - Dynamic Table with TanStack Table
|
|
4
|
+
*
|
|
5
|
+
* A composable data table component that combines BellhopOS table primitives
|
|
6
|
+
* with TanStack Table for sorting, filtering, pagination, selection,
|
|
7
|
+
* expansion, grouping, and editing.
|
|
8
|
+
* Uses existing Bellhop components where possible.
|
|
9
|
+
*/
|
|
10
|
+
import React, { useState, useMemo, useRef, useEffect, useCallback, } from 'react';
|
|
11
|
+
import { useReactTable, getCoreRowModel, getSortedRowModel, getFilteredRowModel, getPaginationRowModel, getExpandedRowModel, getGroupedRowModel, flexRender, } from '@tanstack/react-table';
|
|
12
|
+
import { Table, TableWrapper, TableHead, TableBody, TableRow, TableHeaderCell, TableCell, TableEmpty, TableActionBar, TablePagination, } from './index';
|
|
13
|
+
// Import existing Bellhop components
|
|
14
|
+
import { BhButtonIcon, BhInputText } from '../stencil-generated';
|
|
15
|
+
function TableCheckbox({ checked, indeterminate = false, disabled = false, onChange, 'aria-label': ariaLabel, }) {
|
|
16
|
+
const inputRef = useRef(null);
|
|
17
|
+
useEffect(() => {
|
|
18
|
+
if (inputRef.current) {
|
|
19
|
+
inputRef.current.indeterminate = indeterminate;
|
|
20
|
+
}
|
|
21
|
+
}, [indeterminate]);
|
|
22
|
+
return (_jsxs("label", { className: "bh-table-checkbox", children: [_jsx("input", { ref: inputRef, type: "checkbox", checked: checked, disabled: disabled, onChange: onChange, "aria-label": ariaLabel, className: "bh-table-checkbox__input" }), _jsxs("span", { className: `bh-table-checkbox__box ${checked || indeterminate ? 'bh-table-checkbox__box--checked' : ''} ${disabled ? 'bh-table-checkbox__box--disabled' : ''}`, children: [checked && !indeterminate && (_jsx("span", { className: "material-symbols-outlined bh-table-checkbox__icon", children: "check" })), indeterminate && (_jsx("span", { className: "material-symbols-outlined bh-table-checkbox__icon", children: "remove" }))] })] }));
|
|
23
|
+
}
|
|
24
|
+
// =============================================================================
|
|
25
|
+
// Checkbox Column Helper
|
|
26
|
+
// =============================================================================
|
|
27
|
+
export function createSelectColumn() {
|
|
28
|
+
return {
|
|
29
|
+
id: 'select',
|
|
30
|
+
header: ({ table }) => (_jsx(TableCheckbox, { checked: table.getIsAllPageRowsSelected(), indeterminate: table.getIsSomePageRowsSelected(), onChange: () => table.toggleAllPageRowsSelected(), "aria-label": "Select all rows" })),
|
|
31
|
+
cell: ({ row }) => (_jsx(TableCheckbox, { checked: row.getIsSelected(), disabled: !row.getCanSelect(), onChange: () => row.toggleSelected(), "aria-label": "Select row" })),
|
|
32
|
+
size: 40,
|
|
33
|
+
enableSorting: false,
|
|
34
|
+
enableColumnFilter: false,
|
|
35
|
+
enableGrouping: false,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
// =============================================================================
|
|
39
|
+
// Expand Column Helper
|
|
40
|
+
// =============================================================================
|
|
41
|
+
export function createExpandColumn() {
|
|
42
|
+
return {
|
|
43
|
+
id: 'expand',
|
|
44
|
+
header: () => null,
|
|
45
|
+
cell: ({ row }) => row.getCanExpand() ? (_jsx(BhButtonIcon, { iconName: row.getIsExpanded() ? 'expand_more' : 'chevron_right', size: "sm", hierarchy: "quaternary", onBhClick: () => row.toggleExpanded(), "aria-label": row.getIsExpanded() ? 'Collapse row' : 'Expand row' })) : null,
|
|
46
|
+
size: 32,
|
|
47
|
+
enableSorting: false,
|
|
48
|
+
enableColumnFilter: false,
|
|
49
|
+
enableGrouping: false,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
// =============================================================================
|
|
53
|
+
// Edit Actions Column Helper
|
|
54
|
+
// =============================================================================
|
|
55
|
+
export function createEditActionsColumn(onEdit, onSave, onCancel, isEditing) {
|
|
56
|
+
return {
|
|
57
|
+
id: 'edit-actions',
|
|
58
|
+
header: () => null,
|
|
59
|
+
cell: ({ row }) => {
|
|
60
|
+
const rowId = row.id;
|
|
61
|
+
const editing = isEditing(rowId);
|
|
62
|
+
if (editing) {
|
|
63
|
+
return (_jsxs("div", { className: "bh-td--edit-actions", children: [_jsx(BhButtonIcon, { iconName: "check", size: "sm", hierarchy: "quaternary", onBhClick: () => onSave(), "aria-label": "Save changes" }), _jsx(BhButtonIcon, { iconName: "close", size: "sm", hierarchy: "quaternary", onBhClick: () => onCancel(), "aria-label": "Cancel editing" })] }));
|
|
64
|
+
}
|
|
65
|
+
return (_jsx(BhButtonIcon, { iconName: "edit", size: "sm", hierarchy: "quaternary", onBhClick: () => onEdit(rowId), "aria-label": "Edit row" }));
|
|
66
|
+
},
|
|
67
|
+
size: 80,
|
|
68
|
+
enableSorting: false,
|
|
69
|
+
enableColumnFilter: false,
|
|
70
|
+
enableGrouping: false,
|
|
71
|
+
meta: {
|
|
72
|
+
editable: false,
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
// =============================================================================
|
|
77
|
+
// DataTable Component
|
|
78
|
+
// =============================================================================
|
|
79
|
+
export function DataTable({ data, columns, size = 'default', variant = 'default', enableRowSelection = false, enableMultiRowSelection = true, enableSorting = true, enableFiltering = false, enablePagination = true, enableExpanding = false, pageSize = 10, pageSizeOptions = [10, 25, 50, 100], stickyHeader = false, rounded = false, hoverable = true, loading = false, emptyTitle = 'No data', emptyDescription = 'There are no records to display.', getRowId, getSubRows, renderExpandedRow, onSelectionChange, onSortingChange, onRowClick, actionBarLeft, actionBarRight, bulkActions, maxHeight, className,
|
|
80
|
+
// Grouping props
|
|
81
|
+
enableGrouping = false, groupBy = [], onGroupingChange,
|
|
82
|
+
// Editing props
|
|
83
|
+
enableEditing = false, editMode = 'cell', showEditActions = true, onEditSave, onEditCancel, onCellChange, }) {
|
|
84
|
+
// ==========================================================================
|
|
85
|
+
// State
|
|
86
|
+
// ==========================================================================
|
|
87
|
+
const [sorting, setSorting] = useState([]);
|
|
88
|
+
const [columnFilters, setColumnFilters] = useState([]);
|
|
89
|
+
const [rowSelection, setRowSelection] = useState({});
|
|
90
|
+
const [expanded, setExpanded] = useState({});
|
|
91
|
+
const [pagination, setPagination] = useState({
|
|
92
|
+
pageIndex: 0,
|
|
93
|
+
pageSize,
|
|
94
|
+
});
|
|
95
|
+
const [globalFilter, setGlobalFilter] = useState('');
|
|
96
|
+
// Grouping state
|
|
97
|
+
const [grouping, setGrouping] = useState(groupBy);
|
|
98
|
+
// Editing state
|
|
99
|
+
const [editState, setEditState] = useState({
|
|
100
|
+
rowId: null,
|
|
101
|
+
columnId: null,
|
|
102
|
+
});
|
|
103
|
+
const [editingValues, setEditingValues] = useState({});
|
|
104
|
+
// Sync groupBy prop with internal state
|
|
105
|
+
useEffect(() => {
|
|
106
|
+
setGrouping(groupBy);
|
|
107
|
+
}, [groupBy]);
|
|
108
|
+
// Track previous rowSelection to detect changes for onSelectionChange callback
|
|
109
|
+
const prevRowSelectionRef = useRef(rowSelection);
|
|
110
|
+
// Compute column visibility to hide grouping columns when grouping is enabled
|
|
111
|
+
const columnVisibility = useMemo(() => {
|
|
112
|
+
if (!enableGrouping || grouping.length === 0) {
|
|
113
|
+
return {};
|
|
114
|
+
}
|
|
115
|
+
// Hide all columns that are being used for grouping
|
|
116
|
+
const visibility = {};
|
|
117
|
+
grouping.forEach((columnId) => {
|
|
118
|
+
visibility[columnId] = false;
|
|
119
|
+
});
|
|
120
|
+
return visibility;
|
|
121
|
+
}, [enableGrouping, grouping]);
|
|
122
|
+
// ==========================================================================
|
|
123
|
+
// Editing Handlers
|
|
124
|
+
// ==========================================================================
|
|
125
|
+
const isEditing = useCallback((rowId, columnId) => {
|
|
126
|
+
if (editState.rowId !== rowId)
|
|
127
|
+
return false;
|
|
128
|
+
if (editMode === 'cell') {
|
|
129
|
+
return columnId ? editState.columnId === columnId : false;
|
|
130
|
+
}
|
|
131
|
+
return true; // Row mode - entire row is editing
|
|
132
|
+
}, [editState, editMode]);
|
|
133
|
+
const isRowEditing = useCallback((rowId) => {
|
|
134
|
+
return editState.rowId === rowId;
|
|
135
|
+
}, [editState.rowId]);
|
|
136
|
+
const startEditing = useCallback((rowId, columnId) => {
|
|
137
|
+
// Initialize editing values from current row
|
|
138
|
+
const initialValues = {};
|
|
139
|
+
columns.forEach((col) => {
|
|
140
|
+
const colId = 'accessorKey' in col
|
|
141
|
+
? String(col.accessorKey)
|
|
142
|
+
: 'id' in col
|
|
143
|
+
? col.id
|
|
144
|
+
: '';
|
|
145
|
+
if (colId && col.meta?.editable !== false) {
|
|
146
|
+
// Find row data
|
|
147
|
+
const rowIndex = data.findIndex((_, i) => (getRowId ? getRowId(data[i], i) : String(i)) === rowId);
|
|
148
|
+
if (rowIndex !== -1) {
|
|
149
|
+
const row = data[rowIndex];
|
|
150
|
+
initialValues[colId] = row[colId];
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
setEditingValues(initialValues);
|
|
155
|
+
setEditState({
|
|
156
|
+
rowId,
|
|
157
|
+
columnId: editMode === 'cell' ? columnId || null : null,
|
|
158
|
+
});
|
|
159
|
+
}, [columns, data, editMode, getRowId]);
|
|
160
|
+
const cancelEditing = useCallback(() => {
|
|
161
|
+
if (!editState.rowId)
|
|
162
|
+
return;
|
|
163
|
+
const rowIndex = data.findIndex((_, i) => (getRowId ? getRowId(data[i], i) : String(i)) === editState.rowId);
|
|
164
|
+
if (rowIndex !== -1) {
|
|
165
|
+
onEditCancel?.(editState.rowId, data[rowIndex]);
|
|
166
|
+
}
|
|
167
|
+
setEditState({ rowId: null, columnId: null });
|
|
168
|
+
setEditingValues({});
|
|
169
|
+
}, [editState.rowId, data, getRowId, onEditCancel]);
|
|
170
|
+
const saveEditing = useCallback(() => {
|
|
171
|
+
if (!editState.rowId)
|
|
172
|
+
return;
|
|
173
|
+
const rowIndex = data.findIndex((_, i) => (getRowId ? getRowId(data[i], i) : String(i)) === editState.rowId);
|
|
174
|
+
if (rowIndex === -1)
|
|
175
|
+
return;
|
|
176
|
+
const originalRow = data[rowIndex];
|
|
177
|
+
const changes = {};
|
|
178
|
+
// Determine what changed
|
|
179
|
+
Object.entries(editingValues).forEach(([columnId, newValue]) => {
|
|
180
|
+
const originalValue = originalRow[columnId];
|
|
181
|
+
if (originalValue !== newValue) {
|
|
182
|
+
changes[columnId] = newValue;
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
if (Object.keys(changes).length > 0) {
|
|
186
|
+
onEditSave?.({
|
|
187
|
+
rowId: editState.rowId,
|
|
188
|
+
rowIndex,
|
|
189
|
+
originalRow,
|
|
190
|
+
changes,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
setEditState({ rowId: null, columnId: null });
|
|
194
|
+
setEditingValues({});
|
|
195
|
+
}, [editState.rowId, editingValues, data, getRowId, onEditSave]);
|
|
196
|
+
const handleEditValueChange = useCallback((columnId, newValue) => {
|
|
197
|
+
const oldValue = editingValues[columnId];
|
|
198
|
+
setEditingValues((prev) => ({
|
|
199
|
+
...prev,
|
|
200
|
+
[columnId]: newValue,
|
|
201
|
+
}));
|
|
202
|
+
if (editState.rowId) {
|
|
203
|
+
onCellChange?.(editState.rowId, columnId, oldValue, newValue);
|
|
204
|
+
}
|
|
205
|
+
}, [editingValues, editState.rowId, onCellChange]);
|
|
206
|
+
const handleEditKeyDown = useCallback((e, columnId) => {
|
|
207
|
+
if (e.key === 'Enter') {
|
|
208
|
+
e.preventDefault();
|
|
209
|
+
saveEditing();
|
|
210
|
+
}
|
|
211
|
+
else if (e.key === 'Escape') {
|
|
212
|
+
e.preventDefault();
|
|
213
|
+
cancelEditing();
|
|
214
|
+
}
|
|
215
|
+
else if (e.key === 'Tab' && editMode === 'cell') {
|
|
216
|
+
e.preventDefault();
|
|
217
|
+
// Move to next editable cell
|
|
218
|
+
const editableColumns = columns.filter((col) => col.meta?.editable !== false);
|
|
219
|
+
const currentIndex = editableColumns.findIndex((c) => {
|
|
220
|
+
const colId = 'accessorKey' in c
|
|
221
|
+
? String(c.accessorKey)
|
|
222
|
+
: 'id' in c
|
|
223
|
+
? c.id
|
|
224
|
+
: '';
|
|
225
|
+
return colId === columnId;
|
|
226
|
+
});
|
|
227
|
+
if (currentIndex !== -1) {
|
|
228
|
+
const nextIndex = e.shiftKey ? currentIndex - 1 : currentIndex + 1;
|
|
229
|
+
if (nextIndex >= 0 && nextIndex < editableColumns.length) {
|
|
230
|
+
const nextCol = editableColumns[nextIndex];
|
|
231
|
+
const nextColId = 'accessorKey' in nextCol
|
|
232
|
+
? String(nextCol.accessorKey)
|
|
233
|
+
: 'id' in nextCol
|
|
234
|
+
? nextCol.id
|
|
235
|
+
: '';
|
|
236
|
+
setEditState((prev) => ({
|
|
237
|
+
...prev,
|
|
238
|
+
columnId: nextColId || null,
|
|
239
|
+
}));
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
saveEditing();
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}, [editMode, columns, saveEditing, cancelEditing]);
|
|
247
|
+
// ==========================================================================
|
|
248
|
+
// Build columns with selection/expansion/editing if enabled
|
|
249
|
+
// ==========================================================================
|
|
250
|
+
const tableColumns = useMemo(() => {
|
|
251
|
+
const cols = [];
|
|
252
|
+
if (enableExpanding && !enableGrouping) {
|
|
253
|
+
cols.push(createExpandColumn());
|
|
254
|
+
}
|
|
255
|
+
if (enableRowSelection) {
|
|
256
|
+
cols.push(createSelectColumn());
|
|
257
|
+
}
|
|
258
|
+
cols.push(...columns);
|
|
259
|
+
if (enableEditing && editMode === 'row' && showEditActions) {
|
|
260
|
+
cols.push(createEditActionsColumn(startEditing, saveEditing, cancelEditing, isRowEditing));
|
|
261
|
+
}
|
|
262
|
+
return cols;
|
|
263
|
+
}, [
|
|
264
|
+
columns,
|
|
265
|
+
enableRowSelection,
|
|
266
|
+
enableExpanding,
|
|
267
|
+
enableGrouping,
|
|
268
|
+
enableEditing,
|
|
269
|
+
editMode,
|
|
270
|
+
showEditActions,
|
|
271
|
+
startEditing,
|
|
272
|
+
saveEditing,
|
|
273
|
+
cancelEditing,
|
|
274
|
+
isRowEditing,
|
|
275
|
+
]);
|
|
276
|
+
// ==========================================================================
|
|
277
|
+
// Create table instance
|
|
278
|
+
// ==========================================================================
|
|
279
|
+
const table = useReactTable({
|
|
280
|
+
data,
|
|
281
|
+
columns: tableColumns,
|
|
282
|
+
state: {
|
|
283
|
+
sorting,
|
|
284
|
+
columnFilters,
|
|
285
|
+
rowSelection,
|
|
286
|
+
expanded,
|
|
287
|
+
pagination,
|
|
288
|
+
globalFilter,
|
|
289
|
+
grouping,
|
|
290
|
+
columnVisibility,
|
|
291
|
+
},
|
|
292
|
+
getRowId,
|
|
293
|
+
getSubRows,
|
|
294
|
+
getRowCanExpand: enableExpanding && renderExpandedRow ? () => true : undefined,
|
|
295
|
+
enableRowSelection: enableRowSelection
|
|
296
|
+
? enableMultiRowSelection
|
|
297
|
+
? true
|
|
298
|
+
: (row) => !Object.keys(rowSelection).length || rowSelection[row.id]
|
|
299
|
+
: false,
|
|
300
|
+
enableMultiRowSelection,
|
|
301
|
+
enableSorting,
|
|
302
|
+
enableFilters: enableFiltering,
|
|
303
|
+
enableExpanding: enableExpanding || enableGrouping,
|
|
304
|
+
enableGrouping,
|
|
305
|
+
// Prevent TanStack Table from auto-resetting expanded state when data/columns change
|
|
306
|
+
// This fixes the issue where grouped rows collapse when selecting items
|
|
307
|
+
autoResetExpanded: false,
|
|
308
|
+
onSortingChange: (updater) => {
|
|
309
|
+
const newSorting = typeof updater === 'function' ? updater(sorting) : updater;
|
|
310
|
+
setSorting(newSorting);
|
|
311
|
+
onSortingChange?.(newSorting);
|
|
312
|
+
},
|
|
313
|
+
onColumnFiltersChange: setColumnFilters,
|
|
314
|
+
onRowSelectionChange: (updater) => {
|
|
315
|
+
const newSelection = typeof updater === 'function' ? updater(rowSelection) : updater;
|
|
316
|
+
setRowSelection(newSelection);
|
|
317
|
+
},
|
|
318
|
+
onExpandedChange: setExpanded,
|
|
319
|
+
onPaginationChange: setPagination,
|
|
320
|
+
onGlobalFilterChange: setGlobalFilter,
|
|
321
|
+
onGroupingChange: (updater) => {
|
|
322
|
+
const newGrouping = typeof updater === 'function' ? updater(grouping) : updater;
|
|
323
|
+
setGrouping(newGrouping);
|
|
324
|
+
onGroupingChange?.(newGrouping);
|
|
325
|
+
},
|
|
326
|
+
getCoreRowModel: getCoreRowModel(),
|
|
327
|
+
getSortedRowModel: enableSorting ? getSortedRowModel() : undefined,
|
|
328
|
+
getFilteredRowModel: enableFiltering ? getFilteredRowModel() : undefined,
|
|
329
|
+
// Disable pagination when grouping is enabled
|
|
330
|
+
getPaginationRowModel: enablePagination && !enableGrouping ? getPaginationRowModel() : undefined,
|
|
331
|
+
getExpandedRowModel: enableExpanding || enableGrouping ? getExpandedRowModel() : undefined,
|
|
332
|
+
getGroupedRowModel: enableGrouping ? getGroupedRowModel() : undefined,
|
|
333
|
+
});
|
|
334
|
+
// Handle selection change callback after table is stable (via useEffect)
|
|
335
|
+
// This prevents issues with grouped rows collapsing when selecting items
|
|
336
|
+
// IMPORTANT: We use a ref for the table to avoid the effect re-running when table changes
|
|
337
|
+
// because table is recreated on every render, which would cause cascading state updates
|
|
338
|
+
const tableRef = useRef(table);
|
|
339
|
+
tableRef.current = table;
|
|
340
|
+
useEffect(() => {
|
|
341
|
+
if (!onSelectionChange)
|
|
342
|
+
return;
|
|
343
|
+
if (prevRowSelectionRef.current === rowSelection)
|
|
344
|
+
return;
|
|
345
|
+
prevRowSelectionRef.current = rowSelection;
|
|
346
|
+
// Use tableRef.current to get the latest table without adding it as a dependency
|
|
347
|
+
const currentTable = tableRef.current;
|
|
348
|
+
const selectedRows = Object.keys(rowSelection)
|
|
349
|
+
.filter((key) => rowSelection[key])
|
|
350
|
+
.map((key) => {
|
|
351
|
+
const row = currentTable.getRow(key);
|
|
352
|
+
return row?.original;
|
|
353
|
+
})
|
|
354
|
+
.filter(Boolean);
|
|
355
|
+
onSelectionChange(selectedRows);
|
|
356
|
+
}, [rowSelection, onSelectionChange]);
|
|
357
|
+
// ==========================================================================
|
|
358
|
+
// Render Helpers
|
|
359
|
+
// ==========================================================================
|
|
360
|
+
const renderEditInput = (column, cell) => {
|
|
361
|
+
const columnId = 'accessorKey' in column.columnDef
|
|
362
|
+
? String(column.columnDef.accessorKey)
|
|
363
|
+
: column.id;
|
|
364
|
+
const currentValue = editingValues[columnId] ?? cell.getValue();
|
|
365
|
+
const editType = column.columnDef.meta?.editType || 'text';
|
|
366
|
+
const editOptions = column.columnDef.meta?.editOptions;
|
|
367
|
+
// Select type
|
|
368
|
+
if (editType === 'select' && editOptions) {
|
|
369
|
+
return (_jsx("select", { className: "bh-edit-select", value: String(currentValue ?? ''), onChange: (e) => handleEditValueChange(columnId, e.target.value), onKeyDown: (e) => handleEditKeyDown(e, columnId), autoFocus: true, children: editOptions.map((opt) => (_jsx("option", { value: opt.value, children: opt.label }, opt.value))) }));
|
|
370
|
+
}
|
|
371
|
+
// Text/Number/Date input
|
|
372
|
+
return (_jsx("input", { type: editType === 'number' ? 'number' : editType === 'date' ? 'date' : 'text', className: "bh-edit-input", value: currentValue != null ? String(currentValue) : '', onChange: (e) => {
|
|
373
|
+
const newValue = editType === 'number' ? Number(e.target.value) : e.target.value;
|
|
374
|
+
handleEditValueChange(columnId, newValue);
|
|
375
|
+
}, onKeyDown: (e) => handleEditKeyDown(e, columnId), autoFocus: true }));
|
|
376
|
+
};
|
|
377
|
+
const renderGroupRow = (row) => {
|
|
378
|
+
const groupingColumnId = row.groupingColumnId;
|
|
379
|
+
const groupValue = row.groupingValue;
|
|
380
|
+
const isExpanded = row.getIsExpanded();
|
|
381
|
+
const leafRows = row.getLeafRows();
|
|
382
|
+
const depth = row.depth;
|
|
383
|
+
return (_jsx(TableRow, { className: "bh-tr--group", onClick: () => row.toggleExpanded(), children: _jsx(TableCell, { colSpan: tableColumns.length, className: "bh-td--group-label", children: _jsxs("div", { className: "bh-group-content", style: { paddingLeft: `${depth * 24}px` }, children: [_jsx(BhButtonIcon, { iconName: isExpanded ? 'expand_more' : 'chevron_right', size: "sm", hierarchy: "quaternary", "aria-label": isExpanded ? 'Collapse group' : 'Expand group' }), _jsxs("span", { className: "bh-group-label__text", children: [_jsxs("span", { className: "bh-group-label__column", children: [groupingColumnId, ": "] }), _jsx("span", { className: "bh-group-label__value", children: String(groupValue) })] }), _jsxs("span", { className: "bh-group-label__count", children: ["(", leafRows.length, " ", leafRows.length === 1 ? 'item' : 'items', ")"] })] }) }) }, row.id));
|
|
384
|
+
};
|
|
385
|
+
const renderDataRow = (row) => {
|
|
386
|
+
const rowId = row.id;
|
|
387
|
+
const rowIsEditing = isRowEditing(rowId);
|
|
388
|
+
return (_jsxs(React.Fragment, { children: [_jsx(TableRow, { selected: row.getIsSelected(), clickable: !!onRowClick && !rowIsEditing, expandable: row.getCanExpand(), expanded: row.getIsExpanded(), className: rowIsEditing ? 'bh-tr--editing' : undefined, onClick: onRowClick && !rowIsEditing
|
|
389
|
+
? () => onRowClick(row.original)
|
|
390
|
+
: undefined, children: row.getVisibleCells().map((cell) => {
|
|
391
|
+
const column = cell.column;
|
|
392
|
+
const isCheckbox = column.id === 'select';
|
|
393
|
+
const isExpand = column.id === 'expand';
|
|
394
|
+
const isEditActions = column.id === 'edit-actions';
|
|
395
|
+
const columnId = 'accessorKey' in column.columnDef
|
|
396
|
+
? String(column.columnDef.accessorKey)
|
|
397
|
+
: column.id;
|
|
398
|
+
const canEdit = enableEditing && column.columnDef.meta?.editable !== false;
|
|
399
|
+
const isCellEditing = isEditing(rowId, columnId);
|
|
400
|
+
// For grouped cells, skip rendering aggregated/placeholder cells
|
|
401
|
+
if (cell.getIsGrouped()) {
|
|
402
|
+
return null;
|
|
403
|
+
}
|
|
404
|
+
if (cell.getIsAggregated()) {
|
|
405
|
+
return (_jsx(TableCell, { children: flexRender(column.columnDef.aggregatedCell ?? column.columnDef.cell, cell.getContext()) }, cell.id));
|
|
406
|
+
}
|
|
407
|
+
if (cell.getIsPlaceholder()) {
|
|
408
|
+
return _jsx(TableCell, {}, cell.id);
|
|
409
|
+
}
|
|
410
|
+
return (_jsx(TableCell, { checkbox: isCheckbox, expandTrigger: isExpand, shrink: isCheckbox || isExpand || isEditActions, className: canEdit && !isCellEditing && !isCheckbox && !isExpand && !isEditActions
|
|
411
|
+
? 'bh-td--editable'
|
|
412
|
+
: isCellEditing
|
|
413
|
+
? 'bh-td--editing'
|
|
414
|
+
: undefined, onClick: isCheckbox || isExpand || isEditActions
|
|
415
|
+
? (e) => e.stopPropagation()
|
|
416
|
+
: undefined, onDoubleClick: canEdit &&
|
|
417
|
+
editMode === 'cell' &&
|
|
418
|
+
!isCellEditing &&
|
|
419
|
+
!isCheckbox &&
|
|
420
|
+
!isExpand &&
|
|
421
|
+
!isEditActions
|
|
422
|
+
? () => startEditing(rowId, columnId)
|
|
423
|
+
: undefined, style: {
|
|
424
|
+
width: cell.column.getSize() !== 150
|
|
425
|
+
? cell.column.getSize()
|
|
426
|
+
: undefined,
|
|
427
|
+
}, children: isCellEditing || (rowIsEditing && canEdit && editMode === 'row') ? (renderEditInput(column, cell)) : (flexRender(column.columnDef.cell, cell.getContext())) }, cell.id));
|
|
428
|
+
}) }), row.getIsExpanded() && renderExpandedRow && !row.getIsGrouped() && (_jsx(TableRow, { expansion: true, children: _jsx(TableCell, { expansion: true, colSpan: tableColumns.length, children: renderExpandedRow(row) }) }))] }, row.id));
|
|
429
|
+
};
|
|
430
|
+
// ==========================================================================
|
|
431
|
+
// Render
|
|
432
|
+
// ==========================================================================
|
|
433
|
+
const selectedCount = Object.keys(rowSelection).filter((key) => rowSelection[key]).length;
|
|
434
|
+
const showActionBar = actionBarLeft || actionBarRight || (selectedCount > 0 && bulkActions);
|
|
435
|
+
return (_jsxs("div", { className: className, children: [showActionBar && (_jsx(TableActionBar, { selectionCount: selectedCount, left: selectedCount > 0 ? bulkActions : actionBarLeft, right: actionBarRight })), _jsx(TableWrapper, { maxHeight: maxHeight, scrollY: !!maxHeight, children: _jsxs(Table, { size: size, variant: variant, rounded: rounded, hoverable: hoverable, loading: loading, children: [_jsxs(TableHead, { sticky: stickyHeader, children: [table.getHeaderGroups().map((headerGroup) => (_jsx(TableRow, { children: headerGroup.headers.map((header) => {
|
|
436
|
+
const isSortable = header.column.getCanSort();
|
|
437
|
+
const sortDirection = header.column.getIsSorted();
|
|
438
|
+
const isCheckbox = header.id === 'select';
|
|
439
|
+
const isExpand = header.id === 'expand';
|
|
440
|
+
const isEditActions = header.id === 'edit-actions';
|
|
441
|
+
return (_jsx(TableHeaderCell, { sortable: isSortable, sortDirection: sortDirection || null, onClick: isSortable
|
|
442
|
+
? header.column.getToggleSortingHandler()
|
|
443
|
+
: undefined, checkbox: isCheckbox, shrink: isCheckbox || isExpand || isEditActions, style: {
|
|
444
|
+
width: header.getSize() !== 150
|
|
445
|
+
? header.getSize()
|
|
446
|
+
: undefined,
|
|
447
|
+
}, children: header.isPlaceholder
|
|
448
|
+
? null
|
|
449
|
+
: flexRender(header.column.columnDef.header, header.getContext()) }, header.id));
|
|
450
|
+
}) }, headerGroup.id))), enableFiltering && (_jsx(TableRow, { filter: true, children: table.getHeaderGroups()[0]?.headers.map((header) => {
|
|
451
|
+
const column = header.column;
|
|
452
|
+
const canFilter = column.getCanFilter();
|
|
453
|
+
const isCheckbox = header.id === 'select';
|
|
454
|
+
const isExpand = header.id === 'expand';
|
|
455
|
+
const isEditActions = header.id === 'edit-actions';
|
|
456
|
+
if (!canFilter || isCheckbox || isExpand || isEditActions) {
|
|
457
|
+
return _jsx(TableCell, { filter: true }, header.id);
|
|
458
|
+
}
|
|
459
|
+
return (_jsx(TableCell, { filter: true, children: _jsx(BhInputText, { placeholder: "", label: "", value: column.getFilterValue() ?? '', onBhInput: (e) => column.setFilterValue(e.detail) }) }, header.id));
|
|
460
|
+
}) }))] }), _jsx(TableBody, { children: table.getRowModel().rows.length === 0 ? (_jsx(TableEmpty, { title: emptyTitle, description: emptyDescription, colSpan: tableColumns.length, icon: _jsx("span", { className: "material-symbols-outlined", children: "inbox" }) })) : (table.getRowModel().rows.map((row) => {
|
|
461
|
+
// Check if this is a group row
|
|
462
|
+
if (row.getIsGrouped()) {
|
|
463
|
+
return renderGroupRow(row);
|
|
464
|
+
}
|
|
465
|
+
// Regular data row
|
|
466
|
+
return renderDataRow(row);
|
|
467
|
+
})) })] }) }), enablePagination && !enableGrouping && data.length > 0 && (_jsx(TablePagination, { page: table.getState().pagination.pageIndex + 1, totalPages: table.getPageCount(), totalItems: data.length, pageSize: table.getState().pagination.pageSize, pageSizeOptions: pageSizeOptions, onPageChange: (page) => table.setPageIndex(page - 1), onPageSizeChange: (newSize) => table.setPageSize(newSize) }))] }));
|
|
468
|
+
}
|
|
469
|
+
export function useDataTable({ data, columns, enableSorting = true, enableFiltering = false, enablePagination = true, enableRowSelection = false, enableExpanding = false, enableGrouping = false, groupBy = [], pageSize = 10, getRowId, getSubRows, }) {
|
|
470
|
+
const [sorting, setSorting] = useState([]);
|
|
471
|
+
const [columnFilters, setColumnFilters] = useState([]);
|
|
472
|
+
const [rowSelection, setRowSelection] = useState({});
|
|
473
|
+
const [expanded, setExpanded] = useState({});
|
|
474
|
+
const [pagination, setPagination] = useState({
|
|
475
|
+
pageIndex: 0,
|
|
476
|
+
pageSize,
|
|
477
|
+
});
|
|
478
|
+
const [grouping, setGrouping] = useState(groupBy);
|
|
479
|
+
return useReactTable({
|
|
480
|
+
data,
|
|
481
|
+
columns,
|
|
482
|
+
state: {
|
|
483
|
+
sorting,
|
|
484
|
+
columnFilters,
|
|
485
|
+
rowSelection,
|
|
486
|
+
expanded,
|
|
487
|
+
pagination,
|
|
488
|
+
grouping,
|
|
489
|
+
},
|
|
490
|
+
getRowId,
|
|
491
|
+
getSubRows,
|
|
492
|
+
enableRowSelection,
|
|
493
|
+
enableSorting,
|
|
494
|
+
enableFilters: enableFiltering,
|
|
495
|
+
enableExpanding: enableExpanding || enableGrouping,
|
|
496
|
+
enableGrouping,
|
|
497
|
+
onSortingChange: setSorting,
|
|
498
|
+
onColumnFiltersChange: setColumnFilters,
|
|
499
|
+
onRowSelectionChange: setRowSelection,
|
|
500
|
+
onExpandedChange: setExpanded,
|
|
501
|
+
onPaginationChange: setPagination,
|
|
502
|
+
onGroupingChange: setGrouping,
|
|
503
|
+
getCoreRowModel: getCoreRowModel(),
|
|
504
|
+
getSortedRowModel: enableSorting ? getSortedRowModel() : undefined,
|
|
505
|
+
getFilteredRowModel: enableFiltering ? getFilteredRowModel() : undefined,
|
|
506
|
+
getPaginationRowModel: enablePagination && !enableGrouping ? getPaginationRowModel() : undefined,
|
|
507
|
+
getExpandedRowModel: enableExpanding || enableGrouping ? getExpandedRowModel() : undefined,
|
|
508
|
+
getGroupedRowModel: enableGrouping ? getGroupedRowModel() : undefined,
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
// =============================================================================
|
|
512
|
+
// Exports
|
|
513
|
+
// =============================================================================
|
|
514
|
+
export { DataTable as BhDataTable };
|