@alaarab/ogrid-react-radix 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 (32) hide show
  1. package/README.md +78 -0
  2. package/dist/esm/ColumnChooser/ColumnChooser.js +22 -0
  3. package/dist/esm/ColumnChooser/ColumnChooser.module.css +140 -0
  4. package/dist/esm/ColumnHeaderFilter/ColumnHeaderFilter.js +55 -0
  5. package/dist/esm/ColumnHeaderFilter/ColumnHeaderFilter.module.css +332 -0
  6. package/dist/esm/ColumnHeaderFilter/MultiSelectFilterPopover.js +5 -0
  7. package/dist/esm/ColumnHeaderFilter/PeopleFilterPopover.js +19 -0
  8. package/dist/esm/ColumnHeaderFilter/TextFilterPopover.js +4 -0
  9. package/dist/esm/ColumnHeaderFilter/index.js +1 -0
  10. package/dist/esm/DataGridTable/DataGridTable.js +155 -0
  11. package/dist/esm/DataGridTable/DataGridTable.module.css +493 -0
  12. package/dist/esm/DataGridTable/GridContextMenu.js +35 -0
  13. package/dist/esm/DataGridTable/InlineCellEditor.js +9 -0
  14. package/dist/esm/DataGridTable/StatusBar.js +7 -0
  15. package/dist/esm/OGrid/OGrid.js +16 -0
  16. package/dist/esm/PaginationControls/PaginationControls.js +31 -0
  17. package/dist/esm/PaginationControls/PaginationControls.module.css +110 -0
  18. package/dist/esm/index.js +8 -0
  19. package/dist/types/ColumnChooser/ColumnChooser.d.ts +10 -0
  20. package/dist/types/ColumnHeaderFilter/ColumnHeaderFilter.d.ts +22 -0
  21. package/dist/types/ColumnHeaderFilter/MultiSelectFilterPopover.d.ts +14 -0
  22. package/dist/types/ColumnHeaderFilter/PeopleFilterPopover.d.ts +13 -0
  23. package/dist/types/ColumnHeaderFilter/TextFilterPopover.d.ts +8 -0
  24. package/dist/types/ColumnHeaderFilter/index.d.ts +1 -0
  25. package/dist/types/DataGridTable/DataGridTable.d.ts +5 -0
  26. package/dist/types/DataGridTable/GridContextMenu.d.ts +10 -0
  27. package/dist/types/DataGridTable/InlineCellEditor.d.ts +12 -0
  28. package/dist/types/DataGridTable/StatusBar.d.ts +16 -0
  29. package/dist/types/OGrid/OGrid.d.ts +5 -0
  30. package/dist/types/PaginationControls/PaginationControls.d.ts +12 -0
  31. package/dist/types/index.d.ts +6 -0
  32. package/package.json +62 -0
