@alaarab/ogrid 1.3.2 → 1.5.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.
@@ -44,6 +44,7 @@
44
44
  display: flex;
45
45
  flex-direction: column;
46
46
  padding: 0;
47
+ z-index: 50;
47
48
  }
48
49
 
49
50
  .header {
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import * as React from 'react';
3
3
  import * as Popover from '@radix-ui/react-popover';
4
4
  import { useColumnHeaderFilterState } from '@alaarab/ogrid-core';
@@ -15,7 +15,7 @@ function FilterIcon() {
15
15
  return _jsx("span", { "aria-hidden": true, children: '\u25BE' });
16
16
  }
17
17
  export const ColumnHeaderFilter = React.memo((props) => {
18
- const { columnName, filterType, isSorted = false, isSortedDescending = false, onSort, selectedValues, onFilterChange, options = [], isLoadingOptions = false, textValue = '', onTextChange, selectedUser, onUserChange, peopleSearch, } = props;
18
+ const { columnName, filterType, isSorted = false, isSortedDescending = false, onSort, selectedValues, onFilterChange, options = [], isLoadingOptions = false, textValue = '', onTextChange, selectedUser, onUserChange, peopleSearch, dateValue, onDateChange, } = props;
19
19
  const state = useColumnHeaderFilterState({
20
20
  filterType,
21
21
  isSorted,
@@ -30,6 +30,8 @@ export const ColumnHeaderFilter = React.memo((props) => {
30
30
  selectedUser,
31
31
  onUserChange,
32
32
  peopleSearch,
33
+ dateValue,
34
+ onDateChange,
33
35
  });
34
36
  const { headerRef, popoverRef, peopleInputRef, isFilterOpen, setFilterOpen, tempSelected, tempTextValue, setTempTextValue, searchText, setSearchText, filteredOptions, peopleSuggestions, isPeopleLoading, peopleSearchText, setPeopleSearchText, hasActiveFilter, handlers, } = state;
35
37
  const safeOptions = options ?? [];
@@ -43,6 +45,9 @@ export const ColumnHeaderFilter = React.memo((props) => {
43
45
  if (filterType === 'people') {
44
46
  return (_jsx(PeopleFilterPopover, { selectedUser: selectedUser, searchText: peopleSearchText, onSearchChange: setPeopleSearchText, suggestions: peopleSuggestions, isLoading: isPeopleLoading, onUserSelect: handlers.handleUserSelect, onClearUser: handlers.handleClearUser, inputRef: peopleInputRef }));
45
47
  }
48
+ if (filterType === 'date') {
49
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { style: { padding: '8px 12px', display: 'flex', flexDirection: 'column', gap: 6 }, children: [_jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: 6, fontSize: 12 }, children: ["From:", _jsx("input", { type: "date", value: state.tempDateFrom, onChange: (e) => state.setTempDateFrom(e.target.value), style: { flex: 1 } })] }), _jsxs("label", { style: { display: 'flex', alignItems: 'center', gap: 6, fontSize: 12 }, children: ["To:", _jsx("input", { type: "date", value: state.tempDateTo, onChange: (e) => state.setTempDateTo(e.target.value), style: { flex: 1 } })] })] }), _jsxs("div", { className: styles.popoverActions, children: [_jsx("button", { className: styles.clearButton, onClick: handlers.handleDateClear, disabled: !state.tempDateFrom && !state.tempDateTo, children: "Clear" }), _jsx("button", { className: styles.applyButton, onClick: handlers.handleDateApply, children: "Apply" })] })] }));
50
+ }
46
51
  return null;
47
52
  };
48
53
  return (_jsxs("div", { className: styles.columnHeader, ref: headerRef, children: [_jsx("div", { className: styles.headerContent, children: _jsx("span", { className: styles.columnName, title: columnName, "data-header-label": true, children: columnName }) }), _jsxs("div", { className: styles.headerActions, children: [onSort && (_jsx("button", { type: "button", className: `${styles.sortIcon} ${isSorted ? styles.sortActive : ''}`, onClick: handlers.handleSortClick, "aria-label": `Sort by ${columnName}`, title: isSorted ? (isSortedDescending ? 'Sorted descending' : 'Sorted ascending') : 'Sort', children: _jsx(SortIcon, { isSorted: isSorted, isDesc: isSortedDescending }) })), filterType !== 'none' && (_jsxs(Popover.Root, { open: isFilterOpen, onOpenChange: setFilterOpen, children: [_jsx(Popover.Trigger, { asChild: true, children: _jsxs("button", { type: "button", className: `${styles.filterIcon} ${hasActiveFilter ? styles.filterActive : ''} ${isFilterOpen ? styles.filterOpen : ''}`, onClick: handlers.handleFilterIconClick, "aria-label": `Filter ${columnName}`, title: `Filter ${columnName}`, children: [_jsx(FilterIcon, {}), hasActiveFilter && _jsx("span", { className: styles.filterBadge })] }) }), _jsx(Popover.Portal, { children: _jsxs(Popover.Content, { ref: popoverRef, className: styles.popoverContent, sideOffset: 4, align: "start", onOpenAutoFocus: (e) => e.preventDefault(), children: [_jsxs("div", { className: styles.popoverHeader, children: ["Filter: ", columnName] }), renderPopoverContent()] }) })] }))] })] }));
