@alaarab/ogrid 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -0
- package/dist/esm/ColumnChooser/ColumnChooser.js +22 -0
- package/dist/esm/ColumnChooser/ColumnChooser.module.css +139 -0
- package/dist/esm/ColumnHeaderFilter/ColumnHeaderFilter.js +50 -0
- package/dist/esm/ColumnHeaderFilter/ColumnHeaderFilter.module.css +332 -0
- package/dist/esm/ColumnHeaderFilter/MultiSelectFilterPopover.js +5 -0
- package/dist/esm/ColumnHeaderFilter/PeopleFilterPopover.js +19 -0
- package/dist/esm/ColumnHeaderFilter/TextFilterPopover.js +4 -0
- package/dist/esm/ColumnHeaderFilter/index.js +1 -0
- package/dist/esm/DataGridTable/DataGridTable.js +123 -0
- package/dist/esm/DataGridTable/DataGridTable.module.css +421 -0
- package/dist/esm/DataGridTable/GridContextMenu.js +26 -0
- package/dist/esm/DataGridTable/InlineCellEditor.js +22 -0
- package/dist/esm/DataGridTable/StatusBar.js +7 -0
- package/dist/esm/OGrid/OGrid.js +16 -0
- package/dist/esm/PaginationControls/PaginationControls.js +31 -0
- package/dist/esm/PaginationControls/PaginationControls.module.css +112 -0
- package/dist/esm/index.js +8 -0
- package/dist/types/ColumnChooser/ColumnChooser.d.ts +10 -0
- package/dist/types/ColumnHeaderFilter/ColumnHeaderFilter.d.ts +20 -0
- package/dist/types/ColumnHeaderFilter/MultiSelectFilterPopover.d.ts +14 -0
- package/dist/types/ColumnHeaderFilter/PeopleFilterPopover.d.ts +13 -0
- package/dist/types/ColumnHeaderFilter/TextFilterPopover.d.ts +8 -0
- package/dist/types/ColumnHeaderFilter/index.d.ts +1 -0
- package/dist/types/DataGridTable/DataGridTable.d.ts +7 -0
- package/dist/types/DataGridTable/GridContextMenu.d.ts +8 -0
- package/dist/types/DataGridTable/InlineCellEditor.d.ts +12 -0
- package/dist/types/DataGridTable/StatusBar.d.ts +8 -0
- package/dist/types/OGrid/OGrid.d.ts +5 -0
- package/dist/types/PaginationControls/PaginationControls.d.ts +11 -0
- package/dist/types/index.d.ts +6 -0
- package/package.json +56 -0
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { useCallback, useRef } from 'react';
|
|
4
|
+
import { createPortal } from 'react-dom';
|
|
5
|
+
import * as Popover from '@radix-ui/react-popover';
|
|
6
|
+
import * as Checkbox from '@radix-ui/react-checkbox';
|
|
7
|
+
import { ColumnHeaderFilter } from '../ColumnHeaderFilter';
|
|
8
|
+
import { InlineCellEditor } from './InlineCellEditor';
|
|
9
|
+
import { StatusBar } from './StatusBar';
|
|
10
|
+
import { GridContextMenu } from './GridContextMenu';
|
|
11
|
+
import { useDataGridState, useColumnResize, getHeaderFilterConfig, getCellRenderDescriptor, } from '@alaarab/ogrid-core';
|
|
12
|
+
import styles from './DataGridTable.module.css';
|
|
13
|
+
function DataGridTableInner(props) {
|
|
14
|
+
const wrapperRef = useRef(null);
|
|
15
|
+
const state = useDataGridState({ props, wrapperRef });
|
|
16
|
+
const lastMouseShiftRef = useRef(false);
|
|
17
|
+
const { visibleCols, totalColCount, hasCheckboxCol, selectedRowIds, updateSelection, handleRowCheckboxChange, handleSelectAll, allSelected, someSelected, setEditingCell, pendingEditorValue, setPendingEditorValue, setActiveCell, handleCellMouseDown, handleSelectAllCells, contextMenu, setContextMenu, handleCellContextMenu, closeContextMenu, handleCopy, handleCut, handlePaste, handleGridKeyDown, handleFillHandleMouseDown, containerWidth, minTableWidth, columnSizingOverrides, setColumnSizingOverrides, statusBarConfig, showEmptyInGrid, hasCellSelection, selectionRange, headerFilterInput, cellDescriptorInput, commitCellEdit, cancelPopoverEdit, popoverAnchorEl, setPopoverAnchorEl, } = state;
|
|
18
|
+
const { items, getRowId, emptyState, layoutMode = 'fill', rowSelection = 'none', freezeRows, freezeCols, isLoading = false, loadingMessage = 'Loading\u2026', 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, } = props;
|
|
19
|
+
const allowOverflowX = containerWidth > 0 && minTableWidth > containerWidth;
|
|
20
|
+
const fitToContent = layoutMode === 'content';
|
|
21
|
+
const { handleResizeStart, getColumnWidth } = useColumnResize({
|
|
22
|
+
columnSizingOverrides,
|
|
23
|
+
setColumnSizingOverrides,
|
|
24
|
+
});
|
|
25
|
+
const renderCellContent = useCallback((item, col, rowIndex, colIdx) => {
|
|
26
|
+
const descriptor = getCellRenderDescriptor(item, col, rowIndex, colIdx, cellDescriptorInput);
|
|
27
|
+
if (descriptor.mode === 'editing-inline') {
|
|
28
|
+
return (_jsx(InlineCellEditor, { value: descriptor.value, item: item, column: col, rowIndex: descriptor.rowIndex, editorType: descriptor.editorType ?? 'text', onCommit: (newValue) => commitCellEdit(item, col.columnId, descriptor.value, newValue, descriptor.rowIndex, descriptor.globalColIndex), onCancel: () => setEditingCell(null) }));
|
|
29
|
+
}
|
|
30
|
+
if (descriptor.mode === 'editing-popover' && typeof col.cellEditor === 'function') {
|
|
31
|
+
const oldValue = descriptor.value;
|
|
32
|
+
const displayValue = pendingEditorValue !== undefined ? pendingEditorValue : oldValue;
|
|
33
|
+
const CustomEditor = col.cellEditor;
|
|
34
|
+
const editorProps = {
|
|
35
|
+
value: displayValue,
|
|
36
|
+
onValueChange: setPendingEditorValue,
|
|
37
|
+
onCommit: () => {
|
|
38
|
+
const newValue = pendingEditorValue !== undefined ? pendingEditorValue : oldValue;
|
|
39
|
+
commitCellEdit(item, col.columnId, oldValue, newValue, descriptor.rowIndex, descriptor.globalColIndex);
|
|
40
|
+
},
|
|
41
|
+
onCancel: cancelPopoverEdit,
|
|
42
|
+
item,
|
|
43
|
+
column: col,
|
|
44
|
+
cellEditorParams: col.cellEditorParams,
|
|
45
|
+
};
|
|
46
|
+
return (_jsxs(Popover.Root, { open: !!popoverAnchorEl, onOpenChange: (open) => { if (!open)
|
|
47
|
+
cancelPopoverEdit(); }, children: [_jsx(Popover.Anchor, { asChild: true, children: _jsx("div", { ref: (el) => el && setPopoverAnchorEl(el), style: { minHeight: '100%', minWidth: 40 }, "aria-hidden": true }) }), _jsx(Popover.Portal, { children: _jsx(Popover.Content, { sideOffset: 4, onOpenAutoFocus: (e) => e.preventDefault(), children: _jsx(CustomEditor, { ...editorProps }) }) })] }));
|
|
48
|
+
}
|
|
49
|
+
let content;
|
|
50
|
+
if (col.renderCell)
|
|
51
|
+
content = col.renderCell(item);
|
|
52
|
+
else {
|
|
53
|
+
const value = descriptor.displayValue;
|
|
54
|
+
if (col.valueFormatter)
|
|
55
|
+
content = col.valueFormatter(value, item);
|
|
56
|
+
else if (value !== null && value !== undefined)
|
|
57
|
+
content = String(value);
|
|
58
|
+
else
|
|
59
|
+
content = null;
|
|
60
|
+
}
|
|
61
|
+
const cellStyle = col.cellStyle ? (typeof col.cellStyle === 'function' ? col.cellStyle(item) : col.cellStyle) : undefined;
|
|
62
|
+
if (cellStyle)
|
|
63
|
+
content = _jsx("span", { style: cellStyle, children: content });
|
|
64
|
+
const cellClassNames = [
|
|
65
|
+
styles.cellContent,
|
|
66
|
+
descriptor.isActive ? styles.activeCellContent : '',
|
|
67
|
+
descriptor.isInRange ? styles.cellInRange : '',
|
|
68
|
+
descriptor.isInCutRange ? styles.cellCut : '',
|
|
69
|
+
].filter(Boolean).join(' ');
|
|
70
|
+
if (descriptor.canEditAny) {
|
|
71
|
+
return (_jsxs("div", { className: cellClassNames, "data-row-index": descriptor.rowIndex, "data-col-index": descriptor.globalColIndex, "data-in-range": descriptor.isInRange ? 'true' : undefined, role: "button", tabIndex: descriptor.isActive ? 0 : -1, onMouseDown: (e) => handleCellMouseDown(e, descriptor.rowIndex, descriptor.globalColIndex), onClick: () => setActiveCell({ rowIndex: descriptor.rowIndex, columnIndex: descriptor.globalColIndex }), onDoubleClick: () => setEditingCell({ rowId: descriptor.rowId, columnId: col.columnId }), onContextMenu: handleCellContextMenu, style: { minHeight: '100%', cursor: 'cell', outline: 'none', position: 'relative', userSelect: 'none' }, children: [content, descriptor.isSelectionEndCell && (_jsx("div", { className: styles.fillHandle, onMouseDown: handleFillHandleMouseDown, "aria-label": "Fill handle" }))] }));
|
|
72
|
+
}
|
|
73
|
+
return (_jsx("div", { className: cellClassNames, "data-row-index": descriptor.rowIndex, "data-col-index": descriptor.globalColIndex, "data-in-range": descriptor.isInRange ? 'true' : undefined, tabIndex: descriptor.isActive ? 0 : -1, onMouseDown: (e) => handleCellMouseDown(e, descriptor.rowIndex, descriptor.globalColIndex), onClick: () => setActiveCell({ rowIndex: descriptor.rowIndex, columnIndex: descriptor.globalColIndex }), onContextMenu: handleCellContextMenu, style: { outline: 'none', userSelect: 'none' }, children: content }));
|
|
74
|
+
}, [cellDescriptorInput, pendingEditorValue, popoverAnchorEl, handleCellMouseDown, handleCellContextMenu, handleFillHandleMouseDown, setActiveCell, setEditingCell, setPendingEditorValue, setPopoverAnchorEl, commitCellEdit, cancelPopoverEdit]);
|
|
75
|
+
return (_jsxs("div", { ref: wrapperRef, tabIndex: 0, onMouseDown: (e) => { lastMouseShiftRef.current = e.shiftKey; }, className: `${styles.tableWrapper} ${rowSelection !== 'none' ? styles.selectableGrid : ''}`, role: "region", "aria-label": ariaLabel ?? (ariaLabelledBy ? undefined : 'Data grid'), "aria-labelledby": ariaLabelledBy, "data-empty": showEmptyInGrid ? 'true' : undefined, "data-column-count": totalColCount, "data-freeze-rows": freezeRows != null && freezeRows >= 1 ? freezeRows : undefined, "data-freeze-cols": freezeCols != null && freezeCols >= 1 ? freezeCols : undefined, "data-overflow-x": allowOverflowX ? 'true' : 'false', "data-container-width": containerWidth, "data-min-table-width": Math.round(minTableWidth), "data-has-selection": rowSelection !== 'none' ? 'true' : undefined, onContextMenu: (e) => { e.preventDefault(); setContextMenu({ x: e.clientX, y: e.clientY }); }, onKeyDown: handleGridKeyDown, style: {
|
|
76
|
+
['--data-table-column-count']: totalColCount,
|
|
77
|
+
['--data-table-width']: showEmptyInGrid ? '100%' : allowOverflowX ? 'fit-content' : fitToContent ? 'fit-content' : '100%',
|
|
78
|
+
['--data-table-min-width']: showEmptyInGrid ? '100%' : allowOverflowX ? 'max-content' : fitToContent ? 'max-content' : '100%',
|
|
79
|
+
['--data-table-total-min-width']: `${minTableWidth}px`,
|
|
80
|
+
}, children: [_jsxs("div", { className: styles.tableScrollContent, children: [_jsxs("div", { className: isLoading && items.length > 0 ? styles.loadingOverlayContainer : undefined, children: [isLoading && items.length > 0 && (_jsx("div", { className: styles.loadingOverlay, "aria-live": "polite", children: _jsxs("div", { className: styles.loadingOverlayContent, children: [_jsx("div", { className: styles.spinner }), _jsx("span", { className: styles.loadingOverlayText, children: loadingMessage })] }) })), _jsx("div", { className: isLoading && items.length > 0 ? styles.loadingDimmed : undefined, children: _jsx("div", { className: styles.tableWidthAnchor, children: _jsxs("table", { className: styles.dataTable, children: [_jsx("thead", { className: freezeRows != null && freezeRows >= 1 ? styles.stickyHeader : undefined, children: _jsxs("tr", { children: [hasCheckboxCol && (_jsx("th", { className: styles.selectionHeaderCell, scope: "col", children: _jsx("div", { className: styles.selectionHeaderCellInner, children: _jsx(Checkbox.Root, { checked: allSelected ? true : someSelected ? 'indeterminate' : false, onCheckedChange: (c) => handleSelectAll(!!c), "aria-label": "Select all rows", children: _jsx(Checkbox.Indicator, { children: "\u2713" }) }) }) })), visibleCols.map((col, colIdx) => {
|
|
81
|
+
const isFreezeCol = freezeCols != null && freezeCols >= 1 && colIdx < freezeCols;
|
|
82
|
+
const isPinnedLeft = col.pinned === 'left';
|
|
83
|
+
const isPinnedRight = col.pinned === 'right';
|
|
84
|
+
const columnWidth = getColumnWidth(col);
|
|
85
|
+
return (_jsxs("th", { scope: "col", "data-column-id": col.columnId, className: [
|
|
86
|
+
isFreezeCol ? styles.freezeCol : '',
|
|
87
|
+
isFreezeCol && colIdx === 0 ? styles.freezeColFirst : '',
|
|
88
|
+
isPinnedLeft ? styles.pinnedColLeft : '',
|
|
89
|
+
isPinnedRight ? styles.pinnedColRight : '',
|
|
90
|
+
].filter(Boolean).join(' '), style: {
|
|
91
|
+
minWidth: col.minWidth ?? 80,
|
|
92
|
+
width: columnWidth,
|
|
93
|
+
maxWidth: columnWidth,
|
|
94
|
+
position: 'relative',
|
|
95
|
+
}, children: [_jsx(ColumnHeaderFilter, { ...getHeaderFilterConfig(col, headerFilterInput) }), _jsx("div", { className: styles.resizeHandle, onMouseDown: (e) => handleResizeStart(e, col), "aria-label": `Resize ${col.name}` })] }, col.columnId));
|
|
96
|
+
})] }) }), !showEmptyInGrid && (_jsx("tbody", { children: items.map((item, rowIndex) => {
|
|
97
|
+
const rowIdStr = getRowId(item);
|
|
98
|
+
const isSelected = selectedRowIds.has(rowIdStr);
|
|
99
|
+
return (_jsxs("tr", { className: isSelected ? styles.selectedRow : '', onClick: () => {
|
|
100
|
+
if (rowSelection === 'single') {
|
|
101
|
+
const id = getRowId(item);
|
|
102
|
+
updateSelection(selectedRowIds.has(id) ? new Set() : new Set([id]));
|
|
103
|
+
}
|
|
104
|
+
}, children: [hasCheckboxCol && (_jsx("td", { className: styles.selectionCell, children: _jsx("div", { className: styles.selectionCellInner, "data-row-index": rowIndex, "data-col-index": 0, onClick: (e) => e.stopPropagation(), children: _jsx("input", { type: "checkbox", checked: selectedRowIds.has(rowIdStr), onChange: (e) => handleRowCheckboxChange(rowIdStr, e.target.checked, rowIndex, lastMouseShiftRef.current), "aria-label": `Select row ${rowIndex + 1}` }) }) })), visibleCols.map((col, colIdx) => {
|
|
105
|
+
const isFreezeCol = freezeCols != null && freezeCols >= 1 && colIdx < freezeCols;
|
|
106
|
+
const isPinnedLeft = col.pinned === 'left';
|
|
107
|
+
const isPinnedRight = col.pinned === 'right';
|
|
108
|
+
const columnWidth = getColumnWidth(col);
|
|
109
|
+
return (_jsx("td", { className: [
|
|
110
|
+
isFreezeCol ? styles.freezeCol : '',
|
|
111
|
+
isFreezeCol && colIdx === 0 ? styles.freezeColFirst : '',
|
|
112
|
+
isPinnedLeft ? styles.pinnedColLeft : '',
|
|
113
|
+
isPinnedRight ? styles.pinnedColRight : '',
|
|
114
|
+
].filter(Boolean).join(' '), style: {
|
|
115
|
+
minWidth: col.minWidth ?? 80,
|
|
116
|
+
width: columnWidth,
|
|
117
|
+
maxWidth: columnWidth,
|
|
118
|
+
}, children: renderCellContent(item, col, rowIndex, colIdx) }, col.columnId));
|
|
119
|
+
})] }, rowIdStr));
|
|
120
|
+
}) }))] }) }) })] }), showEmptyInGrid && emptyState && (_jsx("div", { className: styles.emptyStateInGrid, children: _jsx("div", { children: emptyState.render ? (emptyState.render()) : (_jsxs(_Fragment, { children: [_jsx("div", { className: styles.emptyStateInGridTitle, children: "No results found" }), _jsx("div", { className: styles.emptyStateInGridMessage, children: emptyState.message != null ? (emptyState.message) : emptyState.hasActiveFilters ? (_jsxs(_Fragment, { children: ["No items match your current filters. Try adjusting your search or", ' ', _jsx("button", { type: "button", className: styles.emptyStateInGridLink, onClick: emptyState.onClearAll, children: "clear all filters" }), ' ', "to see all items."] })) : ('There are no items available at this time.') })] })) }) }))] }), statusBarConfig && (_jsx(StatusBar, { totalCount: statusBarConfig.totalCount, filteredCount: statusBarConfig.filteredCount, selectedCount: statusBarConfig.selectedCount ?? selectedRowIds.size, selectedCellCount: selectionRange ? (Math.abs(selectionRange.endRow - selectionRange.startRow) + 1) * (Math.abs(selectionRange.endCol - selectionRange.startCol) + 1) : undefined })), contextMenu &&
|
|
121
|
+
createPortal(_jsx(GridContextMenu, { x: contextMenu.x, y: contextMenu.y, hasSelection: hasCellSelection, onCopy: handleCopy, onCut: handleCut, onPaste: () => void handlePaste(), onSelectAll: handleSelectAllCells, onClose: closeContextMenu }), document.body)] }));
|
|
122
|
+
}
|
|
123
|
+
export const DataGridTable = React.memo(DataGridTableInner);
|
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
.tableScrollContent {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
width: 100%;
|
|
5
|
+
min-width: 0;
|
|
6
|
+
background: var(--ogrid-bg, #fff);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.tableWrapper {
|
|
10
|
+
overflow-x: hidden;
|
|
11
|
+
overflow-y: visible;
|
|
12
|
+
width: 100%;
|
|
13
|
+
min-width: 0;
|
|
14
|
+
max-width: 100%;
|
|
15
|
+
margin-bottom: 15px;
|
|
16
|
+
border-radius: 6px;
|
|
17
|
+
box-sizing: border-box;
|
|
18
|
+
}
|
|
19
|
+
.tableWrapper[data-overflow-x=true] {
|
|
20
|
+
overflow-x: auto;
|
|
21
|
+
}
|
|
22
|
+
.tableWrapper[data-empty=true] {
|
|
23
|
+
overflow-x: hidden;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.tableWidthAnchor {
|
|
27
|
+
width: max-content;
|
|
28
|
+
min-width: max(100%, var(--data-table-total-min-width, 0px));
|
|
29
|
+
background: var(--ogrid-bg, #fff);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.dataTable {
|
|
33
|
+
width: var(--data-table-width, fit-content);
|
|
34
|
+
max-width: 100%;
|
|
35
|
+
min-width: var(--data-table-min-width, max-content);
|
|
36
|
+
border-collapse: separate;
|
|
37
|
+
border-spacing: 0;
|
|
38
|
+
border: 1px solid var(--ogrid-border, #e0e0e0);
|
|
39
|
+
border-radius: 6px;
|
|
40
|
+
overflow: hidden;
|
|
41
|
+
box-sizing: border-box;
|
|
42
|
+
table-layout: auto;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.dataTable th,
|
|
46
|
+
.dataTable td {
|
|
47
|
+
min-width: 80px;
|
|
48
|
+
box-sizing: border-box;
|
|
49
|
+
padding: 6px 10px;
|
|
50
|
+
border-right: 1px solid var(--ogrid-border, #e0e0e0);
|
|
51
|
+
font-size: 13px;
|
|
52
|
+
vertical-align: middle;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.dataTable th:last-of-type,
|
|
56
|
+
.dataTable td:last-of-type {
|
|
57
|
+
border-right: none;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.dataTable thead {
|
|
61
|
+
background: var(--ogrid-bg-subtle, #f3f2f1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* Freeze panes: sticky header when freezeRows >= 1 */
|
|
65
|
+
.dataTable thead.stickyHeader {
|
|
66
|
+
position: sticky;
|
|
67
|
+
top: 0;
|
|
68
|
+
z-index: 2;
|
|
69
|
+
background: var(--ogrid-bg-subtle, #f3f2f1);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.dataTable thead.stickyHeader th {
|
|
73
|
+
background: var(--ogrid-bg-subtle, #f3f2f1);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/* Freeze panes: sticky first column when freezeCols >= 1 */
|
|
77
|
+
.dataTable .freezeColFirst {
|
|
78
|
+
position: sticky;
|
|
79
|
+
left: 0;
|
|
80
|
+
z-index: 2;
|
|
81
|
+
background: var(--ogrid-bg, #ffffff);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.dataTable thead .freezeColFirst {
|
|
85
|
+
background: var(--ogrid-bg-subtle, #f3f2f1);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* Pinned columns: sticky positioning based on column pinned property */
|
|
89
|
+
.pinnedColLeft {
|
|
90
|
+
position: sticky;
|
|
91
|
+
left: 0;
|
|
92
|
+
z-index: 2;
|
|
93
|
+
background: var(--ogrid-bg, #ffffff);
|
|
94
|
+
}
|
|
95
|
+
.pinnedColLeft::after {
|
|
96
|
+
content: "";
|
|
97
|
+
position: absolute;
|
|
98
|
+
top: 0;
|
|
99
|
+
right: -4px;
|
|
100
|
+
bottom: 0;
|
|
101
|
+
width: 4px;
|
|
102
|
+
background: linear-gradient(to right, rgba(0, 0, 0, 0.08), transparent);
|
|
103
|
+
pointer-events: none;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.dataTable thead .pinnedColLeft {
|
|
107
|
+
background: var(--ogrid-bg-subtle, #f3f2f1);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.pinnedColRight {
|
|
111
|
+
position: sticky;
|
|
112
|
+
right: 0;
|
|
113
|
+
z-index: 2;
|
|
114
|
+
background: var(--ogrid-bg, #ffffff);
|
|
115
|
+
}
|
|
116
|
+
.pinnedColRight::before {
|
|
117
|
+
content: "";
|
|
118
|
+
position: absolute;
|
|
119
|
+
top: 0;
|
|
120
|
+
left: -4px;
|
|
121
|
+
bottom: 0;
|
|
122
|
+
width: 4px;
|
|
123
|
+
background: linear-gradient(to left, rgba(0, 0, 0, 0.08), transparent);
|
|
124
|
+
pointer-events: none;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.dataTable thead .pinnedColRight {
|
|
128
|
+
background: var(--ogrid-bg-subtle, #f3f2f1);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
.dataTable thead th {
|
|
132
|
+
font-weight: 600;
|
|
133
|
+
font-size: 14px;
|
|
134
|
+
color: var(--ogrid-fg, #242424);
|
|
135
|
+
white-space: nowrap;
|
|
136
|
+
position: relative;
|
|
137
|
+
border-bottom: 1px solid var(--ogrid-border, #e0e0e0);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/* Column resize handle */
|
|
141
|
+
.resizeHandle {
|
|
142
|
+
position: absolute;
|
|
143
|
+
top: 0;
|
|
144
|
+
right: 0;
|
|
145
|
+
bottom: 0;
|
|
146
|
+
width: 4px;
|
|
147
|
+
cursor: col-resize;
|
|
148
|
+
user-select: none;
|
|
149
|
+
z-index: 1;
|
|
150
|
+
}
|
|
151
|
+
.resizeHandle:hover {
|
|
152
|
+
background-color: var(--ogrid-accent, #0078d4);
|
|
153
|
+
}
|
|
154
|
+
.resizeHandle:active {
|
|
155
|
+
background-color: var(--ogrid-accent-dark, #005a9e);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.dataTable tbody td {
|
|
159
|
+
border-bottom: 1px solid var(--ogrid-border, #e8e8e8);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.dataTable tbody tr:last-child td {
|
|
163
|
+
border-bottom: none;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
.dataTable tbody tr:hover td {
|
|
167
|
+
background: var(--ogrid-bg-hover, #f5f5f5);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.dataTable tbody tr.selectedRow td {
|
|
171
|
+
background: var(--ogrid-bg-selected, #e6f0fb);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.dataTable tbody tr.selectedRow:hover td {
|
|
175
|
+
background: var(--ogrid-bg-selected-hover, #dae8f8);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
.dataTable tbody td {
|
|
179
|
+
overflow: hidden;
|
|
180
|
+
text-overflow: ellipsis;
|
|
181
|
+
white-space: nowrap;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.cellContent {
|
|
185
|
+
width: 100%;
|
|
186
|
+
min-height: 100%;
|
|
187
|
+
display: flex;
|
|
188
|
+
align-items: center;
|
|
189
|
+
min-width: 0;
|
|
190
|
+
user-select: none;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.activeCellContent {
|
|
194
|
+
outline: 2px solid var(--ogrid-primary, #0066cc);
|
|
195
|
+
outline-offset: -2px;
|
|
196
|
+
border-radius: 2px;
|
|
197
|
+
z-index: 1;
|
|
198
|
+
position: relative;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
.cellInRange {
|
|
202
|
+
background: var(--ogrid-bg-range, #cce4f7) !important;
|
|
203
|
+
box-shadow: inset 0 0 0 1px var(--ogrid-primary, #0066cc);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.cellCut {
|
|
207
|
+
outline: 2px dashed var(--ogrid-primary, #0066cc);
|
|
208
|
+
outline-offset: -2px;
|
|
209
|
+
background: var(--ogrid-bg-hover, rgba(0, 0, 0, 0.04)) !important;
|
|
210
|
+
animation: cellCutDash 0.6s linear infinite;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
@keyframes cellCutDash {
|
|
214
|
+
0% {
|
|
215
|
+
outline-offset: -2px;
|
|
216
|
+
}
|
|
217
|
+
50% {
|
|
218
|
+
outline-offset: -3px;
|
|
219
|
+
}
|
|
220
|
+
100% {
|
|
221
|
+
outline-offset: -2px;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
.fillHandle {
|
|
225
|
+
position: absolute;
|
|
226
|
+
right: 2px;
|
|
227
|
+
bottom: 2px;
|
|
228
|
+
width: 8px;
|
|
229
|
+
height: 8px;
|
|
230
|
+
background: var(--ogrid-primary, #0066cc);
|
|
231
|
+
border-radius: 2px;
|
|
232
|
+
cursor: crosshair;
|
|
233
|
+
pointer-events: auto;
|
|
234
|
+
z-index: 3;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.dataTable th.selectionHeaderCell,
|
|
238
|
+
.dataTable td.selectionCell {
|
|
239
|
+
width: 48px;
|
|
240
|
+
min-width: 48px;
|
|
241
|
+
max-width: 48px;
|
|
242
|
+
padding: 4px !important;
|
|
243
|
+
text-align: center;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
.selectionHeaderCellInner,
|
|
247
|
+
.selectionCellInner {
|
|
248
|
+
display: flex;
|
|
249
|
+
align-items: center;
|
|
250
|
+
justify-content: center;
|
|
251
|
+
width: 100%;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.statusBar {
|
|
255
|
+
display: flex;
|
|
256
|
+
align-items: center;
|
|
257
|
+
gap: 16px;
|
|
258
|
+
width: 100%;
|
|
259
|
+
padding: 6px 12px;
|
|
260
|
+
font-size: 12px;
|
|
261
|
+
color: var(--ogrid-muted, #616161);
|
|
262
|
+
background: var(--ogrid-bg-subtle, #f3f2f1);
|
|
263
|
+
border: 1px solid var(--ogrid-border, #e0e0e0);
|
|
264
|
+
border-top: none;
|
|
265
|
+
border-radius: 0 0 6px 6px;
|
|
266
|
+
min-height: 28px;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.statusBarItem {
|
|
270
|
+
display: inline-flex;
|
|
271
|
+
align-items: center;
|
|
272
|
+
gap: 4px;
|
|
273
|
+
}
|
|
274
|
+
.statusBarItem:not(:last-child)::after {
|
|
275
|
+
content: "";
|
|
276
|
+
width: 1px;
|
|
277
|
+
height: 14px;
|
|
278
|
+
background: var(--ogrid-border, #c4c4c4);
|
|
279
|
+
margin-left: 12px;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.statusBarLabel {
|
|
283
|
+
color: var(--ogrid-muted, #707070);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.statusBarValue {
|
|
287
|
+
color: var(--ogrid-fg, #242424);
|
|
288
|
+
font-weight: 600;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.contextMenu {
|
|
292
|
+
position: fixed;
|
|
293
|
+
z-index: 10000;
|
|
294
|
+
min-width: 160px;
|
|
295
|
+
padding: 4px 0;
|
|
296
|
+
background: var(--ogrid-bg, #fff);
|
|
297
|
+
border: 1px solid var(--ogrid-border, #e0e0e0);
|
|
298
|
+
border-radius: 6px;
|
|
299
|
+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
.contextMenuItem {
|
|
303
|
+
display: block;
|
|
304
|
+
width: 100%;
|
|
305
|
+
padding: 6px 12px;
|
|
306
|
+
border: none;
|
|
307
|
+
background: none;
|
|
308
|
+
font-size: 13px;
|
|
309
|
+
text-align: left;
|
|
310
|
+
cursor: pointer;
|
|
311
|
+
color: var(--ogrid-fg, #242424);
|
|
312
|
+
}
|
|
313
|
+
.contextMenuItem:hover:not(:disabled) {
|
|
314
|
+
background: var(--ogrid-bg-hover, #f5f5f5);
|
|
315
|
+
}
|
|
316
|
+
.contextMenuItem:disabled {
|
|
317
|
+
opacity: 0.5;
|
|
318
|
+
cursor: not-allowed;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.contextMenuDivider {
|
|
322
|
+
height: 1px;
|
|
323
|
+
margin: 4px 0;
|
|
324
|
+
background: var(--ogrid-border, #e0e0e0);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
.loadingOverlayContainer {
|
|
328
|
+
position: relative;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
.loadingOverlay {
|
|
332
|
+
position: absolute;
|
|
333
|
+
inset: 0;
|
|
334
|
+
z-index: 2;
|
|
335
|
+
display: flex;
|
|
336
|
+
align-items: center;
|
|
337
|
+
justify-content: center;
|
|
338
|
+
background: rgba(255, 255, 255, 0.7);
|
|
339
|
+
backdrop-filter: blur(1px);
|
|
340
|
+
pointer-events: all;
|
|
341
|
+
border-radius: 6px;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
.loadingOverlayContent {
|
|
345
|
+
display: flex;
|
|
346
|
+
flex-direction: column;
|
|
347
|
+
align-items: center;
|
|
348
|
+
gap: 8px;
|
|
349
|
+
padding: 16px 24px;
|
|
350
|
+
background: var(--ogrid-bg, #fff);
|
|
351
|
+
border: 1px solid var(--ogrid-border, #c4c4c4);
|
|
352
|
+
border-radius: 6px;
|
|
353
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.14);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
.loadingOverlayText {
|
|
357
|
+
font-size: 13px;
|
|
358
|
+
font-weight: 500;
|
|
359
|
+
color: var(--ogrid-muted, #616161);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
.loadingDimmed {
|
|
363
|
+
opacity: 0.6;
|
|
364
|
+
pointer-events: none;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
.emptyStateInGrid {
|
|
368
|
+
display: flex;
|
|
369
|
+
flex-direction: column;
|
|
370
|
+
align-items: center;
|
|
371
|
+
justify-content: center;
|
|
372
|
+
text-align: center;
|
|
373
|
+
padding: 20px 16px;
|
|
374
|
+
min-height: 88px;
|
|
375
|
+
width: 100%;
|
|
376
|
+
box-sizing: border-box;
|
|
377
|
+
border-top: 1px solid var(--ogrid-border, #e0e0e0);
|
|
378
|
+
background: var(--ogrid-bg-subtle, #fafafa);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
.emptyStateInGridTitle {
|
|
382
|
+
font-size: 14px;
|
|
383
|
+
font-weight: 600;
|
|
384
|
+
color: var(--ogrid-fg, #242424);
|
|
385
|
+
margin-bottom: 4px;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
.emptyStateInGridMessage {
|
|
389
|
+
font-size: 13px;
|
|
390
|
+
color: var(--ogrid-muted, #616161);
|
|
391
|
+
line-height: 1.5;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
.emptyStateInGridLink {
|
|
395
|
+
background: none;
|
|
396
|
+
border: none;
|
|
397
|
+
color: var(--ogrid-primary, #0066cc);
|
|
398
|
+
text-decoration: underline;
|
|
399
|
+
cursor: pointer;
|
|
400
|
+
padding: 0;
|
|
401
|
+
font-size: inherit;
|
|
402
|
+
font-family: inherit;
|
|
403
|
+
}
|
|
404
|
+
.emptyStateInGridLink:hover {
|
|
405
|
+
color: var(--ogrid-primary-hover, #0052a3);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
.spinner {
|
|
409
|
+
width: 24px;
|
|
410
|
+
height: 24px;
|
|
411
|
+
border: 2px solid var(--ogrid-border, #e0e0e0);
|
|
412
|
+
border-top-color: var(--ogrid-primary, #0066cc);
|
|
413
|
+
border-radius: 50%;
|
|
414
|
+
animation: ogrid-spin 0.8s linear infinite;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
@keyframes ogrid-spin {
|
|
418
|
+
to {
|
|
419
|
+
transform: rotate(360deg);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { GRID_CONTEXT_MENU_ITEMS, getContextMenuHandlers } from '@alaarab/ogrid-core';
|
|
4
|
+
import styles from './DataGridTable.module.css';
|
|
5
|
+
export function GridContextMenu(props) {
|
|
6
|
+
const { x, y, hasSelection, onClose } = props;
|
|
7
|
+
const ref = React.useRef(null);
|
|
8
|
+
const handlers = React.useMemo(() => getContextMenuHandlers(props), [props]);
|
|
9
|
+
React.useEffect(() => {
|
|
10
|
+
const handleClickOutside = (e) => {
|
|
11
|
+
if (ref.current && !ref.current.contains(e.target))
|
|
12
|
+
onClose();
|
|
13
|
+
};
|
|
14
|
+
const handleKeyDown = (e) => {
|
|
15
|
+
if (e.key === 'Escape')
|
|
16
|
+
onClose();
|
|
17
|
+
};
|
|
18
|
+
document.addEventListener('mousedown', handleClickOutside, true);
|
|
19
|
+
document.addEventListener('keydown', handleKeyDown, true);
|
|
20
|
+
return () => {
|
|
21
|
+
document.removeEventListener('mousedown', handleClickOutside, true);
|
|
22
|
+
document.removeEventListener('keydown', handleKeyDown, true);
|
|
23
|
+
};
|
|
24
|
+
}, [onClose]);
|
|
25
|
+
return (_jsx("div", { ref: ref, className: styles.contextMenu, role: "menu", style: { left: x, top: y }, "aria-label": "Grid context menu", children: GRID_CONTEXT_MENU_ITEMS.map((item) => (_jsxs(React.Fragment, { children: [item.id === 'selectAll' && _jsx("div", { className: styles.contextMenuDivider }), _jsx("button", { type: "button", className: styles.contextMenuItem, onClick: handlers[item.id], disabled: item.disabledWhenNoSelection ? !hasSelection : false, children: item.label })] }, item.id))) }));
|
|
26
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import * as Checkbox from '@radix-ui/react-checkbox';
|
|
4
|
+
import { useInlineCellEditorState } from '@alaarab/ogrid-core';
|
|
5
|
+
export function InlineCellEditor(props) {
|
|
6
|
+
const { value, column, editorType, onCommit, onCancel } = props;
|
|
7
|
+
const wrapperRef = React.useRef(null);
|
|
8
|
+
const { localValue, setLocalValue, handleKeyDown, handleBlur, commit, cancel } = useInlineCellEditorState({ value, editorType, onCommit, onCancel });
|
|
9
|
+
React.useEffect(() => {
|
|
10
|
+
const input = wrapperRef.current?.querySelector('input');
|
|
11
|
+
input?.focus();
|
|
12
|
+
}, []);
|
|
13
|
+
if (editorType === 'checkbox') {
|
|
14
|
+
const checked = value === true;
|
|
15
|
+
return (_jsx(Checkbox.Root, { checked: checked, onCheckedChange: (c) => commit(c === true), onKeyDown: (e) => e.key === 'Escape' && (e.preventDefault(), cancel()), children: _jsx(Checkbox.Indicator, { children: "\u2713" }) }));
|
|
16
|
+
}
|
|
17
|
+
if (editorType === 'select') {
|
|
18
|
+
const values = column.cellEditorParams?.values ?? [];
|
|
19
|
+
return (_jsx("select", { value: value !== null && value !== undefined ? String(value) : '', onChange: (e) => commit(e.target.value), onKeyDown: (e) => e.key === 'Escape' && (e.preventDefault(), cancel()), autoFocus: true, children: values.map((v) => (_jsx("option", { value: String(v), children: String(v) }, String(v)))) }));
|
|
20
|
+
}
|
|
21
|
+
return (_jsx("div", { ref: wrapperRef, children: _jsx("input", { type: "text", value: localValue, onChange: (e) => setLocalValue(e.target.value), onBlur: handleBlur, onKeyDown: handleKeyDown, style: { minWidth: 60 }, autoFocus: true }) }));
|
|
22
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { getStatusBarParts } from '@alaarab/ogrid-core';
|
|
3
|
+
import styles from './DataGridTable.module.css';
|
|
4
|
+
export function StatusBar(props) {
|
|
5
|
+
const parts = getStatusBarParts(props);
|
|
6
|
+
return (_jsx("div", { className: styles.statusBar, role: "status", "aria-live": "polite", children: parts.map((p) => (_jsxs("span", { className: styles.statusBarItem, children: [_jsx("span", { className: styles.statusBarLabel, children: p.label }), _jsx("span", { className: styles.statusBarValue, children: p.value.toLocaleString() })] }, p.key))) }));
|
|
7
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { forwardRef } from 'react';
|
|
4
|
+
import { DataGridTable } from '../DataGridTable/DataGridTable';
|
|
5
|
+
import { ColumnChooser } from '../ColumnChooser/ColumnChooser';
|
|
6
|
+
import { PaginationControls } from '../PaginationControls/PaginationControls';
|
|
7
|
+
import { useOGrid, OGridLayout, } from '@alaarab/ogrid-core';
|
|
8
|
+
const OGridInner = forwardRef(function OGridInner(props, ref) {
|
|
9
|
+
const { dataGridProps, page, pageSize, displayTotalCount, setPage, setPageSize, columnChooserColumns, visibleColumns, handleVisibilityChange, title, toolbar, className, entityLabelPlural, } = useOGrid(props, ref);
|
|
10
|
+
return (_jsx(OGridLayout, { className: className, gap: 16, title: title, toolbar: toolbar, columnChooser: _jsx(ColumnChooser, { columns: columnChooserColumns, visibleColumns: visibleColumns, onVisibilityChange: handleVisibilityChange }), pagination: _jsx(PaginationControls, { currentPage: page, pageSize: pageSize, totalCount: displayTotalCount, onPageChange: setPage, onPageSizeChange: (size) => {
|
|
11
|
+
setPageSize(size);
|
|
12
|
+
setPage(1);
|
|
13
|
+
}, entityLabelPlural: entityLabelPlural }), children: _jsx(DataGridTable, { ...dataGridProps }) }));
|
|
14
|
+
});
|
|
15
|
+
OGridInner.displayName = 'OGrid';
|
|
16
|
+
export const OGrid = React.memo(OGridInner);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { useMemo, useCallback } from 'react';
|
|
4
|
+
import { getPaginationViewModel } from '@alaarab/ogrid-core';
|
|
5
|
+
import styles from './PaginationControls.module.css';
|
|
6
|
+
function ChevronLeft() {
|
|
7
|
+
return _jsx("span", { "aria-hidden": true, children: "\u2039" });
|
|
8
|
+
}
|
|
9
|
+
function ChevronRight() {
|
|
10
|
+
return _jsx("span", { "aria-hidden": true, children: "\u203A" });
|
|
11
|
+
}
|
|
12
|
+
function ChevronDoubleLeft() {
|
|
13
|
+
return _jsx("span", { "aria-hidden": true, children: "\u00AB" });
|
|
14
|
+
}
|
|
15
|
+
function ChevronDoubleRight() {
|
|
16
|
+
return _jsx("span", { "aria-hidden": true, children: "\u00BB" });
|
|
17
|
+
}
|
|
18
|
+
export const PaginationControls = React.memo((props) => {
|
|
19
|
+
const { currentPage, pageSize, totalCount, onPageChange, onPageSizeChange, entityLabelPlural, className } = props;
|
|
20
|
+
const labelPlural = entityLabelPlural ?? 'items';
|
|
21
|
+
const vm = useMemo(() => getPaginationViewModel(currentPage, pageSize, totalCount), [currentPage, pageSize, totalCount]);
|
|
22
|
+
const handlePageSizeChange = useCallback((e) => {
|
|
23
|
+
onPageSizeChange(Number(e.target.value));
|
|
24
|
+
}, [onPageSizeChange]);
|
|
25
|
+
if (!vm) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
const { pageNumbers, showStartEllipsis, showEndEllipsis, totalPages, startItem, endItem } = vm;
|
|
29
|
+
return (_jsxs("div", { className: `${styles.pagination} ${className || ''}`, role: "navigation", "aria-label": "Pagination", children: [_jsxs("div", { className: styles.paginationInfo, children: ["Showing ", startItem, " to ", endItem, " of ", totalCount.toLocaleString(), " ", labelPlural] }), _jsxs("div", { className: styles.paginationControls, children: [_jsx("button", { type: "button", className: styles.navBtn, onClick: () => onPageChange(1), disabled: currentPage === 1, "aria-label": "First page", children: _jsx(ChevronDoubleLeft, {}) }), _jsx("button", { type: "button", className: styles.navBtn, onClick: () => onPageChange(currentPage - 1), disabled: currentPage === 1, "aria-label": "Previous page", children: _jsx(ChevronLeft, {}) }), _jsxs("div", { className: styles.pageNumbers, children: [showStartEllipsis && (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: styles.pageBtn, onClick: () => onPageChange(1), "aria-label": "Page 1", children: "1" }), _jsx("span", { className: styles.ellipsis, "aria-hidden": true, children: "\u2026" })] })), pageNumbers.map((pageNum) => (_jsx("button", { type: "button", className: `${styles.pageBtn} ${currentPage === pageNum ? styles.active : ''}`, onClick: () => onPageChange(pageNum), "aria-label": `Page ${pageNum}`, "aria-current": currentPage === pageNum ? 'page' : undefined, children: pageNum }, pageNum))), showEndEllipsis && (_jsxs(_Fragment, { children: [_jsx("span", { className: styles.ellipsis, "aria-hidden": true, children: "\u2026" }), _jsx("button", { type: "button", className: styles.pageBtn, onClick: () => onPageChange(totalPages), "aria-label": `Page ${totalPages}`, children: totalPages })] }))] }), _jsx("button", { type: "button", className: styles.navBtn, onClick: () => onPageChange(currentPage + 1), disabled: currentPage >= totalPages, "aria-label": "Next page", children: _jsx(ChevronRight, {}) }), _jsx("button", { type: "button", className: styles.navBtn, onClick: () => onPageChange(totalPages), disabled: currentPage >= totalPages, "aria-label": "Last page", children: _jsx(ChevronDoubleRight, {}) })] }), _jsxs("div", { className: styles.pageSizeSelector, children: [_jsx("span", { className: styles.pageSizeLabel, children: "Rows" }), _jsx("select", { className: styles.pageSizeSelect, value: String(pageSize), onChange: handlePageSizeChange, "aria-label": "Rows per page", children: vm.pageSizeOptions.map((n) => (_jsx("option", { value: n, children: n }, n))) })] })] }));
|
|
30
|
+
});
|
|
31
|
+
PaginationControls.displayName = 'PaginationControls';
|