package/README.md ADDED
@@ -0,0 +1,78 @@
1
+ <p align="center">
2
+ <strong>OGrid</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-radix"><img src="https://img.shields.io/npm/v/@alaarab/ogrid-react-radix?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
+ This is the **default OGrid package** built with Radix UI primitives — lightweight, no Fluent/Material dependency. Also available for [Fluent UI](https://www.npmjs.com/package/@alaarab/ogrid-react-fluent) and [Material UI](https://www.npmjs.com/package/@alaarab/ogrid-react-material). 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-radix
43
+ ```
44
+
45
+ Radix UI primitives are bundled as regular dependencies — only `react` and `react-dom` are peer deps.
46
+
47
+ ## Quick Start
48
+
49
+ ```tsx
50
+ import { OGrid, type IColumnDef } from '@alaarab/ogrid-react-radix';
51
+
52
+ const columns: IColumnDef<Employee>[] = [
53
+ { columnId: 'name', name: 'Name', sortable: true, editable: true },
54
+ { columnId: 'department', name: 'Department',
55
+ filterable: { type: 'multiSelect' },
56
+ cellEditor: 'richSelect', cellEditorParams: { values: ['Engineering', 'Sales', 'Marketing'] } },
57
+ { columnId: 'salary', name: 'Salary', type: 'numeric', editable: true,
58
+ valueFormatter: (v) => `$${Number(v).toLocaleString()}` },
59
+ ];
60
+
61
+ <OGrid
62
+ columns={columns}
63
+ data={employees}
64
+ getRowId={(e) => e.id}
65
+ editable
66
+ cellSelection
67
+ statusBar
68
+ sideBar
69
+ />
70
+ ```
71
+
72
+ ## Documentation
73
+
74
+ Full docs, API reference, and interactive examples at **[alaarab.github.io/ogrid](https://alaarab.github.io/ogrid/)**.
75
+
76
+ ## License
77
+
78
+ MIT — Free forever. No enterprise tiers. No feature paywalls.
@@ -0,0 +1,22 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import * as Popover from '@radix-ui/react-popover';
3
+ import * as Checkbox from '@radix-ui/react-checkbox';
4
+ import { useColumnChooserState } from '@alaarab/ogrid-react';
5
+ import styles from './ColumnChooser.module.css';
6
+ function TableSettingsIcon() {
7
+ return (_jsx("span", { className: styles.buttonIcon, "aria-hidden": true, children: "\u2699" }));
8
+ }
9
+ function ChevronDown() {
10
+ return _jsx("span", { className: styles.chevron, "aria-hidden": true, children: "\u25BC" });
11
+ }
12
+ function ChevronUp() {
13
+ return _jsx("span", { className: styles.chevron, "aria-hidden": true, children: "\u25B2" });
14
+ }
15
+ export const ColumnChooser = (props) => {
16
+ const { columns, visibleColumns, onVisibilityChange, className } = props;
17
+ const { open, setOpen, handleCheckboxChange: setColumnVisible, handleSelectAll, handleClearAll, visibleCount, totalCount, } = useColumnChooserState({ columns, visibleColumns, onVisibilityChange });
18
+ const handleCheckboxChange = (columnKey) => (checked) => {
19
+ setColumnVisible(columnKey)(checked === true);
20
+ };
21
+ return (_jsx("div", { className: `${styles.container} ${className || ''}`, children: _jsxs(Popover.Root, { open: open, onOpenChange: setOpen, children: [_jsx(Popover.Trigger, { asChild: true, children: _jsxs("button", { type: "button", className: styles.triggerButton, "aria-expanded": open, "aria-haspopup": "listbox", children: [_jsx(TableSettingsIcon, {}), _jsxs("span", { children: ["Column Visibility (", visibleCount, " of ", totalCount, ")"] }), open ? _jsx(ChevronUp, {}) : _jsx(ChevronDown, {})] }) }), _jsx(Popover.Portal, { children: _jsxs(Popover.Content, { className: styles.dropdown, sideOffset: 4, align: "end", onOpenAutoFocus: (e) => e.preventDefault(), children: [_jsxs("div", { className: styles.header, children: ["Select Columns (", visibleCount, " of ", totalCount, ")"] }), _jsx("div", { className: styles.optionsList, children: columns.map((column) => (_jsxs("div", { className: styles.optionItem, children: [_jsx(Checkbox.Root, { id: `col-${column.columnId}`, checked: visibleColumns.has(column.columnId), onCheckedChange: handleCheckboxChange(column.columnId), disabled: column.required === true, className: styles.checkbox, children: _jsx(Checkbox.Indicator, { className: styles.checkboxIndicator, children: "\u2713" }) }), _jsx("label", { htmlFor: `col-${column.columnId}`, style: { marginLeft: 8, cursor: 'pointer' }, children: column.name })] }, column.columnId))) }), _jsxs("div", { className: styles.actions, children: [_jsx("button", { type: "button", className: styles.clearButton, onClick: handleClearAll, children: "Clear All" }), _jsx("button", { type: "button", className: styles.selectAllButton, onClick: handleSelectAll, children: "Select All" })] })] }) })] }) }));
22
+ };
@@ -0,0 +1,140 @@
1
+ .container {
2
+ position: relative;
3
+ display: inline-flex;
4
+ }
5
+
6
+ .triggerButton {
7
+ display: inline-flex;
8
+ align-items: center;
9
+ gap: 6px;
10
+ padding: 6px 12px;
11
+ border: 1px solid var(--ogrid-border, #ccc);
12
+ border-radius: 6px;
13
+ background: var(--ogrid-bg, #fff);
14
+ cursor: pointer;
15
+ font-size: 13px;
16
+ font-weight: 600;
17
+ color: var(--ogrid-fg, #333);
18
+ transition: background 0.15s, border-color 0.15s;
19
+ }
20
+ .triggerButton:hover {
21
+ background: var(--ogrid-bg-hover, #f5f5f5);
22
+ border-color: var(--ogrid-border-hover, #999);
23
+ }
24
+ .triggerButton[data-state=open] {
25
+ border-color: var(--ogrid-primary, #0066cc);
26
+ }
27
+
28
+ .buttonIcon {
29
+ font-size: 16px;
30
+ line-height: 1;
31
+ }
32
+
33
+ .chevron {
34
+ font-size: 12px;
35
+ color: var(--ogrid-muted, #888);
36
+ }
37
+
38
+ .dropdown {
39
+ min-width: 220px;
40
+ background: var(--ogrid-bg, #fff);
41
+ border-radius: 6px;
42
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
43
+ border: 1px solid var(--ogrid-border, #e0e0e0);
44
+ display: flex;
45
+ flex-direction: column;
46
+ padding: 0;
47
+ z-index: 50;
48
+ }
49
+
50
+ .header {
51
+ padding: 8px 12px;
52
+ border-bottom: 1px solid var(--ogrid-border, #e5e5e5);
53
+ font-weight: 600;
54
+ font-size: 13px;
55
+ color: var(--ogrid-fg, #333);
56
+ background: var(--ogrid-bg-subtle, #f5f5f5);
57
+ }
58
+
59
+ .optionsList {
60
+ max-height: 320px;
61
+ overflow-y: auto;
62
+ padding: 0;
63
+ }
64
+
65
+ .optionItem {
66
+ padding: 4px 12px;
67
+ display: flex;
68
+ align-items: center;
69
+ min-height: 32px;
70
+ }
71
+ .optionItem:hover {
72
+ background: var(--ogrid-bg-hover, #f0f0f0);
73
+ }
74
+
75
+ .checkbox {
76
+ width: 16px;
77
+ height: 16px;
78
+ border: 1px solid var(--ogrid-border, #888);
79
+ border-radius: 3px;
80
+ background: var(--ogrid-bg, #fff);
81
+ cursor: pointer;
82
+ display: inline-flex;
83
+ align-items: center;
84
+ justify-content: center;
85
+ flex-shrink: 0;
86
+ }
87
+ .checkbox[data-state=checked] {
88
+ background: var(--ogrid-primary, #0066cc);
89
+ border-color: var(--ogrid-primary, #0066cc);
90
+ }
91
+ .checkbox[data-state=indeterminate] {
92
+ background: var(--ogrid-primary, #0066cc);
93
+ border-color: var(--ogrid-primary, #0066cc);
94
+ }
95
+
96
+ .checkboxIndicator {
97
+ color: var(--ogrid-primary-fg, #fff);
98
+ font-size: 12px;
99
+ line-height: 1;
100
+ display: flex;
101
+ align-items: center;
102
+ justify-content: center;
103
+ }
104
+
105
+ .actions {
106
+ display: flex;
107
+ justify-content: flex-end;
108
+ gap: 8px;
109
+ padding: 8px 12px;
110
+ border-top: 1px solid var(--ogrid-border, #e5e5e5);
111
+ background: var(--ogrid-bg-subtle, #f5f5f5);
112
+ }
113
+
114
+ .clearButton {
115
+ padding: 6px 12px;
116
+ border: 1px solid var(--ogrid-border, #ccc);
117
+ border-radius: 4px;
118
+ background: var(--ogrid-bg, #fff);
119
+ color: var(--ogrid-muted, #666);
120
+ font-size: 12px;
121
+ cursor: pointer;
122
+ }
123
+ .clearButton:hover {
124
+ background: var(--ogrid-bg-hover, #f5f5f5);
125
+ color: var(--ogrid-fg, #333);
126
+ }
127
+
128
+ .selectAllButton {
129
+ padding: 6px 16px;
130
+ border: none;
131
+ border-radius: 4px;
132
+ background: var(--ogrid-primary, #0066cc);
133
+ color: var(--ogrid-primary-fg, #fff);
134
+ font-size: 12px;
135
+ font-weight: 600;
136
+ cursor: pointer;
137
+ }
138
+ .selectAllButton:hover {
139
+ background: var(--ogrid-primary-hover, #0052a3);
140
+ }
@@ -0,0 +1,55 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import * as React from 'react';
3
+ import * as Popover from '@radix-ui/react-popover';
4
+ import { useColumnHeaderFilterState } from '@alaarab/ogrid-react';
5
+ import { TextFilterPopover } from './TextFilterPopover';
6
+ import { MultiSelectFilterPopover } from './MultiSelectFilterPopover';
7
+ import { PeopleFilterPopover } from './PeopleFilterPopover';
8
+ import styles from './ColumnHeaderFilter.module.css';
9
+ function SortIcon({ isSorted, isDesc }) {
10
+ if (isSorted)
11
+ return _jsx("span", { "aria-hidden": true, children: isDesc ? '\u2193' : '\u2191' });
12
+ return _jsx("span", { "aria-hidden": true, children: '\u21C5' });
13
+ }
14
+ function FilterIcon() {
15
+ return _jsx("span", { "aria-hidden": true, children: '\u25BE' });
16
+ }
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, dateValue, onDateChange, } = props;
19
+ const state = useColumnHeaderFilterState({
20
+ filterType,
21
+ isSorted,
22
+ isSortedDescending,
23
+ onSort,
24
+ selectedValues,
25
+ onFilterChange,
26
+ options,
27
+ isLoadingOptions,
28
+ textValue,
29
+ onTextChange,
30
+ selectedUser,
31
+ onUserChange,
32
+ peopleSearch,
33
+ dateValue,
34
+ onDateChange,
35
+ });
36
+ const { headerRef, popoverRef, peopleInputRef, isFilterOpen, setFilterOpen, tempSelected, tempTextValue, setTempTextValue, searchText, setSearchText, filteredOptions, peopleSuggestions, isPeopleLoading, peopleSearchText, setPeopleSearchText, hasActiveFilter, handlers, } = state;
37
+ const safeOptions = options ?? [];
38
+ const renderPopoverContent = () => {
39
+ if (filterType === 'multiSelect') {
40
+ 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 }));
41
+ }
42
+ if (filterType === 'text') {
43
+ return (_jsx(TextFilterPopover, { value: tempTextValue, onValueChange: setTempTextValue, onApply: handlers.handleTextApply, onClear: handlers.handleTextClear }));
44
+ }
45
+ if (filterType === 'people') {
46
+ return (_jsx(PeopleFilterPopover, { selectedUser: selectedUser, searchText: peopleSearchText, onSearchChange: setPeopleSearchText, suggestions: peopleSuggestions, isLoading: isPeopleLoading, onUserSelect: handlers.handleUserSelect, onClearUser: handlers.handleClearUser, inputRef: peopleInputRef }));
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
+ }
51
+ return null;
52
+ };
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()] }) })] }))] })] }));
54
+ });
55
+ ColumnHeaderFilter.displayName = 'ColumnHeaderFilter';
@@ -0,0 +1,332 @@
1
+ .columnHeader {
2
+ display: flex;
3
+ align-items: center;
4
+ gap: 4px;
5
+ width: 100%;
6
+ max-width: 100%;
7
+ min-width: 0;
8
+ position: relative;
9
+ box-sizing: border-box;
10
+ overflow: hidden;
11
+ }
12
+
13
+ .headerContent {
14
+ display: flex;
15
+ align-items: center;
16
+ flex: 1;
17
+ min-width: 0;
18
+ overflow: hidden;
19
+ }
20
+
21
+ .columnName {
22
+ display: block;
23
+ min-width: 0;
24
+ max-width: 100%;
25
+ overflow: hidden;
26
+ text-overflow: ellipsis;
27
+ white-space: nowrap;
28
+ font-weight: 600;
29
+ font-size: 14px;
30
+ color: var(--ogrid-fg, #242424);
31
+ }
32
+
33
+ .headerActions {
34
+ display: flex;
35
+ align-items: center;
36
+ gap: 2px;
37
+ margin-left: auto;
38
+ flex-shrink: 0;
39
+ }
40
+
41
+ .sortIcon {
42
+ display: flex;
43
+ align-items: center;
44
+ justify-content: center;
45
+ width: 24px;
46
+ height: 24px;
47
+ padding: 4px;
48
+ border: none;
49
+ border-radius: 4px;
50
+ background: transparent;
51
+ color: var(--ogrid-muted, #616161);
52
+ cursor: pointer;
53
+ flex-shrink: 0;
54
+ font-size: 14px;
55
+ }
56
+ .sortIcon:hover {
57
+ background: var(--ogrid-bg-hover, #f5f5f5);
58
+ color: var(--ogrid-fg, #424242);
59
+ }
60
+ .sortIcon.sortActive {
61
+ background: var(--ogrid-bg-selected, #e0e0e0);
62
+ color: var(--ogrid-fg, #333);
63
+ }
64
+
65
+ .filterIcon {
66
+ display: flex;
67
+ align-items: center;
68
+ justify-content: center;
69
+ width: 24px;
70
+ height: 24px;
71
+ padding: 4px;
72
+ border: none;
73
+ border-radius: 4px;
74
+ background: transparent;
75
+ color: var(--ogrid-muted, #616161);
76
+ cursor: pointer;
77
+ flex-shrink: 0;
78
+ position: relative;
79
+ font-size: 14px;
80
+ }
81
+ .filterIcon:hover {
82
+ background: var(--ogrid-bg-hover, #f5f5f5);
83
+ color: var(--ogrid-fg, #424242);
84
+ }
85
+ .filterIcon.filterActive {
86
+ background: var(--ogrid-bg-selected, #e0e0e0);
87
+ color: var(--ogrid-primary, #0066cc);
88
+ }
89
+ .filterIcon.filterOpen {
90
+ background: var(--ogrid-bg-selected, #e8e8e8);
91
+ }
92
+
93
+ .filterBadge {
94
+ position: absolute;
95
+ top: 2px;
96
+ right: 2px;
97
+ width: 6px;
98
+ height: 6px;
99
+ background: var(--ogrid-primary, #0066cc);
100
+ border-radius: 50%;
101
+ border: 1px solid var(--ogrid-bg, #fff);
102
+ }
103
+
104
+ .popoverContent {
105
+ z-index: 1000;
106
+ min-width: 280px;
107
+ max-width: 320px;
108
+ background: var(--ogrid-bg, #fff);
109
+ border: 1px solid var(--ogrid-border, #d1d1d1);
110
+ border-radius: 8px;
111
+ box-shadow: 0 8px 16px rgba(0, 0, 0, 0.14);
112
+ overflow: hidden;
113
+ }
114
+
115
+ .popoverHeader {
116
+ padding: 10px 14px;
117
+ font-size: 12px;
118
+ font-weight: 600;
119
+ color: var(--ogrid-muted, #616161);
120
+ border-bottom: 1px solid var(--ogrid-border, #e0e0e0);
121
+ background: var(--ogrid-bg-subtle, #fafafa);
122
+ }
123
+
124
+ .popoverSearch {
125
+ padding: 10px 12px;
126
+ border-bottom: 1px solid var(--ogrid-border, #e0e0e0);
127
+ }
128
+
129
+ .searchInput {
130
+ width: 100%;
131
+ padding: 6px 10px;
132
+ border: 1px solid var(--ogrid-border, #d1d1d1);
133
+ border-radius: 4px;
134
+ font-size: 14px;
135
+ box-sizing: border-box;
136
+ }
137
+ .searchInput:focus {
138
+ outline: none;
139
+ border-color: var(--ogrid-primary, #0066cc);
140
+ }
141
+
142
+ .nativeInputWrapper {
143
+ display: flex;
144
+ align-items: center;
145
+ gap: 8px;
146
+ width: 100%;
147
+ border: 1px solid var(--ogrid-border, #d1d1d1);
148
+ border-radius: 4px;
149
+ background: var(--ogrid-bg, #fff);
150
+ padding: 6px 12px;
151
+ min-height: 36px;
152
+ box-sizing: border-box;
153
+ }
154
+ .nativeInputWrapper:focus-within {
155
+ border-color: var(--ogrid-primary, #0066cc);
156
+ outline: none;
157
+ }
158
+
159
+ .nativeInput {
160
+ flex: 1;
161
+ min-width: 0;
162
+ border: none;
163
+ outline: none;
164
+ padding: 0;
165
+ font-size: 14px;
166
+ font-family: inherit;
167
+ background: transparent;
168
+ color: var(--ogrid-fg, #242424);
169
+ }
170
+ .nativeInput::placeholder {
171
+ color: var(--ogrid-muted, #707070);
172
+ }
173
+
174
+ .resultCount {
175
+ margin-top: 6px;
176
+ font-size: 11px;
177
+ color: var(--ogrid-muted, #616161);
178
+ }
179
+
180
+ .selectAllRow {
181
+ display: flex;
182
+ gap: 8px;
183
+ padding: 6px 12px;
184
+ border-bottom: 1px solid var(--ogrid-border, #e0e0e0);
185
+ background: var(--ogrid-bg-subtle, #fafafa);
186
+ }
187
+
188
+ .selectAllButton {
189
+ background: none;
190
+ border: none;
191
+ color: var(--ogrid-primary, #0066cc);
192
+ font-size: 12px;
193
+ font-weight: 500;
194
+ cursor: pointer;
195
+ padding: 4px 8px;
196
+ border-radius: 4px;
197
+ }
198
+ .selectAllButton:hover {
199
+ background: var(--ogrid-bg-hover, #e8f4fc);
200
+ }
201
+
202
+ .popoverOptions {
203
+ overflow-y: auto;
204
+ max-height: 250px;
205
+ padding: 6px 0;
206
+ }
207
+
208
+ .popoverOption {
209
+ padding: 4px 12px;
210
+ display: flex;
211
+ align-items: center;
212
+ }
213
+ .popoverOption:hover {
214
+ background: var(--ogrid-bg-hover, #f5f5f5);
215
+ }
216
+
217
+ .filterCheckbox {
218
+ width: 16px;
219
+ height: 16px;
220
+ border: 1px solid var(--ogrid-border, #888);
221
+ border-radius: 3px;
222
+ background: var(--ogrid-bg, #fff);
223
+ cursor: pointer;
224
+ display: inline-flex;
225
+ align-items: center;
226
+ justify-content: center;
227
+ flex-shrink: 0;
228
+ }
229
+ .filterCheckbox[data-state=checked] {
230
+ background: var(--ogrid-primary, #0066cc);
231
+ border-color: var(--ogrid-primary, #0066cc);
232
+ color: var(--ogrid-primary-fg, #fff);
233
+ }
234
+
235
+ .personOption {
236
+ padding: 8px 12px;
237
+ cursor: pointer;
238
+ }
239
+ .personOption:hover {
240
+ background: var(--ogrid-bg-hover, #f5f5f5);
241
+ }
242
+
243
+ .loadingContainer,
244
+ .noResults {
245
+ padding: 20px;
246
+ text-align: center;
247
+ font-size: 13px;
248
+ color: var(--ogrid-muted, #666);
249
+ }
250
+
251
+ .selectedUserSection {
252
+ padding: 8px 12px;
253
+ border-bottom: 1px solid var(--ogrid-border, #e0e0e0);
254
+ }
255
+
256
+ .selectedUserLabel {
257
+ font-size: 11px;
258
+ color: var(--ogrid-muted, #666);
259
+ margin-bottom: 4px;
260
+ }
261
+
262
+ .selectedUser {
263
+ display: flex;
264
+ align-items: center;
265
+ justify-content: space-between;
266
+ gap: 8px;
267
+ }
268
+
269
+ .userInfo {
270
+ display: flex;
271
+ align-items: center;
272
+ gap: 8px;
273
+ min-width: 0;
274
+ }
275
+
276
+ .userText {
277
+ min-width: 0;
278
+ font-size: 13px;
279
+ }
280
+ .userText .userSecondary {
281
+ font-size: 12px;
282
+ color: var(--ogrid-muted, #666);
283
+ }
284
+
285
+ .removeUserButton {
286
+ padding: 4px;
287
+ border: none;
288
+ background: transparent;
289
+ cursor: pointer;
290
+ color: var(--ogrid-muted, #666);
291
+ flex-shrink: 0;
292
+ }
293
+ .removeUserButton:hover {
294
+ color: var(--ogrid-fg, #333);
295
+ }
296
+
297
+ .popoverActions {
298
+ display: flex;
299
+ justify-content: flex-end;
300
+ gap: 8px;
301
+ padding: 8px 12px;
302
+ border-top: 1px solid var(--ogrid-border, #e0e0e0);
303
+ background: var(--ogrid-bg-subtle, #f5f5f5);
304
+ }
305
+
306
+ .clearButton {
307
+ padding: 6px 12px;
308
+ border: 1px solid var(--ogrid-border, #ccc);
309
+ border-radius: 4px;
310
+ background: var(--ogrid-bg, #fff);
311
+ color: var(--ogrid-muted, #666);
312
+ font-size: 12px;
313
+ cursor: pointer;
314
+ }
315
+ .clearButton:hover {
316
+ background: var(--ogrid-bg-hover, #f5f5f5);
317
+ color: var(--ogrid-fg, #333);
318
+ }
319
+
320
+ .applyButton {
321
+ padding: 6px 16px;
322
+ border: none;
323
+ border-radius: 4px;
324
+ background: var(--ogrid-primary, #0066cc);
325
+ color: var(--ogrid-primary-fg, #fff);
326
+ font-size: 12px;
327
+ font-weight: 600;
328
+ cursor: pointer;
329
+ }
330
+ .applyButton:hover {
331
+ background: var(--ogrid-primary-hover, #0052a3);
332
+ }
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import * as Checkbox from '@radix-ui/react-checkbox';
3
+ import styles from './ColumnHeaderFilter.module.css';
4
+ export const MultiSelectFilterPopover = ({ searchText, onSearchChange, options, filteredOptions, selected, onOptionToggle, onSelectAll, onClearSelection, onApply, isLoading, }) => (_jsxs(_Fragment, { children: [_jsxs("div", { className: styles.popoverSearch, children: [_jsx("input", { type: "text", className: styles.searchInput, placeholder: "Search...", value: searchText, onChange: (e) => onSearchChange(e.target.value), autoComplete: "off" }), _jsxs("div", { className: styles.resultCount, children: [filteredOptions.length, " of ", options.length, " options"] })] }), _jsxs("div", { className: styles.selectAllRow, children: [_jsxs("button", { type: "button", className: styles.selectAllButton, onClick: onSelectAll, children: ["Select All (", filteredOptions.length, ")"] }), _jsx("button", { type: "button", className: styles.selectAllButton, onClick: onClearSelection, children: "Clear" })] }), _jsx("div", { className: styles.popoverOptions, children: isLoading ? (_jsx("div", { className: styles.loadingContainer, children: "Loading..." })) : filteredOptions.length === 0 ? (_jsx("div", { className: styles.noResults, children: "No options found" })) : (filteredOptions.map((option) => (_jsxs("div", { className: styles.popoverOption, children: [_jsx(Checkbox.Root, { checked: selected.has(option), onCheckedChange: (c) => onOptionToggle(option, c === true), className: styles.filterCheckbox, children: _jsx(Checkbox.Indicator, { children: "\u2713" }) }), _jsx("label", { style: { marginLeft: 8, cursor: 'pointer' }, children: option })] }, option)))) }), _jsxs("div", { className: styles.popoverActions, children: [_jsx("button", { type: "button", className: styles.clearButton, onClick: onClearSelection, children: "Clear" }), _jsx("button", { type: "button", className: styles.applyButton, onClick: onApply, children: "Apply" })] })] }));
5
+ MultiSelectFilterPopover.displayName = 'MultiSelectFilterPopover';
@@ -0,0 +1,19 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import styles from './ColumnHeaderFilter.module.css';
3
+ function UserAvatar({ user, size = 32 }) {
4
+ if (user.photo) {
5
+ return _jsx("img", { src: user.photo, alt: "", width: size, height: size, style: { borderRadius: '50%' } });
6
+ }
7
+ return (_jsx("span", { style: {
8
+ width: size,
9
+ height: size,
10
+ borderRadius: '50%',
11
+ background: '#e0e0e0',
12
+ display: 'flex',
13
+ alignItems: 'center',
14
+ justifyContent: 'center',
15
+ fontSize: 12,
16
+ }, children: user.displayName?.charAt(0) ?? '?' }));
17
+ }
18
+ export const PeopleFilterPopover = ({ selectedUser, searchText, onSearchChange, suggestions, isLoading, onUserSelect, onClearUser, inputRef, }) => (_jsxs(_Fragment, { children: [selectedUser && (_jsxs("div", { className: styles.selectedUserSection, children: [_jsx("div", { className: styles.selectedUserLabel, children: "Currently filtered by:" }), _jsxs("div", { className: styles.selectedUser, children: [_jsxs("div", { className: styles.userInfo, children: [_jsx(UserAvatar, { user: selectedUser }), _jsxs("div", { className: styles.userText, children: [_jsx("div", { children: selectedUser.displayName }), _jsx("div", { className: styles.userSecondary, children: selectedUser.email })] })] }), _jsx("button", { type: "button", className: styles.removeUserButton, onClick: onClearUser, "aria-label": "Remove filter", children: "\u2715" })] })] })), _jsx("div", { className: styles.popoverSearch, children: _jsx("div", { className: styles.nativeInputWrapper, children: _jsx("input", { ref: inputRef, type: "text", className: styles.nativeInput, placeholder: "Search for a person...", value: searchText, onChange: (e) => onSearchChange(e.target.value), autoComplete: "off" }) }) }), _jsx("div", { className: styles.popoverOptions, children: isLoading && searchText.trim() ? (_jsx("div", { className: styles.loadingContainer, children: "Searching..." })) : suggestions.length === 0 && searchText.trim() ? (_jsx("div", { className: styles.noResults, children: "No results found" })) : searchText.trim() ? (suggestions.map((user) => (_jsx("div", { className: styles.personOption, onClick: () => onUserSelect(user), onKeyDown: (e) => e.key === 'Enter' && onUserSelect(user), role: "button", tabIndex: 0, children: _jsxs("div", { className: styles.userInfo, children: [_jsx(UserAvatar, { user: user }), _jsxs("div", { className: styles.userText, children: [_jsx("div", { children: user.displayName }), _jsx("div", { className: styles.userSecondary, children: user.email })] })] }) }, user.id ?? user.email ?? user.displayName ?? '')))) : (_jsx("div", { className: styles.noResults, children: "Type to search..." })) }), selectedUser && (_jsx("div", { className: styles.popoverActions, children: _jsx("button", { type: "button", className: styles.clearButton, onClick: onClearUser, children: "Clear Filter" }) }))] }));
19
+ PeopleFilterPopover.displayName = 'PeopleFilterPopover';
@@ -0,0 +1,4 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import styles from './ColumnHeaderFilter.module.css';
3
+ export const TextFilterPopover = ({ value, onValueChange, onApply, onClear, }) => (_jsxs(_Fragment, { children: [_jsx("div", { className: styles.popoverSearch, children: _jsx("input", { type: "text", className: styles.searchInput, placeholder: "Enter search term...", value: value, onChange: (e) => onValueChange(e.target.value), onKeyDown: (e) => e.key === 'Enter' && (e.preventDefault(), onApply()), autoComplete: "off" }) }), _jsxs("div", { className: styles.popoverActions, children: [_jsx("button", { type: "button", className: styles.clearButton, onClick: onClear, disabled: !value, children: "Clear" }), _jsx("button", { type: "button", className: styles.applyButton, onClick: onApply, children: "Apply" })] })] }));
4
+ TextFilterPopover.displayName = 'TextFilterPopover';
@@ -0,0 +1 @@
1
+ export { ColumnHeaderFilter } from './ColumnHeaderFilter';