@alaarab/ogrid-react-material 2.0.0-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/README.md +87 -0
  2. package/dist/esm/ColumnChooser/ColumnChooser.js +53 -0
  3. package/dist/esm/ColumnHeaderFilter/ColumnHeaderFilter.js +60 -0
  4. package/dist/esm/ColumnHeaderFilter/MultiSelectFilterPopover.js +9 -0
  5. package/dist/esm/ColumnHeaderFilter/PeopleFilterPopover.js +17 -0
  6. package/dist/esm/ColumnHeaderFilter/TextFilterPopover.js +15 -0
  7. package/dist/esm/ColumnHeaderFilter/index.js +1 -0
  8. package/dist/esm/DataGridTable/DataGridTable.js +243 -0
  9. package/dist/esm/DataGridTable/GridContextMenu.js +18 -0
  10. package/dist/esm/DataGridTable/InlineCellEditor.js +6 -0
  11. package/dist/esm/DataGridTable/StatusBar.js +13 -0
  12. package/dist/esm/MaterialDataTable/MaterialDataTable.js +19 -0
  13. package/dist/esm/MaterialDataTable/index.js +1 -0
  14. package/dist/esm/PaginationControls/PaginationControls.js +29 -0
  15. package/dist/esm/index.js +8 -0
  16. package/dist/types/ColumnChooser/ColumnChooser.d.ts +10 -0
  17. package/dist/types/ColumnHeaderFilter/ColumnHeaderFilter.d.ts +22 -0
  18. package/dist/types/ColumnHeaderFilter/MultiSelectFilterPopover.d.ts +14 -0
  19. package/dist/types/ColumnHeaderFilter/PeopleFilterPopover.d.ts +13 -0
  20. package/dist/types/ColumnHeaderFilter/TextFilterPopover.d.ts +8 -0
  21. package/dist/types/ColumnHeaderFilter/index.d.ts +1 -0
  22. package/dist/types/DataGridTable/DataGridTable.d.ts +5 -0
  23. package/dist/types/DataGridTable/GridContextMenu.d.ts +10 -0
  24. package/dist/types/DataGridTable/InlineCellEditor.d.ts +12 -0
  25. package/dist/types/DataGridTable/StatusBar.d.ts +16 -0
  26. package/dist/types/MaterialDataTable/MaterialDataTable.d.ts +7 -0
  27. package/dist/types/MaterialDataTable/index.d.ts +1 -0
  28. package/dist/types/PaginationControls/PaginationControls.d.ts +12 -0
  29. package/dist/types/index.d.ts +6 -0
  30. package/package.json +64 -0