@@ -8,60 +8,38 @@ import { ColumnHeaderFilter } from '../ColumnHeaderFilter';
8
8
  import { InlineCellEditor } from './InlineCellEditor';
9
9
  import { StatusBar } from './StatusBar';
10
10
  import { GridContextMenu } from './GridContextMenu';
11
- import { useDataGridState, useColumnResize, getHeaderFilterConfig, getCellRenderDescriptor, MarchingAntsOverlay, } from '@alaarab/ogrid-core';
11
+ import { useDataGridState, useColumnResize, getHeaderFilterConfig, getCellRenderDescriptor, buildHeaderRows, MarchingAntsOverlay, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, getCellInteractionProps, } from '@alaarab/ogrid-core';
12
12
  import styles from './DataGridTable.module.css';
13
13
  function DataGridTableInner(props) {
14
14
  const wrapperRef = useRef(null);
15
15
  const tableContainerRef = useRef(null);
16
16
  const state = useDataGridState({ props, wrapperRef });
17
17
  const lastMouseShiftRef = useRef(false);
18
- const { visibleCols, totalColCount, hasCheckboxCol, selectedRowIds, updateSelection, handleRowCheckboxChange, handleSelectAll, allSelected, someSelected, setEditingCell, pendingEditorValue, setPendingEditorValue, setActiveCell, handleCellMouseDown, handleSelectAllCells, contextMenu, setContextMenu, handleCellContextMenu, closeContextMenu, canUndo, canRedo, onUndo, onRedo, handleCopy, handleCut, handlePaste, handleGridKeyDown, handleFillHandleMouseDown, containerWidth, minTableWidth, columnSizingOverrides, setColumnSizingOverrides, statusBarConfig, showEmptyInGrid, hasCellSelection, selectionRange, copyRange, cutRange, colOffset, headerFilterInput, cellDescriptorInput, commitCellEdit, cancelPopoverEdit, popoverAnchorEl, setPopoverAnchorEl, } = state;
19
- const { items, getRowId, emptyState, layoutMode = 'fill', rowSelection = 'none', freezeRows, freezeCols, isLoading = false, loadingMessage = 'Loading\u2026', 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, } = props;
20
- const allowOverflowX = containerWidth > 0 && minTableWidth > containerWidth;
18
+ const { visibleCols, totalColCount, hasCheckboxCol, selectedRowIds, updateSelection, handleRowCheckboxChange, handleSelectAll, allSelected, someSelected, setEditingCell, pendingEditorValue, setPendingEditorValue, setActiveCell, handleCellMouseDown, handleSelectAllCells, contextMenu, handleCellContextMenu, closeContextMenu, canUndo, canRedo, onUndo, onRedo, handleCopy, handleCut, handlePaste, handleGridKeyDown, handleFillHandleMouseDown, containerWidth, minTableWidth, desiredTableWidth, columnSizingOverrides, setColumnSizingOverrides, statusBarConfig, showEmptyInGrid, hasCellSelection, selectionRange, copyRange, cutRange, colOffset, headerFilterInput, cellDescriptorInput, commitCellEdit, cancelPopoverEdit, popoverAnchorEl, setPopoverAnchorEl, } = state;
19
+ const { items, columns, getRowId, emptyState, layoutMode = 'fill', rowSelection = 'none', freezeRows, freezeCols, suppressHorizontalScroll, isLoading = false, loadingMessage = 'Loading\u2026', 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, visibleColumns, } = props;
20
+ const headerRows = buildHeaderRows(columns, visibleColumns);
21
+ const allowOverflowX = !suppressHorizontalScroll && containerWidth > 0 && (minTableWidth > containerWidth || desiredTableWidth > containerWidth);
21
22
  const fitToContent = layoutMode === 'content';
22
23
  const { handleResizeStart, getColumnWidth } = useColumnResize({
23
24
  columnSizingOverrides,
24
25
  setColumnSizingOverrides,
25
26
  });
27
+ const editCallbacks = React.useMemo(() => ({ commitCellEdit, setEditingCell, setPendingEditorValue, cancelPopoverEdit }), [commitCellEdit, setEditingCell, setPendingEditorValue, cancelPopoverEdit]);
28
+ const interactionHandlers = React.useMemo(() => ({ handleCellMouseDown, setActiveCell, setEditingCell, handleCellContextMenu }), [handleCellMouseDown, setActiveCell, setEditingCell, handleCellContextMenu]);
26
29
  const renderCellContent = useCallback((item, col, rowIndex, colIdx) => {
27
30
  const descriptor = getCellRenderDescriptor(item, col, rowIndex, colIdx, cellDescriptorInput);
28
31
  if (descriptor.mode === 'editing-inline') {
29
- 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) }));
32
+ return _jsx(InlineCellEditor, { ...buildInlineEditorProps(item, col, descriptor, editCallbacks) });
30
33
  }
