@actabldesign/bellhop-react 0.0.7 → 0.0.9
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/dist/index.js +5884 -24
- package/package.json +6 -3
- package/dist/components/stencil-generated/components.js +0 -7
- package/dist/components/stencil-generated/components.server.js +0 -1677
- package/dist/components/stencil-generated/index.js +0 -6
- package/dist/components/table/DataTable.js +0 -514
- package/dist/components/table/index.js +0 -95
|
@@ -1,514 +0,0 @@
|
|
|
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 Bellhop 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 };
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
/**
|
|
3
|
-
* Bellhop Table Primitives
|
|
4
|
-
*
|
|
5
|
-
* Composable table components for building static and dynamic tables.
|
|
6
|
-
* These primitives can be used standalone or combined with TanStack Table
|
|
7
|
-
* for advanced features like sorting, filtering, and pagination.
|
|
8
|
-
* Uses existing Bellhop components for consistency.
|
|
9
|
-
*/
|
|
10
|
-
import { forwardRef, createContext, useContext } from 'react';
|
|
11
|
-
import { BhButtonIcon } from '../stencil-generated';
|
|
12
|
-
// =============================================================================
|
|
13
|
-
// Context
|
|
14
|
-
// =============================================================================
|
|
15
|
-
const TableContext = createContext({
|
|
16
|
-
size: 'default',
|
|
17
|
-
variant: 'default',
|
|
18
|
-
hoverable: true,
|
|
19
|
-
});
|
|
20
|
-
const useTableContext = () => useContext(TableContext);
|
|
21
|
-
// =============================================================================
|
|
22
|
-
// Utility Functions
|
|
23
|
-
// =============================================================================
|
|
24
|
-
function clsx(...classes) {
|
|
25
|
-
return classes.filter(Boolean).join(' ');
|
|
26
|
-
}
|
|
27
|
-
export const TableWrapper = forwardRef(({ className, children, maxHeight, maxWidth, scrollX, scrollY, style, ...props }, ref) => {
|
|
28
|
-
return (_jsx("div", { ref: ref, className: clsx('bh-table-wrapper', scrollX && 'bh-table-wrapper--scroll-x', scrollY && 'bh-table-wrapper--scroll-y', className), style: {
|
|
29
|
-
maxHeight: maxHeight,
|
|
30
|
-
maxWidth: maxWidth,
|
|
31
|
-
...style,
|
|
32
|
-
}, tabIndex: 0, ...props, children: children }));
|
|
33
|
-
});
|
|
34
|
-
TableWrapper.displayName = 'TableWrapper';
|
|
35
|
-
export const Table = forwardRef(({ className, children, size = 'default', variant = 'default', rounded = false, hoverable = true, responsive = false, loading = false, ...props }, ref) => {
|
|
36
|
-
return (_jsx(TableContext.Provider, { value: { size, variant, hoverable }, children: _jsx("table", { ref: ref, className: clsx('bh-table', `bh-table--${size}`, variant === 'bordered' && 'bh-table--bordered', variant === 'striped' && 'bh-table--striped', rounded && 'bh-table--rounded', responsive && 'bh-table--responsive', loading && 'bh-table--loading', className), ...props, children: children }) }));
|
|
37
|
-
});
|
|
38
|
-
Table.displayName = 'Table';
|
|
39
|
-
export const TableCaption = forwardRef(({ className, children, position = 'top', srOnly = false, ...props }, ref) => {
|
|
40
|
-
return (_jsx("caption", { ref: ref, className: clsx('bh-caption', position === 'bottom' && 'bh-caption--bottom', srOnly && 'bh-caption--sr-only', className), ...props, children: children }));
|
|
41
|
-
});
|
|
42
|
-
TableCaption.displayName = 'TableCaption';
|
|
43
|
-
export const TableHead = forwardRef(({ className, children, sticky = false, subtle = false, ...props }, ref) => {
|
|
44
|
-
return (_jsx("thead", { ref: ref, className: clsx('bh-thead', sticky && 'bh-thead--sticky', subtle && 'bh-thead--subtle', className), ...props, children: children }));
|
|
45
|
-
});
|
|
46
|
-
TableHead.displayName = 'TableHead';
|
|
47
|
-
export const TableBody = forwardRef(({ className, children, ...props }, ref) => {
|
|
48
|
-
return (_jsx("tbody", { ref: ref, className: clsx('bh-tbody', className), ...props, children: children }));
|
|
49
|
-
});
|
|
50
|
-
TableBody.displayName = 'TableBody';
|
|
51
|
-
export const TableFooter = forwardRef(({ className, children, ...props }, ref) => {
|
|
52
|
-
return (_jsx("tfoot", { ref: ref, className: clsx('bh-tfoot', className), ...props, children: children }));
|
|
53
|
-
});
|
|
54
|
-
TableFooter.displayName = 'TableFooter';
|
|
55
|
-
export const TableRow = forwardRef(({ className, children, selected = false, clickable = false, expandable = false, expanded = false, filter = false, expansion = false, ...props }, ref) => {
|
|
56
|
-
const { hoverable } = useTableContext();
|
|
57
|
-
return (_jsx("tr", { ref: ref, className: clsx('bh-tr', hoverable && 'bh-tr--hoverable', selected && 'bh-tr--selected', clickable && 'bh-tr--clickable', expandable && 'bh-tr--expandable', expanded && 'bh-tr--expanded', filter && 'bh-tr--filter', expansion && 'bh-tr--expansion', className), ...props, children: children }));
|
|
58
|
-
});
|
|
59
|
-
TableRow.displayName = 'TableRow';
|
|
60
|
-
export const TableHeaderCell = forwardRef(({ className, children, align = 'left', sortable = false, sortDirection = null, shrink = false, expand = false, checkbox = false, actions = false, pinnedLeft = false, pinnedRight = false, onClick, ...props }, ref) => {
|
|
61
|
-
const isSorted = sortDirection !== null;
|
|
62
|
-
return (_jsx("th", { ref: ref, className: clsx('bh-th', `bh-th--${align}`, sortable && 'bh-th--sortable', isSorted && 'bh-th--sorted', shrink && 'bh-th--shrink', expand && 'bh-th--expand', checkbox && 'bh-th--checkbox', actions && 'bh-th--actions', pinnedLeft && 'bh-th--pinned-left', pinnedRight && 'bh-th--pinned-right', className), onClick: sortable ? onClick : undefined, "aria-sort": sortDirection === 'asc'
|
|
63
|
-
? 'ascending'
|
|
64
|
-
: sortDirection === 'desc'
|
|
65
|
-
? 'descending'
|
|
66
|
-
: undefined, ...props, children: _jsxs("span", { className: "bh-th__content", children: [children, sortable && isSorted && (_jsx("span", { className: "bh-th__sort-icon material-symbols-outlined", children: sortDirection === 'asc' ? 'arrow_upward' : 'arrow_downward' }))] }) }));
|
|
67
|
-
});
|
|
68
|
-
TableHeaderCell.displayName = 'TableHeaderCell';
|
|
69
|
-
export const TableCell = forwardRef(({ className, children, align = 'left', numeric = false, truncate = false, wrap = false, shrink = false, expand = false, checkbox = false, actions = false, expandTrigger = false, filter = false, expansion = false, pinnedLeft = false, pinnedRight = false, dataLabel, ...props }, ref) => {
|
|
70
|
-
return (_jsx("td", { ref: ref, className: clsx('bh-td', `bh-td--${align}`, numeric && 'bh-td--numeric', truncate && 'bh-td--truncate', wrap && 'bh-td--wrap', shrink && 'bh-td--shrink', expand && 'bh-td--expand', checkbox && 'bh-td--checkbox', actions && 'bh-td--actions', expandTrigger && 'bh-td--expand-trigger', filter && 'bh-td--filter', expansion && 'bh-td--expansion', pinnedLeft && 'bh-td--pinned-left', pinnedRight && 'bh-td--pinned-right', className), "data-label": dataLabel, ...props, children: children }));
|
|
71
|
-
});
|
|
72
|
-
TableCell.displayName = 'TableCell';
|
|
73
|
-
export const TableEmpty = forwardRef(({ className, children, icon, title, description, colSpan = 1, ...props }, ref) => {
|
|
74
|
-
return (_jsx("tr", { ref: ref, children: _jsx("td", { colSpan: colSpan, children: _jsxs("div", { className: clsx('bh-table-empty', className), ...props, children: [icon && _jsx("div", { className: "bh-table-empty__icon", children: icon }), title && _jsx("div", { className: "bh-table-empty__title", children: title }), description && (_jsx("div", { className: "bh-table-empty__description", children: description })), children] }) }) }));
|
|
75
|
-
});
|
|
76
|
-
TableEmpty.displayName = 'TableEmpty';
|
|
77
|
-
export const TableActionBar = forwardRef(({ className, children, selectionCount, left, right, ...props }, ref) => {
|
|
78
|
-
const hasSelection = selectionCount !== undefined && selectionCount > 0;
|
|
79
|
-
return (_jsxs("div", { ref: ref, className: clsx('bh-table-action-bar', hasSelection && 'bh-table-action-bar--selected', className), ...props, children: [_jsxs("div", { className: "bh-table-action-bar__left", children: [hasSelection && (_jsxs("span", { className: "bh-table-action-bar__selection-count", children: [selectionCount, " selected"] })), left] }), _jsx("div", { className: "bh-table-action-bar__right", children: right }), children] }));
|
|
80
|
-
});
|
|
81
|
-
TableActionBar.displayName = 'TableActionBar';
|
|
82
|
-
export const TablePagination = forwardRef(({ className, page, totalPages, totalItems, pageSize, pageSizeOptions = [10, 25, 50, 100], onPageChange, onPageSizeChange, ...props }, ref) => {
|
|
83
|
-
const startItem = (page - 1) * pageSize + 1;
|
|
84
|
-
const endItem = Math.min(page * pageSize, totalItems || page * pageSize);
|
|
85
|
-
return (_jsxs("div", { ref: ref, className: clsx('bh-table-pagination', className), ...props, children: [_jsx("div", { className: "bh-table-pagination__info", children: totalItems !== undefined ? (_jsxs(_Fragment, { children: ["Showing ", startItem, " to ", endItem, " of ", totalItems, " results"] })) : (_jsxs(_Fragment, { children: ["Page ", page, " of ", totalPages] })) }), _jsxs("div", { className: "bh-table-pagination__controls", children: [onPageSizeChange && (_jsxs("div", { className: "bh-table-pagination__page-size", children: [_jsx("span", { children: "Rows per page:" }), _jsx("select", { value: pageSize, onChange: (e) => onPageSizeChange(Number(e.target.value)), className: "bh-table-pagination__select", children: pageSizeOptions.map((size) => (_jsx("option", { value: size, children: size }, size))) })] })), _jsx(BhButtonIcon, { iconName: "first_page", size: "sm", hierarchy: "quaternary", disabled: page <= 1, onBhClick: () => onPageChange(1), "aria-label": "First page" }), _jsx(BhButtonIcon, { iconName: "chevron_left", size: "sm", hierarchy: "quaternary", disabled: page <= 1, onBhClick: () => onPageChange(page - 1), "aria-label": "Previous page" }), _jsx(BhButtonIcon, { iconName: "chevron_right", size: "sm", hierarchy: "quaternary", disabled: page >= totalPages, onBhClick: () => onPageChange(page + 1), "aria-label": "Next page" }), _jsx(BhButtonIcon, { iconName: "last_page", size: "sm", hierarchy: "quaternary", disabled: page >= totalPages, onBhClick: () => onPageChange(totalPages), "aria-label": "Last page" })] })] }));
|
|
86
|
-
});
|
|
87
|
-
TablePagination.displayName = 'TablePagination';
|
|
88
|
-
export const ExpandIcon = forwardRef(({ className, expanded = false, ...props }, ref) => {
|
|
89
|
-
return (_jsx("span", { ref: ref, className: clsx('bh-expand-icon', className), ...props, children: _jsx("span", { className: "material-symbols-outlined", children: "chevron_right" }) }));
|
|
90
|
-
});
|
|
91
|
-
ExpandIcon.displayName = 'ExpandIcon';
|
|
92
|
-
// =============================================================================
|
|
93
|
-
// Exports
|
|
94
|
-
// =============================================================================
|
|
95
|
-
export { Table as BhTable, TableWrapper as BhTableWrapper, TableCaption as BhTableCaption, TableHead as BhThead, TableBody as BhTbody, TableFooter as BhTfoot, TableRow as BhTr, TableHeaderCell as BhTh, TableCell as BhTd, TableEmpty as BhTableEmpty, TableActionBar as BhTableActionBar, TablePagination as BhTablePagination, ExpandIcon as BhExpandIcon, };
|