package/README.md ADDED
@@ -0,0 +1,87 @@
1
+ <p align="center">
2
+ <strong>OGrid for Material UI</strong> — The lightweight React data grid with enterprise features and zero enterprise cost.
3
+ </p>
4
+
5
+ <p align="center">
6
+ <a href="https://www.npmjs.com/package/@alaarab/ogrid-react-material"><img src="https://img.shields.io/npm/v/@alaarab/ogrid-react-material?color=%23217346&label=npm" alt="npm version" /></a>
7
+ <a href="https://github.com/alaarab/ogrid/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="MIT License" /></a>
8
+ <img src="https://img.shields.io/badge/React-17%20%7C%2018%20%7C%2019-blue" alt="React 17, 18, 19" />
9
+ <img src="https://img.shields.io/badge/TypeScript-strict-blue" alt="TypeScript strict" />
10
+ </p>
11
+
12
+ <p align="center">
13
+ <a href="https://alaarab.github.io/ogrid/">Documentation</a> · <a href="https://alaarab.github.io/ogrid/docs/getting-started/overview">Getting Started</a> · <a href="https://alaarab.github.io/ogrid/docs/api/ogrid-props">API Reference</a>
14
+ </p>
15
+
16
+ ---
17
+
18
+ OGrid data grid for **[Material UI v7](https://mui.com/)**, built on MUI Table. Also available for [Radix UI](https://www.npmjs.com/package/@alaarab/ogrid-react-radix) (default, lightweight) and [Fluent UI](https://www.npmjs.com/package/@alaarab/ogrid-react-fluent). Same API, just swap the import.
19
+
20
+ ## Why OGrid?
21
+
22
+ | | OGrid | AG Grid Community | AG Grid Enterprise |
23
+ |---|---|---|---|
24
+ | Spreadsheet selection | Built-in | - | $999/dev/year |
25
+ | Clipboard (copy/paste) | Built-in | - | $999/dev/year |
26
+ | Fill handle | Built-in | - | $999/dev/year |
27
+ | Undo/redo | Built-in | - | $999/dev/year |
28
+ | Context menu | Built-in | - | $999/dev/year |
29
+ | Status bar | Built-in | - | $999/dev/year |
30
+ | Side bar | Built-in | - | $999/dev/year |
31
+ | Cell editing | Built-in | Built-in | Built-in |
32
+ | Sorting & filtering | Built-in | Built-in | Built-in |
33
+ | **License** | **MIT (free)** | MIT | Commercial |
34
+
35
+ ## Features
36
+
37
+ Sorting · Filtering (text, multi-select, date range, people picker) · Pagination · Cell editing (inline, select, checkbox, rich select, date, custom popover) · Spreadsheet selection · Clipboard · Fill handle · Undo/redo · Row selection · Column groups · Column pinning · Column resize · Column chooser · Side bar · Context menu · Status bar with aggregations · CSV export · Grid API · Server-side data · Column state persistence · Keyboard navigation (Excel-style Ctrl+Arrow) · Built-in column types (text, numeric, date, boolean) · React 17/18/19 · TypeScript strict
38
+
39
+ ## Install
40
+
41
+ ```bash
42
+ npm install @alaarab/ogrid-react-material
43
+ ```
44
+
45
+ ### Peer Dependencies
46
+
47
+ ```
48
+ @mui/material ^7.0.0
49
+ @mui/icons-material ^7.0.0
50
+ @emotion/react ^11.0.0
51
+ @emotion/styled ^11.0.0
52
+ react ^17.0.0 || ^18.0.0 || ^19.0.0
53
+ react-dom ^17.0.0 || ^18.0.0 || ^19.0.0
54
+ ```
55
+
56
+ ## Quick Start
57
+
58
+ ```tsx
59
+ import { OGrid, type IColumnDef } from '@alaarab/ogrid-react-material';
60
+
61
+ const columns: IColumnDef<Employee>[] = [
62
+ { columnId: 'name', name: 'Name', sortable: true, editable: true },
63
+ { columnId: 'department', name: 'Department',
64
+ filterable: { type: 'multiSelect' },
65
+ cellEditor: 'richSelect', cellEditorParams: { values: ['Engineering', 'Sales', 'Marketing'] } },
66
+ { columnId: 'salary', name: 'Salary', type: 'numeric', editable: true,
67
+ valueFormatter: (v) => `$${Number(v).toLocaleString()}` },
68
+ ];
69
+
70
+ <OGrid
71
+ columns={columns}
72
+ data={employees}
73
+ getRowId={(e) => e.id}
74
+ editable
75
+ cellSelection
76
+ statusBar
77
+ sideBar
78
+ />
79
+ ```
80
+
81
+ ## Documentation
82
+
83
+ Full docs, API reference, and interactive examples at **[alaarab.github.io/ogrid](https://alaarab.github.io/ogrid/)**.
84
+
85
+ ## License
86
+
87
+ MIT — Free forever. No enterprise tiers. No feature paywalls.
@@ -0,0 +1,53 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useRef } from 'react';
3
+ import { Button, Popover, Checkbox, Box, Typography, FormControlLabel, } from '@mui/material';
4
+ import { ViewColumn as ViewColumnIcon, ExpandMore as ExpandMoreIcon, ExpandLess as ExpandLessIcon, } from '@mui/icons-material';
5
+ import { useColumnChooserState } from '@alaarab/ogrid-react';
6
+ export const ColumnChooser = (props) => {
7
+ const { columns, visibleColumns, onVisibilityChange, className } = props;
8
+ const [anchorEl, setAnchorEl] = useState(null);
9
+ const buttonRef = useRef(null);
10
+ const { open: isOpen, setOpen, handleClose, handleCheckboxChange: setColumnVisible, handleSelectAll, handleClearAll, visibleCount, totalCount, } = useColumnChooserState({ columns, visibleColumns, onVisibilityChange });
11
+ const handleToggle = (e) => {
12
+ if (isOpen) {
13
+ handleClose();
14
+ setAnchorEl(null);
15
+ }
16
+ else {
17
+ setAnchorEl(e.currentTarget);
18
+ setOpen(true);
19
+ }
20
+ };
21
+ const handlePopoverClose = () => {
22
+ handleClose();
23
+ setAnchorEl(null);
24
+ };
25
+ const handleCheckboxChange = (columnKey) => (ev) => {
26
+ ev.stopPropagation();
27
+ setColumnVisible(columnKey)(ev.target.checked);
28
+ };
29
+ return (_jsxs(Box, { className: className, sx: { display: 'inline-flex' }, children: [_jsxs(Button, { ref: buttonRef, variant: "outlined", size: "small", startIcon: _jsx(ViewColumnIcon, {}), endIcon: isOpen ? _jsx(ExpandLessIcon, {}) : _jsx(ExpandMoreIcon, {}), onClick: handleToggle, "aria-expanded": isOpen, "aria-haspopup": "listbox", sx: {
30
+ textTransform: 'none',
31
+ fontWeight: 600,
32
+ borderColor: isOpen ? 'primary.main' : 'divider',
33
+ }, children: ["Column Visibility (", visibleCount, " of ", totalCount, ")"] }), _jsxs(Popover, { open: isOpen, anchorEl: anchorEl, onClose: handlePopoverClose, anchorOrigin: { vertical: 'bottom', horizontal: 'right' }, transformOrigin: { vertical: 'top', horizontal: 'right' }, slotProps: {
34
+ paper: {
35
+ sx: { mt: 0.5, minWidth: 220 },
36
+ },
37
+ }, children: [_jsx(Box, { sx: {
38
+ px: 1.5,
39
+ py: 1,
40
+ borderBottom: 1,
41
+ borderColor: 'divider',
42
+ bgcolor: 'grey.50',
43
+ }, children: _jsxs(Typography, { variant: "subtitle2", fontWeight: 600, children: ["Select Columns (", visibleCount, " of ", totalCount, ")"] }) }), _jsx(Box, { sx: { maxHeight: 320, overflowY: 'auto', py: 0.5 }, children: columns.map((column) => (_jsx(Box, { sx: { px: 1.5, minHeight: 32, display: 'flex', alignItems: 'center' }, children: _jsx(FormControlLabel, { control: _jsx(Checkbox, { size: "small", checked: visibleColumns.has(column.columnId), onChange: handleCheckboxChange(column.columnId) }), label: _jsx(Typography, { variant: "body2", children: column.name }), sx: { m: 0 } }) }, column.columnId))) }), _jsxs(Box, { sx: {
44
+ display: 'flex',
45
+ justifyContent: 'flex-end',
46
+ gap: 1,
47
+ px: 1.5,
48
+ py: 1,
49
+ borderTop: 1,
50
+ borderColor: 'divider',
51
+ bgcolor: 'grey.50',
52
+ }, children: [_jsx(Button, { size: "small", onClick: handleClearAll, sx: { textTransform: 'none' }, children: "Clear All" }), _jsx(Button, { size: "small", variant: "contained", onClick: handleSelectAll, sx: { textTransform: 'none' }, children: "Select All" })] })] })] }));
53
+ };
@@ -0,0 +1,60 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import * as React from 'react';
3
+ import { Popover, Tooltip, IconButton, Box, Typography } from '@mui/material';
4
+ import { ArrowUpward as ArrowUpwardIcon, ArrowDownward as ArrowDownwardIcon, SwapVert as SwapVertIcon, FilterList as FilterListIcon, } from '@mui/icons-material';
5
+ import { useColumnHeaderFilterState } from '@alaarab/ogrid-react';
6
+ import { TextFilterPopover } from './TextFilterPopover';
7
+ import { MultiSelectFilterPopover } from './MultiSelectFilterPopover';
8
+ import { PeopleFilterPopover } from './PeopleFilterPopover';
9
+ export const ColumnHeaderFilter = React.memo((props) => {
10
+ const { columnName, filterType, isSorted = false, isSortedDescending = false, onSort, selectedValues, onFilterChange, options = [], isLoadingOptions = false, textValue = '', onTextChange, selectedUser, onUserChange, peopleSearch, dateValue, onDateChange, } = props;
11
+ const state = useColumnHeaderFilterState({
12
+ filterType,
13
+ isSorted,
14
+ isSortedDescending,
15
+ onSort,
16
+ selectedValues,
17
+ onFilterChange,
18
+ options,
19
+ isLoadingOptions,
20
+ textValue,
21
+ onTextChange,
22
+ selectedUser,
23
+ onUserChange,
24
+ peopleSearch,
25
+ dateValue,
26
+ onDateChange,
27
+ });
28
+ const { headerRef, peopleInputRef, isFilterOpen, setFilterOpen, tempSelected, tempTextValue, setTempTextValue, searchText, setSearchText, filteredOptions, peopleSuggestions, isPeopleLoading, peopleSearchText, setPeopleSearchText, hasActiveFilter, popoverPosition, handlers, } = state;
29
+ const safeOptions = options ?? [];
30
+ const renderPopoverContent = () => {
31
+ if (filterType === 'multiSelect') {
32
+ return (_jsx(MultiSelectFilterPopover, { searchText: searchText, onSearchChange: setSearchText, options: safeOptions, filteredOptions: filteredOptions, selected: tempSelected, onOptionToggle: handlers.handleCheckboxChange, onSelectAll: handlers.handleSelectAll, onClearSelection: handlers.handleClearSelection, onApply: handlers.handleApplyMultiSelect, isLoading: isLoadingOptions }));
33
+ }
34
+ if (filterType === 'text') {
35
+ return (_jsx(TextFilterPopover, { value: tempTextValue, onValueChange: setTempTextValue, onApply: handlers.handleTextApply, onClear: handlers.handleTextClear }));
36
+ }
37
+ if (filterType === 'people') {
38
+ return (_jsx(PeopleFilterPopover, { selectedUser: selectedUser, searchText: peopleSearchText, onSearchChange: setPeopleSearchText, suggestions: peopleSuggestions, isLoading: isPeopleLoading, onUserSelect: handlers.handleUserSelect, onClearUser: handlers.handleClearUser, inputRef: peopleInputRef }));
39
+ }
40
+ if (filterType === 'date') {
41
+ return (_jsxs(Box, { sx: { p: 1.5, display: 'flex', flexDirection: 'column', gap: 1 }, children: [_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 1 }, children: [_jsx(Typography, { variant: "caption", sx: { minWidth: 36 }, children: "From:" }), _jsx("input", { type: "date", value: state.tempDateFrom, onChange: (e) => state.setTempDateFrom(e.target.value), style: { flex: 1, padding: '4px 6px' } })] }), _jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 1 }, children: [_jsx(Typography, { variant: "caption", sx: { minWidth: 36 }, children: "To:" }), _jsx("input", { type: "date", value: state.tempDateTo, onChange: (e) => state.setTempDateTo(e.target.value), style: { flex: 1, padding: '4px 6px' } })] }), _jsxs(Box, { sx: { display: 'flex', justifyContent: 'flex-end', gap: 1, mt: 0.5 }, children: [_jsx("button", { onClick: handlers.handleDateClear, disabled: !state.tempDateFrom && !state.tempDateTo, style: { padding: '4px 12px', cursor: 'pointer' }, children: "Clear" }), _jsx("button", { onClick: handlers.handleDateApply, style: { padding: '4px 12px', cursor: 'pointer' }, children: "Apply" })] })] }));
42
+ }
43
+ return null;
44
+ };
45
+ return (_jsxs(Box, { ref: headerRef, sx: { display: 'flex', alignItems: 'center', width: '100%', minWidth: 0 }, children: [_jsx(Box, { sx: { flex: 1, minWidth: 0, overflow: 'hidden' }, children: _jsx(Tooltip, { title: columnName, arrow: true, children: _jsx(Typography, { variant: "body2", fontWeight: 600, noWrap: true, "data-header-label": true, sx: { lineHeight: 1.4 }, children: columnName }) }) }), _jsxs(Box, { sx: { display: 'flex', alignItems: 'center', ml: 0.5, flexShrink: 0 }, children: [onSort && (_jsx(IconButton, { size: "small", onClick: handlers.handleSortClick, "aria-label": `Sort by ${columnName}`, title: isSorted ? (isSortedDescending ? 'Sorted descending' : 'Sorted ascending') : 'Sort', color: isSorted ? 'primary' : 'default', sx: { p: 0.25 }, children: isSorted ? (isSortedDescending ? (_jsx(ArrowDownwardIcon, { sx: { fontSize: 16 } })) : (_jsx(ArrowUpwardIcon, { sx: { fontSize: 16 } }))) : (_jsx(SwapVertIcon, { sx: { fontSize: 16 } })) })), filterType !== 'none' && (_jsxs(IconButton, { size: "small", onClick: handlers.handleFilterIconClick, "aria-label": `Filter ${columnName}`, title: `Filter ${columnName}`, color: hasActiveFilter || isFilterOpen ? 'primary' : 'default', sx: { p: 0.25, position: 'relative' }, children: [_jsx(FilterListIcon, { sx: { fontSize: 16 } }), hasActiveFilter && (_jsx(Box, { sx: {
46
+ position: 'absolute',
47
+ top: 2,
48
+ right: 2,
49
+ width: 6,
50
+ height: 6,
51
+ borderRadius: '50%',
52
+ bgcolor: 'primary.main',
53
+ } }))] }))] }), _jsxs(Popover, { open: isFilterOpen && filterType !== 'none', onClose: () => setFilterOpen(false), anchorReference: "anchorPosition", anchorPosition: popoverPosition ?? { top: 0, left: 0 }, anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, transformOrigin: { vertical: 'top', horizontal: 'left' }, slotProps: {
54
+ paper: {
55
+ sx: { mt: 0.5, overflow: 'visible' },
56
+ onClick: (e) => e.stopPropagation(),
57
+ },
58
+ }, children: [_jsx(Box, { sx: { borderBottom: 1, borderColor: 'divider', px: 1.5, py: 1 }, children: _jsxs(Typography, { variant: "subtitle2", children: ["Filter: ", columnName] }) }), renderPopoverContent()] })] }));
59
+ });
60
+ ColumnHeaderFilter.displayName = 'ColumnHeaderFilter';
@@ -0,0 +1,9 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { TextField, Checkbox, CircularProgress, Button, Box, Typography, InputAdornment, FormControlLabel, } from '@mui/material';
3
+ import { Search as SearchIcon } from '@mui/icons-material';
4
+ export const MultiSelectFilterPopover = ({ searchText, onSearchChange, options, filteredOptions, selected, onOptionToggle, onSelectAll, onClearSelection, onApply, isLoading, }) => (_jsxs(Box, { sx: { width: 280 }, children: [_jsxs(Box, { sx: { p: 1.5, pb: 0.5 }, children: [_jsx(TextField, { placeholder: "Search...", value: searchText, onChange: (e) => onSearchChange(e.target.value), onKeyDown: (e) => e.stopPropagation(), autoComplete: "off", size: "small", fullWidth: true, slotProps: {
5
+ input: {
6
+ startAdornment: (_jsx(InputAdornment, { position: "start", children: _jsx(SearchIcon, { fontSize: "small" }) })),
7
+ },
8
+ } }), _jsxs(Typography, { variant: "caption", color: "text.secondary", sx: { mt: 0.5, display: 'block' }, children: [filteredOptions.length, " of ", options.length, " options"] })] }), _jsxs(Box, { sx: { display: 'flex', justifyContent: 'space-between', px: 1.5, py: 0.5 }, children: [_jsxs(Button, { size: "small", onClick: onSelectAll, children: ["Select All (", filteredOptions.length, ")"] }), _jsx(Button, { size: "small", onClick: onClearSelection, children: "Clear" })] }), _jsx(Box, { sx: { maxHeight: 240, overflowY: 'auto', px: 0.5 }, children: isLoading ? (_jsx(Box, { sx: { display: 'flex', justifyContent: 'center', py: 2 }, children: _jsx(CircularProgress, { size: 24 }) })) : filteredOptions.length === 0 ? (_jsx(Typography, { variant: "body2", color: "text.secondary", sx: { py: 2, textAlign: 'center' }, children: "No options found" })) : (filteredOptions.map((option) => (_jsx(FormControlLabel, { control: _jsx(Checkbox, { size: "small", checked: selected.has(option), onChange: (e) => onOptionToggle(option, e.target.checked) }), label: _jsx(Typography, { variant: "body2", children: option }), sx: { display: 'flex', mx: 0, '& .MuiFormControlLabel-label': { flex: 1, minWidth: 0 } } }, option)))) }), _jsxs(Box, { sx: { display: 'flex', justifyContent: 'flex-end', gap: 1, p: 1.5, pt: 1, borderTop: 1, borderColor: 'divider' }, children: [_jsx(Button, { size: "small", onClick: onClearSelection, children: "Clear" }), _jsx(Button, { size: "small", variant: "contained", onClick: onApply, children: "Apply" })] })] }));
9
+ MultiSelectFilterPopover.displayName = 'MultiSelectFilterPopover';
@@ -0,0 +1,17 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { TextField, CircularProgress, Button, Box, Typography, InputAdornment, Avatar, IconButton, } from '@mui/material';
3
+ import { Search as SearchIcon, Clear as ClearIcon } from '@mui/icons-material';
4
+ export const PeopleFilterPopover = ({ selectedUser, searchText, onSearchChange, suggestions, isLoading, onUserSelect, onClearUser, inputRef, }) => (_jsxs(Box, { sx: { width: 300 }, children: [selectedUser && (_jsxs(Box, { sx: { p: 1.5, pb: 1, borderBottom: 1, borderColor: 'divider' }, children: [_jsx(Typography, { variant: "caption", color: "text.secondary", children: "Currently filtered by:" }), _jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 1, mt: 0.5 }, children: [_jsx(Avatar, { src: selectedUser.photo, alt: selectedUser.displayName, sx: { width: 32, height: 32 }, children: selectedUser.displayName?.[0] }), _jsxs(Box, { sx: { flex: 1, minWidth: 0 }, children: [_jsx(Typography, { variant: "body2", noWrap: true, children: selectedUser.displayName }), _jsx(Typography, { variant: "caption", color: "text.secondary", noWrap: true, children: selectedUser.email })] }), _jsx(IconButton, { size: "small", onClick: onClearUser, "aria-label": "Remove filter", children: _jsx(ClearIcon, { fontSize: "small" }) })] })] })), _jsx(Box, { sx: { p: 1.5, pb: 0.5 }, children: _jsx(TextField, { inputRef: inputRef, placeholder: "Search for a person...", value: searchText, onChange: (e) => onSearchChange(e.target.value), onKeyDown: (e) => e.stopPropagation(), autoComplete: "off", size: "small", fullWidth: true, slotProps: {
5
+ input: {
6
+ startAdornment: (_jsx(InputAdornment, { position: "start", children: _jsx(SearchIcon, { fontSize: "small" }) })),
7
+ },
8
+ } }) }), _jsx(Box, { sx: { maxHeight: 240, overflowY: 'auto' }, children: isLoading && searchText.trim() ? (_jsx(Box, { sx: { display: 'flex', justifyContent: 'center', py: 2 }, children: _jsx(CircularProgress, { size: 24 }) })) : suggestions.length === 0 && searchText.trim() ? (_jsx(Typography, { variant: "body2", color: "text.secondary", sx: { py: 2, textAlign: 'center' }, children: "No results found" })) : searchText.trim() ? (suggestions.map((user) => (_jsxs(Box, { onClick: () => onUserSelect(user), sx: {
9
+ display: 'flex',
10
+ alignItems: 'center',
11
+ gap: 1,
12
+ px: 1.5,
13
+ py: 1,
14
+ cursor: 'pointer',
15
+ '&:hover': { bgcolor: 'action.hover' },
16
+ }, children: [_jsx(Avatar, { src: user.photo, alt: user.displayName, sx: { width: 32, height: 32 }, children: user.displayName?.[0] }), _jsxs(Box, { sx: { flex: 1, minWidth: 0 }, children: [_jsx(Typography, { variant: "body2", noWrap: true, children: user.displayName }), _jsx(Typography, { variant: "caption", color: "text.secondary", noWrap: true, children: user.email })] })] }, user.id || user.email || user.displayName)))) : (_jsx(Typography, { variant: "body2", color: "text.secondary", sx: { py: 2, textAlign: 'center' }, children: "Type to search..." })) }), selectedUser && (_jsx(Box, { sx: { p: 1.5, pt: 1, borderTop: 1, borderColor: 'divider' }, children: _jsx(Button, { size: "small", fullWidth: true, onClick: onClearUser, children: "Clear Filter" }) }))] }));
17
+ PeopleFilterPopover.displayName = 'PeopleFilterPopover';
@@ -0,0 +1,15 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { TextField, Button, Box, InputAdornment } from '@mui/material';
3
+ import { Search as SearchIcon } from '@mui/icons-material';
4
+ export const TextFilterPopover = ({ value, onValueChange, onApply, onClear, }) => (_jsxs(Box, { sx: { width: 260 }, children: [_jsx(Box, { sx: { p: 1.5 }, children: _jsx(TextField, { placeholder: "Enter search term...", value: value, onChange: (e) => onValueChange(e.target.value), onKeyDown: (e) => {
5
+ e.stopPropagation();
6
+ if (e.key === 'Enter') {
7
+ e.preventDefault();
8
+ onApply();
9
+ }
10
+ }, autoComplete: "off", size: "small", fullWidth: true, slotProps: {
11
+ input: {
12
+ startAdornment: (_jsx(InputAdornment, { position: "start", children: _jsx(SearchIcon, { fontSize: "small" }) })),
13
+ },
14
+ } }) }), _jsxs(Box, { sx: { display: 'flex', justifyContent: 'flex-end', gap: 1, p: 1.5, pt: 0 }, children: [_jsx(Button, { size: "small", disabled: !value, onClick: onClear, children: "Clear" }), _jsx(Button, { size: "small", variant: "contained", onClick: onApply, children: "Apply" })] })] }));
15
+ TextFilterPopover.displayName = 'TextFilterPopover';
@@ -0,0 +1 @@
1
+ export { ColumnHeaderFilter } from './ColumnHeaderFilter';
@@ -0,0 +1,243 @@
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, useMemo } from 'react';
4
+ import { createPortal } from 'react-dom';
5
+ import { Box, CircularProgress, Typography, Button, Popover, Checkbox, Table, TableHead, TableBody, TableRow, TableCell, TableContainer, } from '@mui/material';
6
+ import { ColumnHeaderFilter } from '../ColumnHeaderFilter';
7
+ import { InlineCellEditor } from './InlineCellEditor';
8
+ import { StatusBar } from './StatusBar';
9
+ import { GridContextMenu } from './GridContextMenu';
10
+ import { useDataGridState, useColumnResize, useLatestRef, getHeaderFilterConfig, getCellRenderDescriptor, MarchingAntsOverlay, buildHeaderRows, resolveCellDisplayContent, resolveCellStyle, buildInlineEditorProps, buildPopoverEditorProps, getCellInteractionProps, areGridRowPropsEqual, CellErrorBoundary, CHECKBOX_COLUMN_WIDTH, DEFAULT_MIN_COLUMN_WIDTH, } from '@alaarab/ogrid-react';
11
+ // ── Module-scope stable styles (avoid per-render Emotion resolutions) ──
12
+ const gridRootSx = { position: 'relative', flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column' };
13
+ // Row
14
+ const ROW_HOVER_SX = { '&:hover': { bgcolor: 'action.hover' } };
15
+ // Checkbox column
16
+ const CHECKBOX_CELL_SX = { width: CHECKBOX_COLUMN_WIDTH, minWidth: CHECKBOX_COLUMN_WIDTH, maxWidth: CHECKBOX_COLUMN_WIDTH, textAlign: 'center' };
17
+ const CHECKBOX_WRAPPER_SX = { display: 'flex', alignItems: 'center', justifyContent: 'center' };
18
+ const CHECKBOX_PLACEHOLDER_SX = { width: CHECKBOX_COLUMN_WIDTH, minWidth: CHECKBOX_COLUMN_WIDTH, p: 0 };
19
+ // Header
20
+ const STICKY_HEADER_SX = { position: 'sticky', top: 0, zIndex: 8, bgcolor: 'action.hover', '& th': { bgcolor: 'action.hover' } };
21
+ const HEADER_ROW_SX = { bgcolor: 'action.hover' };
22
+ const GROUP_HEADER_CELL_SX = { textAlign: 'center', fontWeight: 600, borderBottom: 2, borderColor: 'divider', py: 0.75 };
23
+ // Cell content base variants (selected by column type + editability)
24
+ const CELL_CONTENT_BASE_SX = {
25
+ width: '100%', height: '100%', display: 'flex', alignItems: 'center', minWidth: 0,
26
+ px: '10px', py: '6px', boxSizing: 'border-box', overflow: 'hidden',
27
+ textOverflow: 'ellipsis', whiteSpace: 'nowrap', userSelect: 'none', outline: 'none',
28
+ };
29
+ const CELL_CONTENT_NUMERIC_SX = { ...CELL_CONTENT_BASE_SX, justifyContent: 'flex-end', textAlign: 'right' };
30
+ const CELL_CONTENT_BOOLEAN_SX = { ...CELL_CONTENT_BASE_SX, justifyContent: 'center', textAlign: 'center' };
31
+ const CELL_CONTENT_EDITABLE_SX = { ...CELL_CONTENT_BASE_SX, cursor: 'cell' };
32
+ const CELL_CONTENT_NUMERIC_EDITABLE_SX = { ...CELL_CONTENT_NUMERIC_SX, cursor: 'cell' };
33
+ const CELL_CONTENT_BOOLEAN_EDITABLE_SX = { ...CELL_CONTENT_BOOLEAN_SX, cursor: 'cell' };
34
+ // Cell overlay states (only applied to the few active/selected cells)
35
+ const CELL_ACTIVE_SX = { outline: '2px solid var(--ogrid-selection, #217346)', outlineOffset: '-1px', zIndex: 2, position: 'relative', overflow: 'visible' };
36
+ const CELL_IN_RANGE_SX = { bgcolor: 'var(--ogrid-bg-range, rgba(33, 115, 70, 0.12))' };
37
+ const CELL_CUT_RANGE_SX = { bgcolor: 'action.hover', opacity: 0.7 };
38
+ // Pre-computed overlay variant arrays (avoid per-cell array allocation + filter)
39
+ // Key: `${base}_${overlay}` where overlay is 'active' | 'range' | 'cut'
40
+ const OVERLAY_VARIANTS = {
41
+ base_active: [CELL_CONTENT_BASE_SX, CELL_ACTIVE_SX],
42
+ base_range: [CELL_CONTENT_BASE_SX, CELL_IN_RANGE_SX],
43
+ base_cut: [CELL_CONTENT_BASE_SX, CELL_IN_RANGE_SX, CELL_CUT_RANGE_SX],
44
+ editable_active: [CELL_CONTENT_EDITABLE_SX, CELL_ACTIVE_SX],
45
+ editable_range: [CELL_CONTENT_EDITABLE_SX, CELL_IN_RANGE_SX],
46
+ editable_cut: [CELL_CONTENT_EDITABLE_SX, CELL_IN_RANGE_SX, CELL_CUT_RANGE_SX],
47
+ numeric_active: [CELL_CONTENT_NUMERIC_SX, CELL_ACTIVE_SX],
48
+ numeric_range: [CELL_CONTENT_NUMERIC_SX, CELL_IN_RANGE_SX],
49
+ numeric_cut: [CELL_CONTENT_NUMERIC_SX, CELL_IN_RANGE_SX, CELL_CUT_RANGE_SX],
50
+ numeric_editable_active: [CELL_CONTENT_NUMERIC_EDITABLE_SX, CELL_ACTIVE_SX],
51
+ numeric_editable_range: [CELL_CONTENT_NUMERIC_EDITABLE_SX, CELL_IN_RANGE_SX],
52
+ numeric_editable_cut: [CELL_CONTENT_NUMERIC_EDITABLE_SX, CELL_IN_RANGE_SX, CELL_CUT_RANGE_SX],
53
+ boolean_active: [CELL_CONTENT_BOOLEAN_SX, CELL_ACTIVE_SX],
54
+ boolean_range: [CELL_CONTENT_BOOLEAN_SX, CELL_IN_RANGE_SX],
55
+ boolean_cut: [CELL_CONTENT_BOOLEAN_SX, CELL_IN_RANGE_SX, CELL_CUT_RANGE_SX],
56
+ boolean_editable_active: [CELL_CONTENT_BOOLEAN_EDITABLE_SX, CELL_ACTIVE_SX],
57
+ boolean_editable_range: [CELL_CONTENT_BOOLEAN_EDITABLE_SX, CELL_IN_RANGE_SX],
58
+ boolean_editable_cut: [CELL_CONTENT_BOOLEAN_EDITABLE_SX, CELL_IN_RANGE_SX, CELL_CUT_RANGE_SX],
59
+ };
60
+ /** Select pre-computed sx for a cell based on column type, editability, and overlay state. */
61
+ function getCellSx(colType, canEdit, isActive, isInRange, isInCutRange) {
62
+ // Determine base key
63
+ let baseKey;
64
+ if (colType === 'numeric')
65
+ baseKey = canEdit ? 'numeric_editable' : 'numeric';
66
+ else if (colType === 'boolean')
67
+ baseKey = canEdit ? 'boolean_editable' : 'boolean';
68
+ else
69
+ baseKey = canEdit ? 'editable' : 'base';
70
+ // Determine overlay
71
+ if (isInCutRange)
72
+ return OVERLAY_VARIANTS[`${baseKey}_cut`];
73
+ if (isInRange)
74
+ return OVERLAY_VARIANTS[`${baseKey}_range`];
75
+ if (isActive)
76
+ return OVERLAY_VARIANTS[`${baseKey}_active`];
77
+ // No overlay — return the base sx directly
78
+ if (colType === 'numeric')
79
+ return canEdit ? CELL_CONTENT_NUMERIC_EDITABLE_SX : CELL_CONTENT_NUMERIC_SX;
80
+ if (colType === 'boolean')
81
+ return canEdit ? CELL_CONTENT_BOOLEAN_EDITABLE_SX : CELL_CONTENT_BOOLEAN_SX;
82
+ return canEdit ? CELL_CONTENT_EDITABLE_SX : CELL_CONTENT_BASE_SX;
83
+ }
84
+ // Fill handle
85
+ const FILL_HANDLE_SX = {
86
+ position: 'absolute', right: -3, bottom: -3, width: 7, height: 7,
87
+ bgcolor: 'var(--ogrid-selection, #217346)', border: '1px solid var(--ogrid-bg, #fff)', borderRadius: '1px',
88
+ cursor: 'crosshair', pointerEvents: 'auto', zIndex: 3,
89
+ };
90
+ // Cell <td> positioning variants
91
+ const CELL_TD_BASE_SX = { position: 'relative', p: 0, height: '1px' };
92
+ const CELL_TD_PINNED_LEFT_SX = { ...CELL_TD_BASE_SX, position: 'sticky', left: 0, zIndex: 6, bgcolor: 'background.paper', willChange: 'transform' };
93
+ const CELL_TD_PINNED_RIGHT_SX = { ...CELL_TD_BASE_SX, position: 'sticky', right: 0, zIndex: 6, bgcolor: 'background.paper', willChange: 'transform' };
94
+ // Header cell positioning variants
95
+ const HEADER_BASE_SX = { fontWeight: 600, position: 'relative' };
96
+ const HEADER_PINNED_LEFT_SX = { ...HEADER_BASE_SX, position: 'sticky', left: 0, top: 0, zIndex: 9, bgcolor: 'action.hover', willChange: 'transform' };
97
+ const HEADER_PINNED_RIGHT_SX = { ...HEADER_BASE_SX, position: 'sticky', right: 0, top: 0, zIndex: 9, bgcolor: 'action.hover', willChange: 'transform' };
98
+ // Resize handle
99
+ const RESIZE_HANDLE_SX = {
100
+ position: 'absolute', top: 0, right: '-3px', bottom: 0, width: '8px',
101
+ cursor: 'col-resize', userSelect: 'none',
102
+ '&::after': { content: '""', position: 'absolute', top: 0, right: '3px', bottom: 0, width: '2px' },
103
+ '&:hover::after': { bgcolor: 'primary.main' },
104
+ '&:active::after': { bgcolor: 'primary.dark' },
105
+ };
106
+ // Popover
107
+ const POPOVER_ANCHOR_SX = { minHeight: '100%', minWidth: 40 };
108
+ const POPOVER_CONTENT_SX = { p: 1 };
109
+ // Wrapper
110
+ const WRAPPER_SCROLL_SX = { display: 'flex', flexDirection: 'column', minHeight: '100%' };
111
+ // Table wrapper
112
+ const TABLE_WRAPPER_SX = { position: 'relative', opacity: 1 };
113
+ const TABLE_WRAPPER_LOADING_SX = { position: 'relative', opacity: 0.6 };
114
+ // Empty state
115
+ const EMPTY_STATE_SX = { py: 4, px: 2, textAlign: 'center', borderTop: 1, borderColor: 'divider', bgcolor: 'action.hover' };
116
+ // Loading overlay
117
+ const LOADING_OVERLAY_SX = {
118
+ position: 'absolute', inset: 0, zIndex: 2,
119
+ display: 'flex', alignItems: 'center', justifyContent: 'center',
120
+ bgcolor: 'rgba(255,255,255,0.7)',
121
+ };
122
+ const LOADING_INNER_SX = {
123
+ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 1,
124
+ p: 2, bgcolor: 'background.paper', border: 1, borderColor: 'divider', borderRadius: 1,
125
+ };
126
+ // Module-scope event handlers
127
+ const STOP_PROPAGATION = (e) => e.stopPropagation();
128
+ const PREVENT_DEFAULT = (e) => { e.preventDefault(); };
129
+ const NOOP = () => { };
130
+ function GridRowInner(props) {
131
+ const { item, rowIndex, rowId, isSelected, columnLayouts, renderCellContent, handleSingleRowClick, handleRowCheckboxChange, lastMouseShiftRef, hasCheckboxCol, } = props;
132
+ return (_jsxs(TableRow, { selected: isSelected, "data-row-id": rowId, onClick: handleSingleRowClick, sx: ROW_HOVER_SX, children: [hasCheckboxCol && (_jsx(TableCell, { padding: "checkbox", sx: CHECKBOX_CELL_SX, children: _jsx(Box, { "data-row-index": rowIndex, "data-col-index": 0, onClick: STOP_PROPAGATION, sx: CHECKBOX_WRAPPER_SX, children: _jsx(Checkbox, { checked: isSelected, onChange: (_, checked) => handleRowCheckboxChange(rowId, checked, rowIndex, lastMouseShiftRef.current), size: "small", "aria-label": `Select row ${rowIndex + 1}` }) }) })), columnLayouts.map((cl, colIdx) => (_jsx(TableCell, { sx: cl.tdSx, style: { minWidth: cl.minWidth, width: cl.width, maxWidth: cl.maxWidth }, children: renderCellContent(item, cl.col, rowIndex, colIdx) }, cl.col.columnId)))] }));
133
+ }
134
+ const GridRow = React.memo(GridRowInner, areGridRowPropsEqual);
135
+ function DataGridTableInner(props) {
136
+ const wrapperRef = useRef(null);
137
+ const tableContainerRef = useRef(null);
138
+ const state = useDataGridState({ props, wrapperRef });
139
+ const lastMouseShiftRef = useRef(false);
140
+ const { layout, rowSelection: rowSel, editing, interaction, contextMenu: ctxMenu, viewModels } = state;
141
+ const { visibleCols, hasCheckboxCol, colOffset, containerWidth, minTableWidth, desiredTableWidth, columnSizingOverrides, setColumnSizingOverrides } = layout;
142
+ const { selectedRowIds, updateSelection, handleRowCheckboxChange, handleSelectAll, allSelected, someSelected } = rowSel;
143
+ const { editingCell, setEditingCell, pendingEditorValue, setPendingEditorValue, commitCellEdit, cancelPopoverEdit, popoverAnchorEl, setPopoverAnchorEl } = editing;
144
+ const { setActiveCell, handleCellMouseDown, handleSelectAllCells, selectionRange, hasCellSelection, handleGridKeyDown, handleFillHandleMouseDown, handleCopy, handleCut, handlePaste, cutRange, copyRange, canUndo, canRedo, onUndo, onRedo, isDragging } = interaction;
145
+ const handlePasteVoid = useCallback(() => { void handlePaste(); }, [handlePaste]);
146
+ const { menuPosition, handleCellContextMenu, closeContextMenu } = ctxMenu;
147
+ const { headerFilterInput, cellDescriptorInput, statusBarConfig, showEmptyInGrid, onCellError } = viewModels;
148
+ const { items, getRowId, emptyState, layoutMode = 'fill', rowSelection = 'none', freezeRows, freezeCols, suppressHorizontalScroll, isLoading = false, loadingMessage = 'Loading\u2026', 'aria-label': ariaLabel, 'aria-labelledby': ariaLabelledBy, } = props;
149
+ const fitToContent = layoutMode === 'content';
150
+ const allowOverflowX = !suppressHorizontalScroll && containerWidth > 0 && (minTableWidth > containerWidth || desiredTableWidth > containerWidth);
151
+ // Memoize header rows (recursive tree traversal)
152
+ const headerRows = useMemo(() => buildHeaderRows(props.columns, props.visibleColumns), [props.columns, props.visibleColumns]);
153
+ const { handleResizeStart, getColumnWidth } = useColumnResize({
154
+ columnSizingOverrides,
155
+ setColumnSizingOverrides,
156
+ });
157
+ // Pre-compute per-column layout (tdSx, widths) so GridRow doesn't recalculate per-cell
158
+ const columnLayouts = useMemo(() => visibleCols.map((col, colIdx) => {
159
+ const isFreezeCol = freezeCols != null && freezeCols >= 1 && colIdx < freezeCols;
160
+ const isPinnedLeft = col.pinned === 'left';
161
+ const isPinnedRight = col.pinned === 'right';
162
+ const columnWidth = getColumnWidth(col);
163
+ const tdSx = isPinnedLeft || (isFreezeCol && colIdx === 0) ? CELL_TD_PINNED_LEFT_SX : isPinnedRight ? CELL_TD_PINNED_RIGHT_SX : CELL_TD_BASE_SX;
164
+ return { col, tdSx, minWidth: col.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH, width: columnWidth, maxWidth: columnWidth };
165
+ }), [visibleCols, freezeCols, getColumnWidth]);
166
+ const editCallbacks = useMemo(() => ({ commitCellEdit, setEditingCell, setPendingEditorValue, cancelPopoverEdit }), [commitCellEdit, setEditingCell, setPendingEditorValue, cancelPopoverEdit]);
167
+ const interactionHandlers = useMemo(() => ({ handleCellMouseDown, setActiveCell, setEditingCell, handleCellContextMenu }), [handleCellMouseDown, setActiveCell, setEditingCell, handleCellContextMenu]);
168
+ // Refs for volatile state — lets renderCellContent be stable (same function ref across
169
+ // selection changes) so that GridRow's React.memo comparator can skip unaffected rows.
170
+ const cellDescriptorInputRef = useLatestRef(cellDescriptorInput);
171
+ const pendingEditorValueRef = useLatestRef(pendingEditorValue);
172
+ const popoverAnchorElRef = useLatestRef(popoverAnchorEl);
173
+ // Stable row-click handler
174
+ const selectedRowIdsRef = useLatestRef(selectedRowIds);
175
+ const handleSingleRowClick = useCallback((e) => {
176
+ if (rowSelection !== 'single')
177
+ return;
178
+ const rowId = e.currentTarget.dataset.rowId;
179
+ if (!rowId)
180
+ return;
181
+ const ids = selectedRowIdsRef.current;
182
+ updateSelection(ids.has(rowId) ? new Set() : new Set([rowId]));
183
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- selectedRowIdsRef is a stable ref
184
+ }, [rowSelection, updateSelection]);
185
+ // Wrapper sx (depends on dynamic values — memoize to avoid recreation)
186
+ const wrapperSx = useMemo(() => ({
187
+ position: 'relative',
188
+ flex: 1,
189
+ minHeight: 0,
190
+ width: fitToContent ? 'fit-content' : '100%',
191
+ maxWidth: '100%',
192
+ overflowX: suppressHorizontalScroll ? 'hidden' : allowOverflowX ? 'auto' : 'hidden',
193
+ overflowY: 'auto',
194
+ bgcolor: 'background.paper',
195
+ willChange: 'scroll-position',
196
+ '& [data-drag-range]': { bgcolor: 'rgba(33, 115, 70, 0.12) !important' },
197
+ }), [fitToContent, suppressHorizontalScroll, allowOverflowX]);
198
+ const renderCellContent = useCallback((item, col, rowIndex, colIdx) => {
199
+ const descriptor = getCellRenderDescriptor(item, col, rowIndex, colIdx, cellDescriptorInputRef.current);
200
+ const rowId = getRowId(item);
201
+ let cellContent;
202
+ if (descriptor.mode === 'editing-inline') {
203
+ cellContent = _jsx(InlineCellEditor, { ...buildInlineEditorProps(item, col, descriptor, editCallbacks) });
204
+ }
205
+ else if (descriptor.mode === 'editing-popover' && typeof col.cellEditor === 'function') {
206
+ const editorProps = buildPopoverEditorProps(item, col, descriptor, pendingEditorValueRef.current, editCallbacks);
207
+ const CustomEditor = col.cellEditor;
208
+ cellContent = (_jsxs(_Fragment, { children: [_jsx(Box, { ref: (el) => { if (el)
209
+ setPopoverAnchorEl(el); }, sx: POPOVER_ANCHOR_SX, "aria-hidden": true }), _jsx(Popover, { open: !!popoverAnchorElRef.current, anchorEl: popoverAnchorElRef.current, onClose: cancelPopoverEdit, anchorOrigin: { vertical: 'bottom', horizontal: 'left' }, transformOrigin: { vertical: 'top', horizontal: 'left' }, children: _jsx(Box, { sx: POPOVER_CONTENT_SX, children: _jsx(CustomEditor, { ...editorProps }) }) })] }));
210
+ }
211
+ else {
212
+ const content = resolveCellDisplayContent(col, item, descriptor.displayValue);
213
+ const cellStyle = resolveCellStyle(col, item);
214
+ const styledContent = cellStyle ? _jsx(Box, { component: "span", sx: cellStyle, children: content }) : content;
215
+ // Select pre-computed sx variant (module-scope = no per-cell allocation)
216
+ const cellSx = getCellSx(col.type, descriptor.canEditAny, descriptor.isActive && !descriptor.isInRange, descriptor.isInRange, descriptor.isInCutRange);
217
+ const interactionProps = getCellInteractionProps(descriptor, col.columnId, interactionHandlers);
218
+ cellContent = (_jsxs(Box, { component: "div", ...interactionProps, sx: cellSx, children: [styledContent, descriptor.canEditAny && descriptor.isSelectionEndCell && (_jsx(Box, { component: "div", onMouseDown: handleFillHandleMouseDown, "aria-label": "Fill handle", sx: FILL_HANDLE_SX }))] }));
219
+ }
220
+ return (_jsx(CellErrorBoundary, { onError: onCellError, children: cellContent }, `${rowId}-${col.columnId}`));
221
+ },
222
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- *Ref vars are stable refs from useLatestRef
223
+ [editCallbacks, interactionHandlers, handleFillHandleMouseDown, setPopoverAnchorEl, cancelPopoverEdit, getRowId, onCellError]);
224
+ return (_jsxs(Box, { sx: gridRootSx, children: [_jsxs(Box, { ref: wrapperRef, tabIndex: 0, role: "region", "aria-label": ariaLabel ?? (ariaLabelledBy ? undefined : 'Data grid'), "aria-labelledby": ariaLabelledBy, onMouseDown: (e) => { lastMouseShiftRef.current = e.shiftKey; }, onKeyDown: handleGridKeyDown, onContextMenu: PREVENT_DEFAULT, "data-overflow-x": allowOverflowX ? 'true' : 'false', sx: wrapperSx, children: [_jsx(Box, { sx: WRAPPER_SCROLL_SX, children: _jsx(TableContainer, { sx: { minWidth: allowOverflowX ? minTableWidth : undefined }, children: _jsxs(Box, { ref: tableContainerRef, sx: isLoading && items.length > 0 ? TABLE_WRAPPER_LOADING_SX : TABLE_WRAPPER_SX, children: [_jsxs(Table, { size: "small", sx: { overflow: 'hidden', minWidth: minTableWidth }, "data-freeze-rows": freezeRows != null && freezeRows >= 1 ? freezeRows : undefined, "data-freeze-cols": freezeCols != null && freezeCols >= 1 ? freezeCols : undefined, children: [_jsx(TableHead, { sx: STICKY_HEADER_SX, children: headerRows.map((row, rowIdx) => (_jsxs(TableRow, { sx: HEADER_ROW_SX, children: [rowIdx === headerRows.length - 1 && hasCheckboxCol && (_jsx(TableCell, { padding: "checkbox", rowSpan: headerRows.length > 1 ? 1 : undefined, sx: CHECKBOX_CELL_SX, children: _jsx(Checkbox, { checked: allSelected, indeterminate: someSelected, onChange: (_, c) => handleSelectAll(!!c), size: "small", "aria-label": "Select all rows" }) })), rowIdx === 0 && rowIdx < headerRows.length - 1 && hasCheckboxCol && (_jsx(TableCell, { rowSpan: headerRows.length - 1, sx: CHECKBOX_PLACEHOLDER_SX })), row.map((cell, cellIdx) => {
225
+ if (cell.isGroup) {
226
+ return (_jsx(TableCell, { colSpan: cell.colSpan, component: "th", scope: "colgroup", sx: GROUP_HEADER_CELL_SX, children: cell.label }, cellIdx));
227
+ }
228
+ // Leaf cell
229
+ const col = cell.columnDef;
230
+ const colIdx = visibleCols.indexOf(col);
231
+ const isFreezeCol = freezeCols != null && freezeCols >= 1 && colIdx < freezeCols;
232
+ const isPinnedLeft = col.pinned === 'left';
233
+ const isPinnedRight = col.pinned === 'right';
234
+ const columnWidth = getColumnWidth(col);
235
+ const headerSx = isPinnedLeft || (isFreezeCol && colIdx === 0) ? HEADER_PINNED_LEFT_SX : isPinnedRight ? HEADER_PINNED_RIGHT_SX : HEADER_BASE_SX;
236
+ return (_jsxs(TableCell, { component: "th", scope: "col", rowSpan: headerRows.length > 1 ? headerRows.length - rowIdx : undefined, sx: headerSx, style: { minWidth: col.minWidth ?? DEFAULT_MIN_COLUMN_WIDTH, width: columnWidth, maxWidth: columnWidth }, children: [_jsx(ColumnHeaderFilter, { ...getHeaderFilterConfig(col, headerFilterInput) }), _jsx(Box, { onMouseDown: (e) => handleResizeStart(e, col), sx: RESIZE_HANDLE_SX })] }, col.columnId));
237
+ })] }, rowIdx))) }), !showEmptyInGrid && (_jsx(TableBody, { children: items.map((item, rowIndex) => {
238
+ const rowIdStr = getRowId(item);
239
+ return (_jsx(GridRow, { item: item, rowIndex: rowIndex, rowId: rowIdStr, isSelected: selectedRowIds.has(rowIdStr), columnLayouts: columnLayouts, renderCellContent: renderCellContent, handleSingleRowClick: handleSingleRowClick, handleRowCheckboxChange: handleRowCheckboxChange, lastMouseShiftRef: lastMouseShiftRef, hasCheckboxCol: hasCheckboxCol, selectionRange: selectionRange, activeCell: interaction.activeCell, cutRange: cutRange, copyRange: copyRange, isDragging: isDragging, editingRowId: editingCell?.rowId ?? null }, rowIdStr));
240
+ }) }))] }), _jsx(MarchingAntsOverlay, { containerRef: tableContainerRef, selectionRange: selectionRange, copyRange: copyRange, cutRange: cutRange, colOffset: colOffset }), showEmptyInGrid && emptyState && (_jsx(Box, { sx: EMPTY_STATE_SX, children: emptyState.render ? (emptyState.render()) : (_jsxs(_Fragment, { children: [_jsx(Typography, { variant: "h6", gutterBottom: true, children: "No results found" }), _jsx(Typography, { variant: "body2", color: "text.secondary", children: emptyState.message != null ? (emptyState.message) : emptyState.hasActiveFilters ? (_jsxs(_Fragment, { children: ["No items match your current filters. Try adjusting your search or", ' ', _jsx(Button, { variant: "text", size: "small", onClick: emptyState.onClearAll, children: "clear all filters" }), ' ', "to see all items."] })) : ('There are no items available at this time.') })] })) }))] }) }) }), menuPosition &&
241
+ createPortal(_jsx(GridContextMenu, { x: menuPosition.x, y: menuPosition.y, hasSelection: hasCellSelection, canUndo: canUndo, canRedo: canRedo, onUndo: onUndo ?? NOOP, onRedo: onRedo ?? NOOP, onCopy: handleCopy, onCut: handleCut, onPaste: handlePasteVoid, onSelectAll: handleSelectAllCells, onClose: closeContextMenu }), document.body)] }), 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 })), isLoading && (_jsx(Box, { sx: LOADING_OVERLAY_SX, children: _jsxs(Box, { sx: LOADING_INNER_SX, children: [_jsx(CircularProgress, { size: 24 }), _jsx(Typography, { variant: "body2", color: "text.secondary", children: loadingMessage })] }) }))] }));
242
+ }
243
+ export const DataGridTable = React.memo(DataGridTableInner);
@@ -0,0 +1,18 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import * as React from 'react';
3
+ import { Menu, MenuItem, Divider } from '@mui/material';
4
+ import { GRID_CONTEXT_MENU_ITEMS, getContextMenuHandlers, formatShortcut } from '@alaarab/ogrid-react';
5
+ export function GridContextMenu(props) {
6
+ const { x, y, hasSelection, canUndo, canRedo, onClose } = props;
7
+ const handlers = React.useMemo(() => getContextMenuHandlers(props), [props]);
8
+ const isDisabled = React.useCallback((item) => {
9
+ if (item.disabledWhenNoSelection && !hasSelection)
10
+ return true;
11
+ if (item.id === 'undo' && !canUndo)
12
+ return true;
13
+ if (item.id === 'redo' && !canRedo)
14
+ return true;
15
+ return false;
16
+ }, [hasSelection, canUndo, canRedo]);
17
+ return (_jsx(Menu, { open: true, onClose: onClose, anchorReference: "anchorPosition", anchorPosition: { top: y, left: x }, MenuListProps: { dense: true, 'aria-label': 'Grid context menu' }, children: GRID_CONTEXT_MENU_ITEMS.map((item) => (_jsxs(React.Fragment, { children: [item.dividerBefore && _jsx(Divider, {}), _jsxs(MenuItem, { onClick: handlers[item.id], disabled: isDisabled(item), children: [_jsx("span", { style: { flex: 1 }, children: item.label }), item.shortcut && (_jsx("span", { style: { marginLeft: 24, color: 'rgba(0,0,0,0.4)', fontSize: '0.8em' }, children: formatShortcut(item.shortcut) }))] })] }, item.id))) }));
18
+ }
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Checkbox, Select, MenuItem } from '@mui/material';
3
+ import { BaseInlineCellEditor, editorWrapperStyle } from '@alaarab/ogrid-react';
4
+ export function InlineCellEditor(props) {
5
+ return (_jsx(BaseInlineCellEditor, { ...props, renderCheckbox: (checked, onCommit, onCancel) => (_jsx(Checkbox, { checked: checked, onChange: (_, c) => onCommit(c), onKeyDown: (e) => e.key === 'Escape' && (e.preventDefault(), onCancel()), size: "small" })), renderSelect: (value, values, onCommit, onCancel) => (_jsx("div", { style: editorWrapperStyle, children: _jsx(Select, { size: "small", value: value !== null && value !== undefined ? String(value) : '', onChange: (e) => onCommit(e.target.value), onKeyDown: (e) => e.key === 'Escape' && (e.preventDefault(), onCancel()), autoFocus: true, sx: { minWidth: 0, flex: 1 }, children: values.map((v) => (_jsx(MenuItem, { value: String(v), children: String(v) }, String(v)))) }) })) }));
6
+ }
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Typography } from '@mui/material';
3
+ import { getStatusBarParts } from '@alaarab/ogrid-react';
4
+ const partSx = (isLast) => ({
5
+ display: 'inline-flex',
6
+ alignItems: 'center',
7
+ gap: 0.5,
8
+ ...(isLast ? {} : { mr: 2, '&::after': { content: '"|"', ml: 2, color: 'divider' } }),
9
+ });
10
+ export function StatusBar(props) {
11
+ const parts = getStatusBarParts(props);
12
+ return (_jsx(Box, { role: "status", "aria-live": "polite", sx: { mt: 'auto', px: 1.5, py: 0.75, borderTop: 1, borderColor: 'divider', bgcolor: 'action.hover' }, children: parts.map((p, i) => (_jsxs(Typography, { component: "span", variant: "body2", sx: partSx(i === parts.length - 1), children: [_jsx(Typography, { component: "span", color: "text.secondary", children: p.label }), _jsx(Typography, { component: "span", fontWeight: 600, children: p.value.toLocaleString() })] }, p.key))) }));
13
+ }
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import * as React from 'react';
3
+ import { forwardRef } from 'react';
4
+ import { Box } from '@mui/material';
5
+ import { DataGridTable } from '../DataGridTable/DataGridTable';
6
+ import { ColumnChooser } from '../ColumnChooser/ColumnChooser';
7
+ import { PaginationControls } from '../PaginationControls/PaginationControls';
8
+ import { useOGrid, OGridLayout, } from '@alaarab/ogrid-react';
9
+ const OGridInner = forwardRef(function OGridInner(props, ref) {
10
+ const { dataGridProps, pagination, columnChooser, layout } = useOGrid(props, ref);
11
+ return (_jsx(OGridLayout, { containerComponent: Box, containerProps: { sx: { display: 'flex', flexDirection: 'column', gap: 1 } }, className: layout.className, sideBar: layout.sideBarProps, toolbar: layout.toolbar, toolbarBelow: layout.toolbarBelow, toolbarEnd: columnChooser.placement === 'toolbar' ? (_jsx(ColumnChooser, { columns: columnChooser.columns, visibleColumns: columnChooser.visibleColumns, onVisibilityChange: columnChooser.onVisibilityChange })) : undefined, pagination: _jsx(PaginationControls, { currentPage: pagination.page, pageSize: pagination.pageSize, totalCount: pagination.displayTotalCount, onPageChange: pagination.setPage, onPageSizeChange: (size) => {
12
+ pagination.setPageSize(size);
13
+ pagination.setPage(1);
14
+ }, pageSizeOptions: pagination.pageSizeOptions, entityLabelPlural: pagination.entityLabelPlural }), children: _jsx(DataGridTable, { ...dataGridProps }) }));
15
+ });
16
+ OGridInner.displayName = 'OGrid';
17
+ export const OGrid = React.memo(OGridInner);
18
+ /** @deprecated Use `OGrid` instead. Backward-compat alias. */
19
+ export const MaterialDataTable = OGrid;
@@ -0,0 +1 @@
1
+ export { OGrid, MaterialDataTable } from './MaterialDataTable';
@@ -0,0 +1,29 @@
1
+ import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import * as React from 'react';
3
+ import { useMemo, useCallback } from 'react';
4
+ import { IconButton, Button, Select, MenuItem, Box, Typography, } from '@mui/material';
5
+ import { FirstPage as FirstPageIcon, LastPage as LastPageIcon, ChevronLeft as ChevronLeftIcon, ChevronRight as ChevronRightIcon, } from '@mui/icons-material';
6
+ import { getPaginationViewModel } from '@alaarab/ogrid-react';
7
+ export const PaginationControls = React.memo((props) => {
8
+ const { currentPage, pageSize, totalCount, onPageChange, onPageSizeChange, pageSizeOptions, entityLabelPlural, className, } = props;
9
+ const labelPlural = entityLabelPlural ?? 'items';
10
+ const vm = useMemo(() => getPaginationViewModel(currentPage, pageSize, totalCount, pageSizeOptions ? { pageSizeOptions } : undefined), [currentPage, pageSize, totalCount, pageSizeOptions]);
11
+ const handlePageSizeChange = useCallback((event) => {
12
+ onPageSizeChange(Number(event.target.value));
13
+ }, [onPageSizeChange]);
14
+ if (!vm) {
15
+ return null;
16
+ }
17
+ const { pageNumbers, showStartEllipsis, showEndEllipsis, totalPages, startItem, endItem } = vm;
18
+ return (_jsxs(Box, { className: className, role: "navigation", "aria-label": "Pagination", sx: {
19
+ display: 'flex',
20
+ alignItems: 'center',
21
+ justifyContent: 'space-between',
22
+ flexWrap: 'wrap',
23
+ gap: 2,
24
+ px: 1.5,
25
+ width: '100%',
26
+ minWidth: 0,
27
+ boxSizing: 'border-box',
28
+ }, children: [_jsxs(Typography, { variant: "body2", color: "text.secondary", children: ["Showing ", startItem, " to ", endItem, " of ", totalCount.toLocaleString(), " ", labelPlural] }), _jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 0.5 }, children: [_jsx(IconButton, { size: "small", onClick: () => onPageChange(1), disabled: currentPage === 1, "aria-label": "First page", children: _jsx(FirstPageIcon, { fontSize: "small" }) }), _jsx(IconButton, { size: "small", onClick: () => onPageChange(currentPage - 1), disabled: currentPage === 1, "aria-label": "Previous page", children: _jsx(ChevronLeftIcon, { fontSize: "small" }) }), showStartEllipsis && (_jsxs(_Fragment, { children: [_jsx(Button, { variant: "outlined", size: "small", onClick: () => onPageChange(1), "aria-label": "Page 1", sx: { minWidth: 32, px: 0.5 }, children: "1" }), _jsx(Typography, { variant: "body2", color: "text.secondary", sx: { mx: 0.5 }, "aria-hidden": true, children: "\u2026" })] })), pageNumbers.map((pageNum) => (_jsx(Button, { variant: currentPage === pageNum ? 'contained' : 'outlined', size: "small", onClick: () => onPageChange(pageNum), "aria-label": `Page ${pageNum}`, "aria-current": currentPage === pageNum ? 'page' : undefined, sx: { minWidth: 32, px: 0.5 }, children: pageNum }, pageNum))), showEndEllipsis && (_jsxs(_Fragment, { children: [_jsx(Typography, { variant: "body2", color: "text.secondary", sx: { mx: 0.5 }, "aria-hidden": true, children: "\u2026" }), _jsx(Button, { variant: "outlined", size: "small", onClick: () => onPageChange(totalPages), "aria-label": `Page ${totalPages}`, sx: { minWidth: 32, px: 0.5 }, children: totalPages })] })), _jsx(IconButton, { size: "small", onClick: () => onPageChange(currentPage + 1), disabled: currentPage >= totalPages, "aria-label": "Next page", children: _jsx(ChevronRightIcon, { fontSize: "small" }) }), _jsx(IconButton, { size: "small", onClick: () => onPageChange(totalPages), disabled: currentPage >= totalPages, "aria-label": "Last page", children: _jsx(LastPageIcon, { fontSize: "small" }) })] }), _jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 1 }, children: [_jsx(Typography, { variant: "body2", color: "text.secondary", children: "Rows" }), _jsx(Select, { value: pageSize, onChange: handlePageSizeChange, size: "small", "aria-label": "Rows per page", sx: { minWidth: 70 }, children: vm.pageSizeOptions.map((n) => (_jsx(MenuItem, { value: n, children: n }, n))) })] })] }));
29
+ });
@@ -0,0 +1,8 @@
1
+ // Components
2
+ export { OGrid, MaterialDataTable } from './MaterialDataTable';
3
+ export { DataGridTable } from './DataGridTable/DataGridTable';
4
+ export { ColumnChooser } from './ColumnChooser/ColumnChooser';
5
+ export { ColumnHeaderFilter } from './ColumnHeaderFilter/ColumnHeaderFilter';
6
+ export { PaginationControls } from './PaginationControls/PaginationControls';
7
+ // Re-export everything from core
8
+ export * from '@alaarab/ogrid-react';
@@ -0,0 +1,10 @@
1
+ import * as React from 'react';
2
+ import type { IColumnDefinition } from '@alaarab/ogrid-react';
3
+ export type { IColumnDefinition };
4
+ export interface IColumnChooserProps {
5
+ columns: IColumnDefinition[];
6
+ visibleColumns: Set<string>;
7
+ onVisibilityChange: (columnKey: string, visible: boolean) => void;
8
+ className?: string;
9
+ }
10
+ export declare const ColumnChooser: React.FC<IColumnChooserProps>;
@@ -0,0 +1,22 @@
1
+ import * as React from 'react';
2
+ import type { UserLike, ColumnFilterType, IDateFilterValue } from '@alaarab/ogrid-react';
3
+ export interface IColumnHeaderFilterProps {
4
+ columnKey: string;
5
+ columnName: string;
6
+ filterType: ColumnFilterType;
7
+ isSorted?: boolean;
8
+ isSortedDescending?: boolean;
9
+ onSort?: () => void;
10
+ selectedValues?: string[];
11
+ onFilterChange?: (values: string[]) => void;
12
+ options?: string[];
13
+ isLoadingOptions?: boolean;
14
+ textValue?: string;
15
+ onTextChange?: (value: string) => void;
16
+ selectedUser?: UserLike;
17
+ onUserChange?: (user: UserLike | undefined) => void;
18
+ peopleSearch?: (query: string) => Promise<UserLike[]>;
19
+ dateValue?: IDateFilterValue;
20
+ onDateChange?: (value: IDateFilterValue | undefined) => void;
21
+ }
22
+ export declare const ColumnHeaderFilter: React.FC<IColumnHeaderFilterProps>;
@@ -0,0 +1,14 @@
1
+ import * as React from 'react';
2
+ export interface MultiSelectFilterPopoverProps {
3
+ searchText: string;
4
+ onSearchChange: (value: string) => void;
5
+ options: string[];
6
+ filteredOptions: string[];
7
+ selected: Set<string>;
8
+ onOptionToggle: (option: string, checked: boolean) => void;
9
+ onSelectAll: () => void;
10
+ onClearSelection: () => void;
11
+ onApply: () => void;
12
+ isLoading: boolean;
13
+ }
14
+ export declare const MultiSelectFilterPopover: React.FC<MultiSelectFilterPopoverProps>;
@@ -0,0 +1,13 @@
1
+ import * as React from 'react';
2
+ import type { UserLike } from '@alaarab/ogrid-react';
3
+ export interface PeopleFilterPopoverProps {
4
+ selectedUser: UserLike | undefined;
5
+ searchText: string;
6
+ onSearchChange: (value: string) => void;
7
+ suggestions: UserLike[];
8
+ isLoading: boolean;
9
+ onUserSelect: (user: UserLike) => void;
10
+ onClearUser: () => void;
11
+ inputRef?: React.RefObject<HTMLInputElement | null>;
12
+ }
13
+ export declare const PeopleFilterPopover: React.FC<PeopleFilterPopoverProps>;
@@ -0,0 +1,8 @@
1
+ import * as React from 'react';
2
+ export interface TextFilterPopoverProps {
3
+ value: string;
4
+ onValueChange: (value: string) => void;
5
+ onApply: () => void;
6
+ onClear: () => void;
7
+ }
8
+ export declare const TextFilterPopover: React.FC<TextFilterPopoverProps>;
@@ -0,0 +1 @@
1
+ export { ColumnHeaderFilter } from './ColumnHeaderFilter';
@@ -0,0 +1,5 @@
1
+ import * as React from 'react';
2
+ import type { IOGridDataGridProps } from '@alaarab/ogrid-react';
3
+ declare function DataGridTableInner<T>(props: IOGridDataGridProps<T>): React.ReactElement;
4
+ export declare const DataGridTable: typeof DataGridTableInner;
5
+ export {};
@@ -0,0 +1,10 @@
1
+ import * as React from 'react';
2
+ import type { GridContextMenuHandlerProps } from '@alaarab/ogrid-react';
3
+ export interface GridContextMenuProps extends GridContextMenuHandlerProps {
4
+ x: number;
5
+ y: number;
6
+ hasSelection: boolean;
7
+ canUndo: boolean;
8
+ canRedo: boolean;
9
+ }
10
+ export declare function GridContextMenu(props: GridContextMenuProps): React.ReactElement;
@@ -0,0 +1,12 @@
1
+ import * as React from 'react';
2
+ import type { IColumnDef } from '@alaarab/ogrid-react';
3
+ export interface InlineCellEditorProps<T> {
4
+ value: unknown;
5
+ item: T;
6
+ column: IColumnDef<T>;
7
+ rowIndex: number;
8
+ editorType: 'text' | 'select' | 'checkbox' | 'richSelect' | 'date';
9
+ onCommit: (value: unknown) => void;
10
+ onCancel: () => void;
11
+ }
12
+ export declare function InlineCellEditor<T>(props: InlineCellEditorProps<T>): React.ReactElement;
@@ -0,0 +1,16 @@
1
+ import * as React from 'react';
2
+ export interface StatusBarProps {
3
+ totalCount: number;
4
+ filteredCount?: number;
5
+ selectedCount?: number;
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;
15
+ }
16
+ export declare function StatusBar(props: StatusBarProps): React.ReactElement;
@@ -0,0 +1,7 @@
1
+ import * as React from 'react';
2
+ import { type IOGridProps, type IOGridApi } from '@alaarab/ogrid-react';
3
+ export type { IOGridProps } from '@alaarab/ogrid-react';
4
+ declare const OGridInner: React.ForwardRefExoticComponent<IOGridProps<unknown> & React.RefAttributes<IOGridApi<unknown>>>;
5
+ export declare const OGrid: typeof OGridInner;
6
+ /** @deprecated Use `OGrid` instead. Backward-compat alias. */
7
+ export declare const MaterialDataTable: React.ForwardRefExoticComponent<IOGridProps<unknown> & React.RefAttributes<IOGridApi<unknown>>>;
@@ -0,0 +1 @@
1
+ export { OGrid, MaterialDataTable, type IOGridProps } from './MaterialDataTable';
@@ -0,0 +1,12 @@
1
+ import * as React from 'react';
2
+ export interface IPaginationControlsProps {
3
+ currentPage: number;
4
+ pageSize: number;
5
+ totalCount: number;
6
+ onPageChange: (page: number) => void;
7
+ onPageSizeChange: (pageSize: number) => void;
8
+ pageSizeOptions?: number[];
9
+ entityLabelPlural?: string;
10
+ className?: string;
11
+ }
12
+ export declare const PaginationControls: React.FC<IPaginationControlsProps>;
@@ -0,0 +1,6 @@
1
+ export { OGrid, MaterialDataTable, type IOGridProps } from './MaterialDataTable';
2
+ export { DataGridTable } from './DataGridTable/DataGridTable';
3
+ export { ColumnChooser, type IColumnChooserProps } from './ColumnChooser/ColumnChooser';
4
+ export { ColumnHeaderFilter, type IColumnHeaderFilterProps } from './ColumnHeaderFilter/ColumnHeaderFilter';
5
+ export { PaginationControls, type IPaginationControlsProps } from './PaginationControls/PaginationControls';
6
+ export * from '@alaarab/ogrid-react';
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@alaarab/ogrid-react-material",
3
+ "version": "2.0.0-beta",
4
+ "description": "OGrid Material UI implementation – MUI Table–based data grid with sorting, filtering, pagination, column chooser, spreadsheet selection, and CSV export.",
5
+ "main": "dist/esm/index.js",
6
+ "module": "dist/esm/index.js",
7
+ "types": "dist/types/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/types/index.d.ts",
11
+ "import": "./dist/esm/index.js",
12
+ "require": "./dist/esm/index.js"
13
+ }
14
+ },
15
+ "scripts": {
16
+ "build": "rimraf dist && tsc -p tsconfig.build.json",
17
+ "test": "jest",
18
+ "storybook": "storybook dev -p 6007 --no-open",
19
+ "build-storybook": "storybook build"
20
+ },
21
+ "keywords": [
22
+ "ogrid",
23
+ "material-ui",
24
+ "mui",
25
+ "datatable",
26
+ "react",
27
+ "typescript",
28
+ "grid"
29
+ ],
30
+ "author": "Ala Arab",
31
+ "license": "MIT",
32
+ "files": [
33
+ "dist",
34
+ "README.md",
35
+ "LICENSE"
36
+ ],
37
+ "engines": {
38
+ "node": ">=18"
39
+ },
40
+ "dependencies": {
41
+ "@alaarab/ogrid-react": "2.0.0-beta"
42
+ },
43
+ "peerDependencies": {
44
+ "@emotion/react": "^11.0.0",
45
+ "@emotion/styled": "^11.0.0",
46
+ "@mui/icons-material": "^7.0.0",
47
+ "@mui/material": "^7.0.0",
48
+ "react": "^17.0.0 || ^18.0.0 || ^19.0.0",
49
+ "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
50
+ },
51
+ "devDependencies": {
52
+ "@emotion/react": "^11.14.0",
53
+ "@emotion/styled": "^11.14.0",
54
+ "@mui/icons-material": "^7.0.0",
55
+ "@mui/material": "^7.0.0",
56
+ "@storybook/react-vite": "10.2.8",
57
+ "storybook": "10.2.8",
58
+ "eslint-plugin-storybook": "10.2.8",
59
+ "vite": "^7.0.0"
60
+ },
61
+ "publishConfig": {
62
+ "access": "public"
63
+ }
64
+ }