31
34
  if (descriptor.mode === 'editing-popover' && typeof col.cellEditor === 'function') {
32
- const oldValue = descriptor.value;
33
- const displayValue = pendingEditorValue !== undefined ? pendingEditorValue : oldValue;
35
+ const editorProps = buildPopoverEditorProps(item, col, descriptor, pendingEditorValue, editCallbacks);
34
36
  const CustomEditor = col.cellEditor;
35
- const editorProps = {
36
- value: displayValue,
37
- onValueChange: setPendingEditorValue,
38
- onCommit: () => {
39
- const newValue = pendingEditorValue !== undefined ? pendingEditorValue : oldValue;
40
- commitCellEdit(item, col.columnId, oldValue, newValue, descriptor.rowIndex, descriptor.globalColIndex);
41
- },
42
- onCancel: cancelPopoverEdit,
43
- item,
44
- column: col,
45
- cellEditorParams: col.cellEditorParams,
46
- };
47
37
  return (_jsxs(Popover.Root, { open: !!popoverAnchorEl, onOpenChange: (open) => { if (!open)
48
38
  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 }) }) })] }));
49
39
  }
50
- let content;
51
- if (col.renderCell)
52
- content = col.renderCell(item);
53
- else {
54
- const value = descriptor.displayValue;
55
- if (col.valueFormatter)
56
- content = col.valueFormatter(value, item);
57
- else if (value !== null && value !== undefined)
58
- content = String(value);
59
- else
60
- content = null;
61
- }
62
- const cellStyle = col.cellStyle ? (typeof col.cellStyle === 'function' ? col.cellStyle(item) : col.cellStyle) : undefined;
63
- if (cellStyle)
64
- content = _jsx("span", { style: cellStyle, children: content });
40
+ const content = resolveCellDisplayContent(col, item, descriptor.displayValue);
41
+ const cellStyle = resolveCellStyle(col, item);
42
+ const styledContent = cellStyle ? _jsx("span", { style: cellStyle, children: content }) : content;
65
43
  const cellClassNames = [
66
44
  styles.cellContent,
67
45
  descriptor.isActive && !descriptor.isInRange ? styles.activeCellContent : '',
@@ -69,47 +47,30 @@ function DataGridTableInner(props) {
69
47
  descriptor.isInCutRange ? styles.cellCut : '',
70
48
  descriptor.isInCopyRange ? styles.cellCopied : '',
71
49
  ].filter(Boolean).join(' ');
72
- if (descriptor.canEditAny) {
73
- 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: { cursor: 'cell' }, children: [content, descriptor.isSelectionEndCell && (_jsx("div", { className: styles.fillHandle, onMouseDown: handleFillHandleMouseDown, "aria-label": "Fill handle" }))] }));
74
- }
75
- 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, children: content }));
76
- }, [cellDescriptorInput, pendingEditorValue, popoverAnchorEl, handleCellMouseDown, handleCellContextMenu, handleFillHandleMouseDown, setActiveCell, setEditingCell, setPendingEditorValue, setPopoverAnchorEl, commitCellEdit, cancelPopoverEdit]);
50
+ const interactionProps = getCellInteractionProps(descriptor, col.columnId, interactionHandlers);
51
+ return (_jsxs("div", { className: cellClassNames, ...interactionProps, style: descriptor.canEditAny ? { cursor: 'cell' } : undefined, children: [styledContent, descriptor.canEditAny && descriptor.isSelectionEndCell && (_jsx("div", { className: styles.fillHandle, onMouseDown: handleFillHandleMouseDown, "aria-label": "Fill handle" }))] }));
52
+ }, [cellDescriptorInput, pendingEditorValue, popoverAnchorEl, editCallbacks, interactionHandlers, handleFillHandleMouseDown, setPopoverAnchorEl, cancelPopoverEdit]);
77
53
  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(); }, onKeyDown: handleGridKeyDown, style: {
78
54
  ['--data-table-column-count']: totalColCount,
79
55
  ['--data-table-width']: showEmptyInGrid ? '100%' : allowOverflowX ? 'fit-content' : fitToContent ? 'fit-content' : '100%',
80
56
  ['--data-table-min-width']: showEmptyInGrid ? '100%' : allowOverflowX ? 'max-content' : fitToContent ? 'max-content' : '100%',
81
57
  ['--data-table-total-min-width']: `${minTableWidth}px`,
82
- }, 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: styles.tableScrollContent, children: _jsx("div", { className: isLoading && items.length > 0 ? styles.loadingDimmed : undefined, children: _jsxs("div", { className: styles.tableWidthAnchor, ref: tableContainerRef, 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, { className: styles.rowCheckbox, checked: allSelected ? true : someSelected ? 'indeterminate' : false, onCheckedChange: (c) => handleSelectAll(!!c), "aria-label": "Select all rows", children: _jsx(Checkbox.Indicator, { className: styles.rowCheckboxIndicator, children: someSelected && !allSelected ? '–' : '✓' }) }) }) })), visibleCols.map((col, colIdx) => {
83
- const isFreezeCol = freezeCols != null && freezeCols >= 1 && colIdx < freezeCols;
84
- const isPinnedLeft = col.pinned === 'left';
85
- const isPinnedRight = col.pinned === 'right';
86
- const columnWidth = getColumnWidth(col);
87
- const hasExplicitWidth = !!(columnSizingOverrides[col.columnId] || col.idealWidth != null || col.defaultWidth != null);
88
- return (_jsxs("th", { scope: "col", "data-column-id": col.columnId, className: [
89
- isFreezeCol ? styles.freezeCol : '',
90
- isFreezeCol && colIdx === 0 ? styles.freezeColFirst : '',
91
- isPinnedLeft ? styles.pinnedColLeft : '',
92
- isPinnedRight ? styles.pinnedColRight : '',
93
- ].filter(Boolean).join(' '), style: {
94
- minWidth: col.minWidth ?? 80,
95
- width: hasExplicitWidth ? columnWidth : undefined,
96
- maxWidth: hasExplicitWidth ? columnWidth : undefined,
97
- }, children: [_jsx(ColumnHeaderFilter, { ...getHeaderFilterConfig(col, headerFilterInput) }), _jsx("div", { className: styles.resizeHandle, onMouseDown: (e) => handleResizeStart(e, col), "aria-label": `Resize ${col.name}` })] }, col.columnId));
98
- })] }) }), !showEmptyInGrid && (_jsx("tbody", { children: items.map((item, rowIndex) => {
99
- const rowIdStr = getRowId(item);
100
- const isSelected = selectedRowIds.has(rowIdStr);
101
- return (_jsxs("tr", { className: isSelected ? styles.selectedRow : '', onClick: () => {
102
- if (rowSelection === 'single') {
103
- const id = getRowId(item);
104
- updateSelection(selectedRowIds.has(id) ? new Set() : new Set([id]));
105
- }
106
- }, 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(Checkbox.Root, { className: styles.rowCheckbox, checked: selectedRowIds.has(rowIdStr), onCheckedChange: (c) => handleRowCheckboxChange(rowIdStr, !!c, rowIndex, lastMouseShiftRef.current), "aria-label": `Select row ${rowIndex + 1}`, children: _jsx(Checkbox.Indicator, { className: styles.rowCheckboxIndicator, children: "\u2713" }) }) }) })), visibleCols.map((col, colIdx) => {
58
+ }, 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 })] }) })), _jsxs("div", { className: styles.tableScrollContent, children: [_jsx("div", { className: isLoading && items.length > 0 ? styles.loadingDimmed : undefined, children: _jsxs("div", { className: styles.tableWidthAnchor, ref: tableContainerRef, children: [_jsxs("table", { className: styles.dataTable, children: [_jsx("thead", { className: styles.stickyHeader, children: headerRows.map((row, rowIdx) => (_jsxs("tr", { children: [rowIdx === headerRows.length - 1 && hasCheckboxCol && (_jsx("th", { className: styles.selectionHeaderCell, scope: "col", rowSpan: 1, children: _jsx("div", { className: styles.selectionHeaderCellInner, children: _jsx(Checkbox.Root, { className: styles.rowCheckbox, checked: allSelected ? true : someSelected ? 'indeterminate' : false, onCheckedChange: (c) => handleSelectAll(!!c), "aria-label": "Select all rows", children: _jsx(Checkbox.Indicator, { className: styles.rowCheckboxIndicator, children: someSelected && !allSelected ? '–' : '✓' }) }) }) })), rowIdx === 0 && rowIdx < headerRows.length - 1 && hasCheckboxCol && (_jsx("th", { rowSpan: headerRows.length - 1 })), row.map((cell, cellIdx) => {
59
+ if (cell.isGroup) {
60
+ return (_jsx("th", { colSpan: cell.colSpan, className: styles.groupHeaderCell, scope: "colgroup", children: cell.label }, cellIdx));
61
+ }
62
+ // Leaf cell
63
+ const col = cell.columnDef;
64
+ const colIdx = visibleCols.indexOf(col);
107
65
  const isFreezeCol = freezeCols != null && freezeCols >= 1 && colIdx < freezeCols;
108
66
  const isPinnedLeft = col.pinned === 'left';
109
67
  const isPinnedRight = col.pinned === 'right';
110
68
  const columnWidth = getColumnWidth(col);
111
69
  const hasExplicitWidth = !!(columnSizingOverrides[col.columnId] || col.idealWidth != null || col.defaultWidth != null);
112
- return (_jsx("td", { className: [
70
+ const leafRowSpan = headerRows.length > 1 && rowIdx < headerRows.length - 1
71
+ ? headerRows.length - rowIdx
72
+ : undefined;
73
+ return (_jsxs("th", { scope: "col", "data-column-id": col.columnId, rowSpan: leafRowSpan, className: [
113
74
  isFreezeCol ? styles.freezeCol : '',
114
75
  isFreezeCol && colIdx === 0 ? styles.freezeColFirst : '',
115
76
  isPinnedLeft ? styles.pinnedColLeft : '',
@@ -118,9 +79,34 @@ function DataGridTableInner(props) {
118
79
  minWidth: col.minWidth ?? 80,
119
80
  width: hasExplicitWidth ? columnWidth : undefined,
120
81
  maxWidth: hasExplicitWidth ? columnWidth : undefined,
121
- }, children: renderCellContent(item, col, rowIndex, colIdx) }, col.columnId));
122
- })] }, rowIdStr));
123
- }) }))] }), _jsx(MarchingAntsOverlay, { containerRef: tableContainerRef, selectionRange: selectionRange, copyRange: copyRange, cutRange: cutRange, colOffset: colOffset }), 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 })), 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.') })] })) }) }))] }) }) }), contextMenu &&
82
+ }, children: [_jsx(ColumnHeaderFilter, { ...getHeaderFilterConfig(col, headerFilterInput) }), _jsx("div", { className: styles.resizeHandle, onMouseDown: (e) => handleResizeStart(e, col), "aria-label": `Resize ${col.name}` })] }, col.columnId));
83
+ })] }, rowIdx))) }), !showEmptyInGrid && (_jsx("tbody", { children: items.map((item, rowIndex) => {
84
+ const rowIdStr = getRowId(item);
85
+ const isSelected = selectedRowIds.has(rowIdStr);
86
+ return (_jsxs("tr", { className: isSelected ? styles.selectedRow : '', onClick: () => {
87
+ if (rowSelection === 'single') {
88
+ const id = getRowId(item);
89
+ updateSelection(selectedRowIds.has(id) ? new Set() : new Set([id]));
90
+ }
91
+ }, 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(Checkbox.Root, { className: styles.rowCheckbox, checked: selectedRowIds.has(rowIdStr), onCheckedChange: (c) => handleRowCheckboxChange(rowIdStr, !!c, rowIndex, lastMouseShiftRef.current), "aria-label": `Select row ${rowIndex + 1}`, children: _jsx(Checkbox.Indicator, { className: styles.rowCheckboxIndicator, children: "\u2713" }) }) }) })), visibleCols.map((col, colIdx) => {
92
+ const isFreezeCol = freezeCols != null && freezeCols >= 1 && colIdx < freezeCols;
93
+ const isPinnedLeft = col.pinned === 'left';
94
+ const isPinnedRight = col.pinned === 'right';
95
+ const columnWidth = getColumnWidth(col);
96
+ const hasExplicitWidth = !!(columnSizingOverrides[col.columnId] || col.idealWidth != null || col.defaultWidth != null);
97
+ return (_jsx("td", { className: [
98
+ isFreezeCol ? styles.freezeCol : '',
99
+ isFreezeCol && colIdx === 0 ? styles.freezeColFirst : '',
100
+ isPinnedLeft ? styles.pinnedColLeft : '',
101
+ isPinnedRight ? styles.pinnedColRight : '',
102
+ ].filter(Boolean).join(' '), style: {
103
+ minWidth: col.minWidth ?? 80,
104
+ width: hasExplicitWidth ? columnWidth : undefined,
105
+ maxWidth: hasExplicitWidth ? columnWidth : undefined,
106
+ textAlign: col.type === 'numeric' ? 'right' : col.type === 'boolean' ? 'center' : undefined,
107
+ }, children: renderCellContent(item, col, rowIndex, colIdx) }, col.columnId));
108
+ })] }, rowIdStr));
109
+ }) }))] }), _jsx(MarchingAntsOverlay, { containerRef: tableContainerRef, selectionRange: selectionRange, copyRange: copyRange, cutRange: cutRange, colOffset: colOffset }), 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, aggregation: statusBarConfig.aggregation, suppressRowCount: statusBarConfig.suppressRowCount }))] }), contextMenu &&
124
110
  createPortal(_jsx(GridContextMenu, { x: contextMenu.x, y: contextMenu.y, hasSelection: hasCellSelection, canUndo: canUndo, canRedo: canRedo, onUndo: onUndo ?? (() => { }), onRedo: onRedo ?? (() => { }), onCopy: handleCopy, onCut: handleCut, onPaste: () => void handlePaste(), onSelectAll: handleSelectAllCells, onClose: closeContextMenu }), document.body)] }));
125
111
  }
126
112
  export const DataGridTable = React.memo(DataGridTableInner);
@@ -4,18 +4,21 @@
4
4
  flex-direction: column;
5
5
  width: 100%;
6
6
  min-width: 0;
7
+ min-height: 100%;
7
8
  background: var(--ogrid-bg, #fff);
8
9
  }
9
10
 
10
11
  .tableWrapper {
11
12
  position: relative;
13
+ flex: 1;
14
+ min-height: 0;
12
15
  overflow-x: hidden;
13
- overflow-y: visible;
16
+ overflow-y: auto;
14
17
  width: 100%;
15
18
  min-width: 0;
16
19
  max-width: 100%;
17
- border-radius: 6px;
18
20
  box-sizing: border-box;
21
+ background: var(--ogrid-bg, #fff);
19
22
  }
20
23
  .tableWrapper[data-overflow-x=true] {
21
24
  overflow-x: auto;
@@ -27,8 +30,9 @@
27
30
  .tableWidthAnchor {
28
31
  position: relative;
29
32
  width: max-content;
30
- /* No min-width: 100% anchor sizes to grid content so status bar aligns with the table border.
31
- .tableScrollContent provides the full-width background so no gap is visible. */
33
+ /* min-width uses the same CSS var as the table: 100% in fill mode, max-content otherwise.
34
+ Safe now that StatusBar is outside this anchor. */
35
+ min-width: var(--data-table-min-width, max-content);
32
36
  background: var(--ogrid-bg, #fff);
33
37
  }
34
38
 
@@ -38,8 +42,6 @@
38
42
  min-width: var(--data-table-min-width, max-content);
39
43
  border-collapse: separate;
40
44
  border-spacing: 0;
41
- border: 1px solid var(--ogrid-border, #e0e0e0);
42
- border-radius: 6px;
43
45
  box-sizing: border-box;
44
46
  table-layout: auto;
45
47
  }
@@ -66,12 +68,6 @@
66
68
  border-right: none;
67
69
  }
68
70
 
69
- /* When status bar follows, remove bottom border-radius so the frame connects */
70
- .tableWidthAnchor:has(.statusBar) .dataTable {
71
- border-bottom-left-radius: 0;
72
- border-bottom-right-radius: 0;
73
- }
74
-
75
71
  .dataTable thead {
76
72
  background: var(--ogrid-bg-subtle, #f3f2f1);
77
73
  }
@@ -154,21 +150,37 @@
154
150
  border-bottom: 1px solid var(--ogrid-border, #e0e0e0);
155
151
  }
156
152
 
157
- /* Column resize handle */
153
+ .groupHeaderCell {
154
+ text-align: center;
155
+ font-weight: 600;
156
+ border-bottom: 2px solid var(--ogrid-border, #e0e0e0);
157
+ padding: 6px 10px;
158
+ background: var(--ogrid-header-bg, #f5f5f5);
159
+ }
160
+
161
+ /* Column resize handle — wide hit area with narrow visual indicator */
158
162
  .resizeHandle {
159
163
  position: absolute;
160
164
  top: 0;
161
- right: 0;
165
+ right: -3px;
162
166
  bottom: 0;
163
- width: 4px;
167
+ width: 8px;
164
168
  cursor: col-resize;
165
169
  user-select: none;
166
170
  z-index: 1;
167
171
  }
168
- .resizeHandle:hover {
172
+ .resizeHandle::after {
173
+ content: "";
174
+ position: absolute;
175
+ top: 0;
176
+ right: 3px;
177
+ bottom: 0;
178
+ width: 2px;
179
+ }
180
+ .resizeHandle:hover::after {
169
181
  background-color: var(--ogrid-accent, #0078d4);
170
182
  }
171
- .resizeHandle:active {
183
+ .resizeHandle:active::after {
172
184
  background-color: var(--ogrid-accent-dark, #005a9e);
173
185
  }
174
186
 
@@ -301,10 +313,9 @@
301
313
  font-size: 12px;
302
314
  color: var(--ogrid-muted, #616161);
303
315
  background: var(--ogrid-bg-subtle, #f3f2f1);
304
- border: 1px solid var(--ogrid-border, #e0e0e0);
305
- border-top: none;
306
- border-radius: 0 0 6px 6px;
316
+ border-top: 1px solid var(--ogrid-border, #e0e0e0);
307
317
  min-height: 28px;
318
+ margin-top: auto;
308
319
  }
309
320
 
310
321
  .statusBarItem {
@@ -387,7 +398,6 @@
387
398
  background: rgba(255, 255, 255, 0.7);
388
399
  backdrop-filter: blur(1px);
389
400
  pointer-events: all;
390
- border-radius: 6px;
391
401
  }
392
402
 
393
403
  .loadingOverlayContent {
@@ -1,22 +1,85 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import * as React from 'react';
3
3
  import * as Checkbox from '@radix-ui/react-checkbox';
4
- import { useInlineCellEditorState } from '@alaarab/ogrid-core';
4
+ import { useInlineCellEditorState, useRichSelectState } from '@alaarab/ogrid-core';
5
+ // Match .cellContent layout so column width doesn't shift during editing
6
+ const editorWrapperStyle = {
7
+ width: '100%',
8
+ height: '100%',
9
+ display: 'flex',
10
+ alignItems: 'center',
11
+ padding: '6px 10px',
12
+ boxSizing: 'border-box',
13
+ overflow: 'hidden',
14
+ minWidth: 0,
15
+ };
16
+ const editorInputStyle = {
17
+ width: '100%',
18
+ padding: 0,
19
+ border: 'none',
20
+ background: 'transparent',
21
+ color: 'inherit',
22
+ font: 'inherit',
23
+ fontSize: '13px',
24
+ outline: 'none',
25
+ minWidth: 0,
26
+ };
5
27
  export function InlineCellEditor(props) {
6
28
  const { value, column, editorType, onCommit, onCancel } = props;
7
29
  const wrapperRef = React.useRef(null);
8
30
  const { localValue, setLocalValue, handleKeyDown, handleBlur, commit, cancel } = useInlineCellEditorState({ value, editorType, onCommit, onCancel });
31
+ const richSelectValues = column.cellEditorParams?.values ?? [];
32
+ const richSelectFormatValue = column.cellEditorParams?.formatValue;
33
+ const richSelect = useRichSelectState({
34
+ values: richSelectValues,
35
+ formatValue: richSelectFormatValue,
36
+ initialValue: value,
37
+ onCommit,
38
+ onCancel,
39
+ });
9
40
  React.useEffect(() => {
10
41
  const input = wrapperRef.current?.querySelector('input');
11
42
  input?.focus();
12
43
  }, []);
44
+ if (editorType === 'richSelect') {
45
+ return (_jsxs("div", { ref: wrapperRef, style: { ...editorWrapperStyle, position: 'relative' }, children: [_jsx("input", { type: "text", value: richSelect.searchText, onChange: (e) => richSelect.setSearchText(e.target.value), onKeyDown: richSelect.handleKeyDown, placeholder: "Search...", autoFocus: true, style: editorInputStyle }), _jsxs("div", { style: {
46
+ position: 'absolute',
47
+ top: '100%',
48
+ left: 0,
49
+ right: 0,
50
+ maxHeight: 200,
51
+ overflowY: 'auto',
52
+ background: 'var(--ogrid-bg, #fff)',
53
+ border: '1px solid var(--ogrid-border, #ccc)',
54
+ zIndex: 10,
55
+ boxShadow: '0 4px 16px rgba(0,0,0,0.2)',
56
+ }, role: "listbox", children: [richSelect.filteredValues.map((v, i) => (_jsx("div", { role: "option", "aria-selected": i === richSelect.highlightedIndex, onClick: () => richSelect.selectValue(v), style: {
57
+ padding: '6px 8px',
58
+ cursor: 'pointer',
59
+ color: 'var(--ogrid-fg, #242424)',
60
+ background: i === richSelect.highlightedIndex ? 'var(--ogrid-bg-hover, #e8f0fe)' : undefined,
61
+ }, children: richSelect.getDisplayText(v) }, String(v)))), richSelect.filteredValues.length === 0 && (_jsx("div", { style: { padding: '6px 8px', color: 'var(--ogrid-muted, #999)' }, children: "No matches" }))] })] }));
62
+ }
13
63
  if (editorType === 'checkbox') {
14
64
  const checked = value === true;
15
65
  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
66
  }
17
67
  if (editorType === 'select') {
18
68
  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)))) }));
69
+ return (_jsx("div", { style: editorWrapperStyle, children: _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, style: {
70
+ width: '100%',
71
+ padding: 0,
72
+ border: 'none',
73
+ background: 'transparent',
74
+ color: 'inherit',
75
+ font: 'inherit',
76
+ fontSize: '13px',
77
+ cursor: 'pointer',
78
+ outline: 'none',
79
+ }, children: values.map((v) => (_jsx("option", { value: String(v), children: String(v) }, String(v)))) }) }));
80
+ }
81
+ if (editorType === 'date') {
82
+ return (_jsx("div", { ref: wrapperRef, style: editorWrapperStyle, children: _jsx("input", { type: "date", value: localValue, onChange: (e) => setLocalValue(e.target.value), onBlur: handleBlur, onKeyDown: handleKeyDown, style: editorInputStyle, autoFocus: true }) }));
20
83
  }
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 }) }));
84
+ return (_jsx("div", { ref: wrapperRef, style: editorWrapperStyle, children: _jsx("input", { type: "text", value: localValue, onChange: (e) => setLocalValue(e.target.value), onBlur: handleBlur, onKeyDown: handleKeyDown, style: editorInputStyle, autoFocus: true }) }));
22
85
  }
@@ -6,11 +6,11 @@ import { ColumnChooser } from '../ColumnChooser/ColumnChooser';
6
6
  import { PaginationControls } from '../PaginationControls/PaginationControls';
7
7
  import { useOGrid, OGridLayout, } from '@alaarab/ogrid-core';
8
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) => {
9
+ const { dataGridProps, page, pageSize, displayTotalCount, setPage, setPageSize, columnChooserColumns, visibleColumns, handleVisibilityChange, columnChooserPlacement, title, toolbar, className, entityLabelPlural, pageSizeOptions, sideBarProps, } = useOGrid(props, ref);
10
+ return (_jsx(OGridLayout, { className: className, gap: 8, sideBar: sideBarProps, title: title, toolbar: toolbar, toolbarEnd: columnChooserPlacement === 'toolbar' ? (_jsx(ColumnChooser, { columns: columnChooserColumns, visibleColumns: visibleColumns, onVisibilityChange: handleVisibilityChange })) : undefined, pagination: _jsx(PaginationControls, { currentPage: page, pageSize: pageSize, totalCount: displayTotalCount, onPageChange: setPage, onPageSizeChange: (size) => {
11
11
  setPageSize(size);
12
12
  setPage(1);
13
- }, entityLabelPlural: entityLabelPlural }), children: _jsx(DataGridTable, { ...dataGridProps }) }));
13
+ }, pageSizeOptions: pageSizeOptions, entityLabelPlural: entityLabelPlural }), children: _jsx(DataGridTable, { ...dataGridProps }) }));
14
14
  });
15
15
  OGridInner.displayName = 'OGrid';
16
16
  export const OGrid = React.memo(OGridInner);
@@ -16,9 +16,9 @@ function ChevronDoubleRight() {
16
16
  return _jsx("span", { "aria-hidden": true, children: "\u00BB" });
17
17
  }
18
18
  export const PaginationControls = React.memo((props) => {
19
- const { currentPage, pageSize, totalCount, onPageChange, onPageSizeChange, entityLabelPlural, className } = props;
19
+ const { currentPage, pageSize, totalCount, onPageChange, onPageSizeChange, pageSizeOptions, entityLabelPlural, className } = props;
20
20
  const labelPlural = entityLabelPlural ?? 'items';
21
- const vm = useMemo(() => getPaginationViewModel(currentPage, pageSize, totalCount), [currentPage, pageSize, totalCount]);
21
+ const vm = useMemo(() => getPaginationViewModel(currentPage, pageSize, totalCount, pageSizeOptions ? { pageSizeOptions } : undefined), [currentPage, pageSize, totalCount, pageSizeOptions]);
22
22
  const handlePageSizeChange = useCallback((e) => {
23
23
  onPageSizeChange(Number(e.target.value));
24
24
  }, [onPageSizeChange]);
@@ -7,9 +7,7 @@
7
7
  width: 100%;
8
8
  min-width: 0;
9
9
  box-sizing: border-box;
10
- padding: 14px 0;
11
- margin-top: 16px;
12
- border-top: 1px solid var(--ogrid-border, #e5e5e5);
10
+ padding: 0;
13
11
  }
14
12
 
15
13
  .paginationInfo {
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import type { UserLike, ColumnFilterType } from '@alaarab/ogrid-core';
2
+ import type { UserLike, ColumnFilterType, IDateFilterValue } from '@alaarab/ogrid-core';
3
3
  export interface IColumnHeaderFilterProps {
4
4
  columnKey: string;
5
5
  columnName: string;
@@ -16,5 +16,7 @@ export interface IColumnHeaderFilterProps {
16
16
  selectedUser?: UserLike;
17
17
  onUserChange?: (user: UserLike | undefined) => void;
18
18
  peopleSearch?: (query: string) => Promise<UserLike[]>;
19
+ dateValue?: IDateFilterValue;
20
+ onDateChange?: (value: IDateFilterValue | undefined) => void;
19
21
  }
20
22
  export declare const ColumnHeaderFilter: React.FC<IColumnHeaderFilterProps>;
@@ -5,7 +5,7 @@ export interface InlineCellEditorProps<T> {
5
5
  item: T;
6
6
  column: IColumnDef<T>;
7
7
  rowIndex: number;
8
- editorType: 'text' | 'select' | 'checkbox';
8
+ editorType: 'text' | 'select' | 'checkbox' | 'richSelect' | 'date';
9
9
  onCommit: (value: unknown) => void;
10
10
  onCancel: () => void;
11
11
  }
@@ -4,5 +4,13 @@ export interface StatusBarProps {
4
4
  filteredCount?: number;
5
5
  selectedCount?: number;
6
6
  selectedCellCount?: number;
7
+ aggregation?: {
8
+ sum: number;
9
+ avg: number;
10
+ min: number;
11
+ max: number;
12
+ count: number;
13
+ } | null;
14
+ suppressRowCount?: boolean;
7
15
  }
8
16
  export declare function StatusBar(props: StatusBarProps): React.ReactElement;
@@ -5,6 +5,7 @@ export interface IPaginationControlsProps {
5
5
  totalCount: number;
6
6
  onPageChange: (page: number) => void;
7
7
  onPageSizeChange: (pageSize: number) => void;
8
+ pageSizeOptions?: number[];
8
9
  entityLabelPlural?: string;
9
10
  className?: string;
10
11
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alaarab/ogrid",
3
- "version": "1.3.2",
3
+ "version": "1.5.0",
4
4
  "description": "OGrid default (Radix) – Data grid with sorting, filtering, pagination, column chooser, and CSV export. Packed with Radix UI; no Fluent or Material required.",
5
5
  "main": "dist/esm/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -41,7 +41,7 @@
41
41
  "node": ">=18"
42
42
  },
43
43
  "dependencies": {
44
- "@alaarab/ogrid-core": "^1.2.0",
44
+ "@alaarab/ogrid-core": "^1.5.0",
45
45
  "@radix-ui/react-checkbox": "^1.1.2",
46
46
  "@radix-ui/react-popover": "^1.1.2"
47
47
  },
@@ -55,8 +55,7 @@
55
55
  "identity-obj-proxy": "^3.0.0",
56
56
  "ts-jest": "^29.2.5",
57
57
  "sass": "^1.83.4",
58
- "@storybook/react": "^8.5.3",
59
- "@storybook/react-vite": "^8.5.3",
58
+ "@storybook/react-vite": "10.2.8",
60
59
  "@types/react": "^18.3.18",
61
60
  "@types/react-dom": "^18.3.5",
62
61
  "jest": "^29.7.0",
@@ -64,9 +63,10 @@
64
63
  "react": "^18.3.1",
65
64
  "react-dom": "^18.3.1",
66
65
  "rimraf": "^6.0.1",
67
- "storybook": "^8.5.3",
66
+ "storybook": "10.2.8",
68
67
  "typescript": "^5.7.3",
69
- "vite": "^6.1.0"
68
+ "vite": "^7.0.0",
69
+ "eslint-plugin-storybook": "10.2.8"
70
70
  },
71
71
  "publishConfig": {
72
72
  "access": "public"