@alaarab/ogrid-react-fluent 2.0.11 → 2.0.13

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 (27) hide show
  1. package/README.md +1 -1
  2. package/dist/esm/ColumnChooser/ColumnChooser.js +10 -128
  3. package/dist/esm/ColumnChooser/ColumnChooser.module.css +45 -10
  4. package/dist/esm/ColumnHeaderFilter/ColumnHeaderFilter.js +16 -36
  5. package/dist/esm/ColumnHeaderMenu/ColumnHeaderMenu.js +9 -3
  6. package/dist/esm/ColumnHeaderMenu/ColumnHeaderMenu.module.css +7 -7
  7. package/dist/esm/DataGridTable/DataGridTable.js +118 -295
  8. package/dist/esm/DataGridTable/DataGridTable.module.css +548 -436
  9. package/dist/esm/DataGridTable/DropIndicator.js +5 -0
  10. package/dist/esm/DataGridTable/EmptyState.js +5 -0
  11. package/dist/esm/DataGridTable/GridContextMenu.js +10 -32
  12. package/dist/esm/DataGridTable/LoadingOverlay.js +6 -0
  13. package/dist/esm/{FluentDataTable/FluentDataTable.js → OGrid/OGrid.js} +0 -2
  14. package/dist/esm/OGrid/index.js +1 -0
  15. package/dist/esm/index.js +1 -1
  16. package/dist/types/ColumnChooser/ColumnChooser.d.ts +2 -8
  17. package/dist/types/ColumnHeaderFilter/ColumnHeaderFilter.d.ts +2 -20
  18. package/dist/types/DataGridTable/DropIndicator.d.ts +7 -0
  19. package/dist/types/DataGridTable/EmptyState.d.ts +11 -0
  20. package/dist/types/DataGridTable/LoadingOverlay.d.ts +6 -0
  21. package/dist/types/{FluentDataTable/FluentDataTable.d.ts → OGrid/OGrid.d.ts} +0 -2
  22. package/dist/types/OGrid/index.d.ts +1 -0
  23. package/dist/types/PaginationControls/PaginationControls.d.ts +2 -10
  24. package/dist/types/index.d.ts +1 -1
  25. package/package.json +3 -3
  26. package/dist/esm/FluentDataTable/index.js +0 -1
  27. package/dist/types/FluentDataTable/index.d.ts +0 -1
package/README.md CHANGED
@@ -1,5 +1,5 @@
1
1
  <p align="center">
2
- <strong>OGrid for Fluent UI</strong> — The lightweight React data grid with enterprise features and zero enterprise cost.
2
+ <strong>OGrid for React Fluent</strong> — The lightweight React data grid with enterprise features and zero enterprise cost.
3
3
  </p>
4
4
 
5
5
  <p align="center">
@@ -1,133 +1,21 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useRef, useEffect } from 'react';
3
- import { Checkbox, makeStyles, tokens, mergeClasses, } from '@fluentui/react-components';
3
+ import { Button, Checkbox, } from '@fluentui/react-components';
4
4
  import { TableSettingsRegular, ChevronDownRegular, ChevronUpRegular } from '@fluentui/react-icons';
5
5
  import { useColumnChooserState } from '@alaarab/ogrid-react';
6
- const useStyles = makeStyles({
7
- container: {
8
- position: 'relative',
9
- display: 'inline-flex',
10
- },
11
- triggerButton: {
12
- display: 'inline-flex',
13
- alignItems: 'center',
14
- gap: '6px',
15
- padding: '6px 12px',
16
- border: `1px solid ${tokens.colorNeutralStroke1}`,
17
- borderRadius: tokens.borderRadiusMedium,
18
- backgroundColor: tokens.colorNeutralBackground1,
19
- cursor: 'pointer',
20
- fontSize: tokens.fontSizeBase300,
21
- fontWeight: tokens.fontWeightSemibold,
22
- color: tokens.colorNeutralForeground1,
23
- transitionDuration: '0.15s',
24
- transitionProperty: 'all',
25
- transitionTimingFunction: 'ease',
26
- ':hover': {
27
- backgroundColor: tokens.colorNeutralBackground1Hover,
28
- border: `1px solid ${tokens.colorNeutralStroke1Hover}`,
29
- },
30
- },
31
- triggerButtonOpen: {
32
- border: `1px solid ${tokens.colorBrandStroke1}`,
33
- },
34
- buttonIcon: {
35
- fontSize: '16px',
36
- },
37
- chevron: {
38
- fontSize: '12px',
39
- color: tokens.colorNeutralForeground3,
40
- },
41
- dropdown: {
42
- position: 'absolute',
43
- top: 'calc(100% + 4px)',
44
- right: '0',
45
- zIndex: 10000,
46
- minWidth: '220px',
47
- backgroundColor: tokens.colorNeutralBackground1,
48
- borderRadius: tokens.borderRadiusMedium,
49
- boxShadow: tokens.shadow16,
50
- border: `1px solid ${tokens.colorNeutralStroke1}`,
51
- display: 'flex',
52
- flexDirection: 'column',
53
- },
54
- header: {
55
- padding: '8px 12px',
56
- borderBottom: `1px solid ${tokens.colorNeutralStroke2}`,
57
- fontWeight: tokens.fontWeightSemibold,
58
- fontSize: tokens.fontSizeBase300,
59
- color: tokens.colorNeutralForeground1,
60
- backgroundColor: tokens.colorNeutralBackground2,
61
- },
62
- optionsList: {
63
- maxHeight: '320px',
64
- overflowY: 'auto',
65
- padding: 0,
66
- },
67
- optionItem: {
68
- padding: '4px 12px',
69
- display: 'flex',
70
- alignItems: 'center',
71
- minHeight: '32px',
72
- ':hover': {
73
- backgroundColor: tokens.colorNeutralBackground1Hover,
74
- },
75
- },
76
- actions: {
77
- display: 'flex',
78
- justifyContent: 'flex-end',
79
- gap: '8px',
80
- padding: '8px 12px',
81
- borderTop: `1px solid ${tokens.colorNeutralStroke2}`,
82
- backgroundColor: tokens.colorNeutralBackground2,
83
- },
84
- clearButton: {
85
- padding: '6px 12px',
86
- border: `1px solid ${tokens.colorNeutralStroke1}`,
87
- borderRadius: tokens.borderRadiusSmall,
88
- backgroundColor: tokens.colorNeutralBackground1,
89
- color: tokens.colorNeutralForeground2,
90
- fontSize: tokens.fontSizeBase200,
91
- fontWeight: tokens.fontWeightRegular,
92
- cursor: 'pointer',
93
- transitionDuration: '0.15s',
94
- transitionProperty: 'all',
95
- ':hover': {
96
- backgroundColor: tokens.colorNeutralBackground1Hover,
97
- color: tokens.colorNeutralForeground1,
98
- border: `1px solid ${tokens.colorNeutralStroke1Hover}`,
99
- },
100
- },
101
- selectAllButton: {
102
- padding: '6px 16px',
103
- border: 'none',
104
- borderRadius: tokens.borderRadiusSmall,
105
- backgroundColor: tokens.colorBrandBackground,
106
- color: tokens.colorNeutralForegroundOnBrand,
107
- fontSize: tokens.fontSizeBase200,
108
- fontWeight: tokens.fontWeightSemibold,
109
- cursor: 'pointer',
110
- transitionDuration: '0.15s',
111
- transitionProperty: 'all',
112
- ':hover': {
113
- backgroundColor: tokens.colorBrandBackgroundHover,
114
- },
115
- },
116
- });
6
+ import styles from './ColumnChooser.module.css';
117
7
  export const ColumnChooser = (props) => {
118
8
  const { columns, visibleColumns, onVisibilityChange, className } = props;
119
- const classes = useStyles();
120
9
  const buttonRef = useRef(null);
121
10
  const dropdownRef = useRef(null);
122
- const { open: isOpen, handleToggle, handleClose, handleCheckboxChange: setColumnVisible, handleSelectAll, handleClearAll, visibleCount, totalCount, } = useColumnChooserState({ columns, visibleColumns, onVisibilityChange });
11
+ const { open, handleToggle, handleClose, handleCheckboxChange: setColumnVisible, handleSelectAll, handleClearAll, visibleCount, totalCount, } = useColumnChooserState({ columns, visibleColumns, onVisibilityChange });
123
12
  useEffect(() => {
124
- if (!isOpen)
13
+ if (!open)
125
14
  return;
126
15
  const handleClickOutside = (event) => {
127
16
  const target = event.target;
128
- const isOutsideDropdown = dropdownRef.current && !dropdownRef.current.contains(target);
129
- const isOutsideButton = buttonRef.current && !buttonRef.current.contains(target);
130
- if (isOutsideDropdown && isOutsideButton) {
17
+ if (dropdownRef.current && !dropdownRef.current.contains(target) &&
18
+ buttonRef.current && !buttonRef.current.contains(target)) {
131
19
  handleClose();
132
20
  }
133
21
  };
@@ -138,15 +26,9 @@ export const ColumnChooser = (props) => {
138
26
  clearTimeout(timeoutId);
139
27
  document.removeEventListener('mousedown', handleClickOutside);
140
28
  };
141
- }, [isOpen, handleClose]);
142
- const handleCheckboxChange = (columnKey) => {
143
- return (ev, data) => {
144
- ev.stopPropagation();
145
- setColumnVisible(columnKey)(data.checked === true);
146
- };
147
- };
148
- const handleDropdownClick = (e) => {
149
- e.stopPropagation();
29
+ }, [open, handleClose]);
30
+ const handleCheckboxChange = (columnKey) => (_ev, data) => {
31
+ setColumnVisible(columnKey)(data.checked === true);
150
32
  };
151
- return (_jsxs("div", { className: `${classes.container} ${className || ''}`, children: [_jsxs("button", { type: "button", ref: buttonRef, className: mergeClasses(classes.triggerButton, isOpen && classes.triggerButtonOpen), onClick: handleToggle, "aria-expanded": isOpen, "aria-haspopup": "listbox", children: [_jsx(TableSettingsRegular, { className: classes.buttonIcon }), _jsxs("span", { children: ["Column Visibility (", visibleCount, " of ", totalCount, ")"] }), isOpen ? _jsx(ChevronUpRegular, { className: classes.chevron }) : _jsx(ChevronDownRegular, { className: classes.chevron })] }), isOpen && (_jsxs("div", { className: classes.dropdown, ref: dropdownRef, onClick: handleDropdownClick, children: [_jsxs("div", { className: classes.header, children: ["Select Columns (", visibleCount, " of ", totalCount, ")"] }), _jsx("div", { className: classes.optionsList, children: columns.map(column => (_jsx("div", { className: classes.optionItem, children: _jsx(Checkbox, { label: column.name, checked: visibleColumns.has(column.columnId), onChange: handleCheckboxChange(column.columnId) }) }, column.columnId))) }), _jsxs("div", { className: classes.actions, children: [_jsx("button", { type: "button", className: classes.clearButton, onClick: handleClearAll, children: "Clear All" }), _jsx("button", { type: "button", className: classes.selectAllButton, onClick: handleSelectAll, children: "Select All" })] })] }))] }));
33
+ return (_jsxs("div", { className: `${styles.container} ${className || ''}`, children: [_jsxs(Button, { ref: buttonRef, appearance: "outline", icon: _jsx(TableSettingsRegular, {}), onClick: handleToggle, "aria-expanded": open, "aria-haspopup": "listbox", children: ["Column Visibility (", visibleCount, " of ", totalCount, ")", open ? _jsx(ChevronUpRegular, {}) : _jsx(ChevronDownRegular, {})] }), open && (_jsxs("div", { ref: dropdownRef, className: styles.dropdown, children: [_jsxs("div", { className: styles.header, children: ["Select Columns (", visibleCount, " of ", totalCount, ")"] }), _jsx("div", { className: styles.optionsList, children: columns.map((column) => (_jsx("div", { className: styles.optionItem, children: _jsx(Checkbox, { label: column.name, checked: visibleColumns.has(column.columnId), onChange: handleCheckboxChange(column.columnId) }) }, column.columnId))) }), _jsxs("div", { className: styles.actions, children: [_jsx(Button, { appearance: "subtle", size: "small", onClick: handleClearAll, children: "Clear All" }), _jsx(Button, { appearance: "primary", size: "small", onClick: handleSelectAll, children: "Select All" })] })] }))] }));
152
34
  };
@@ -1,15 +1,50 @@
1
- .columnChooser {
2
- display: inline-block;
1
+ .container {
2
+ position: relative;
3
+ display: inline-flex;
3
4
  }
4
5
 
5
- .columnChooserHeader {
6
- padding: 10px 15px;
7
- border-bottom: 1px solid var(--colorNeutralStroke2, #edebe9);
8
- margin-bottom: 5px;
6
+ .dropdown {
7
+ position: absolute;
8
+ top: calc(100% + 4px);
9
+ right: 0;
10
+ z-index: 10000;
11
+ min-width: 220px;
12
+ display: flex;
13
+ flex-direction: column;
14
+ background: var(--colorNeutralBackground1, #fff);
15
+ border: 1px solid var(--colorNeutralStroke2, #edebe9);
16
+ border-radius: 6px;
17
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
18
+ padding: 0;
9
19
  }
10
- .columnChooserHeader h3 {
11
- margin: 0;
12
- font-size: 14px;
20
+
21
+ .header {
22
+ padding: 8px 12px;
23
+ border-bottom: 1px solid var(--colorNeutralStroke2, #edebe9);
13
24
  font-weight: 600;
14
- color: var(--colorNeutralForeground1, #323130);
25
+ font-size: 13px;
26
+ }
27
+
28
+ .optionsList {
29
+ max-height: 320px;
30
+ overflow-y: auto;
31
+ padding: 0;
32
+ }
33
+
34
+ .optionItem {
35
+ padding: 4px 12px;
36
+ display: flex;
37
+ align-items: center;
38
+ min-height: 32px;
39
+ }
40
+ .optionItem:hover {
41
+ background: var(--colorNeutralBackground1Hover, #f5f5f5);
42
+ }
43
+
44
+ .actions {
45
+ display: flex;
46
+ justify-content: flex-end;
47
+ gap: 8px;
48
+ padding: 8px 12px;
49
+ border-top: 1px solid var(--colorNeutralStroke2, #edebe9);
15
50
  }
@@ -2,48 +2,28 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import * as React from 'react';
3
3
  import { Popover, PopoverSurface } from '@fluentui/react-components';
4
4
  import { ArrowUpRegular, ArrowDownRegular, ArrowSortRegular, FilterRegular } from '@fluentui/react-icons';
5
- import { useColumnHeaderFilterState } from '@alaarab/ogrid-react';
5
+ import { useColumnHeaderFilterState, getColumnHeaderFilterStateParams, DateFilterContent, renderFilterContent, } from '@alaarab/ogrid-react';
6
6
  import { TextFilterPopover } from './TextFilterPopover';
7
7
  import { MultiSelectFilterPopover } from './MultiSelectFilterPopover';
8
8
  import { PeopleFilterPopover } from './PeopleFilterPopover';
9
9
  import styles from './ColumnHeaderFilter.module.css';
10
10
  export const ColumnHeaderFilter = React.memo((props) => {
11
- const { columnName, filterType, isSorted = false, isSortedDescending = false, onSort, selectedValues, onFilterChange, options, isLoadingOptions = false, textValue = '', onTextChange, selectedUser, onUserChange, peopleSearch, dateValue, onDateChange, } = props;
12
- const state = useColumnHeaderFilterState({
13
- filterType,
14
- isSorted,
15
- isSortedDescending,
16
- onSort,
17
- selectedValues,
18
- onFilterChange,
19
- options,
20
- isLoadingOptions,
21
- textValue,
22
- onTextChange,
23
- selectedUser,
24
- onUserChange,
25
- peopleSearch,
26
- dateValue,
27
- onDateChange,
28
- });
29
- const { headerRef, popoverRef, peopleInputRef, isFilterOpen, setFilterOpen, tempSelected, setTempTextValue, searchText, setSearchText, filteredOptions, peopleSuggestions, isPeopleLoading, peopleSearchText, setPeopleSearchText, hasActiveFilter, handlers, } = state;
11
+ const { columnName, filterType, isSorted = false, isSortedDescending = false, onSort, options, isLoadingOptions = false, selectedUser, } = props;
12
+ const state = useColumnHeaderFilterState(getColumnHeaderFilterStateParams(props));
13
+ const { headerRef, popoverRef, isFilterOpen, setFilterOpen, hasActiveFilter, handlers, } = state;
30
14
  const filterBtnRef = React.useRef(null);
31
- const renderPopoverContent = () => {
32
- if (filterType === 'multiSelect') {
33
- return (_jsx(MultiSelectFilterPopover, { searchText: searchText, onSearchChange: setSearchText, options: options ?? [], filteredOptions: filteredOptions, selected: tempSelected, onOptionToggle: handlers.handleCheckboxChange, onSelectAll: handlers.handleSelectAll, onClearSelection: handlers.handleClearSelection, onApply: handlers.handleApplyMultiSelect, isLoading: isLoadingOptions, onPopoverClick: handlers.handlePopoverClick, onInputFocus: handlers.handleInputFocus, onInputMouseDown: handlers.handleInputMouseDown, onInputClick: handlers.handleInputClick, onInputKeyDown: handlers.handleInputKeyDown }));
34
- }
35
- if (filterType === 'text') {
36
- return (_jsx(TextFilterPopover, { value: state.tempTextValue, onValueChange: setTempTextValue, onApply: handlers.handleTextApply, onClear: handlers.handleTextClear, onPopoverClick: handlers.handlePopoverClick, onInputFocus: handlers.handleInputFocus, onInputMouseDown: handlers.handleInputMouseDown, onInputClick: handlers.handleInputClick, onInputKeyDown: handlers.handleInputKeyDown }));
37
- }
38
- if (filterType === 'people') {
39
- return (_jsx(PeopleFilterPopover, { selectedUser: selectedUser, searchText: peopleSearchText, onSearchChange: setPeopleSearchText, suggestions: peopleSuggestions, isLoading: isPeopleLoading, onUserSelect: handlers.handleUserSelect, onClearUser: handlers.handleClearUser, onPopoverClick: handlers.handlePopoverClick, inputRef: peopleInputRef }));
40
- }
41
- if (filterType === 'date') {
42
- return (_jsxs("div", { onClick: handlers.handlePopoverClick, 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" })] })] }));
43
- }
44
- return null;
45
- };
15
+ // Fluent-specific renderers that pass additional event propagation handlers
16
+ const fluentRenderers = React.useMemo(() => ({
17
+ renderMultiSelect: (p) => (_jsx(MultiSelectFilterPopover, { searchText: p.searchText, onSearchChange: p.onSearchChange, options: p.options, filteredOptions: p.filteredOptions, selected: p.selected, onOptionToggle: p.onOptionToggle, onSelectAll: p.onSelectAll, onClearSelection: p.onClearSelection, onApply: p.onApply, isLoading: p.isLoading, onPopoverClick: handlers.handlePopoverClick, onInputFocus: handlers.handleInputFocus, onInputMouseDown: handlers.handleInputMouseDown, onInputClick: handlers.handleInputClick, onInputKeyDown: handlers.handleInputKeyDown })),
18
+ renderText: (p) => (_jsx(TextFilterPopover, { value: p.value, onValueChange: p.onValueChange, onApply: p.onApply, onClear: p.onClear, onPopoverClick: handlers.handlePopoverClick, onInputFocus: handlers.handleInputFocus, onInputMouseDown: handlers.handleInputMouseDown, onInputClick: handlers.handleInputClick, onInputKeyDown: handlers.handleInputKeyDown })),
19
+ renderPeople: (p) => (_jsx(PeopleFilterPopover, { selectedUser: p.selectedUser, searchText: p.searchText, onSearchChange: p.onSearchChange, suggestions: p.suggestions, isLoading: p.isLoading, onUserSelect: p.onUserSelect, onClearUser: p.onClearUser, onPopoverClick: handlers.handlePopoverClick, inputRef: p.inputRef })),
20
+ renderDate: (p) => (_jsx("div", { onClick: handlers.handlePopoverClick, children: _jsx(DateFilterContent, { tempDateFrom: p.tempDateFrom, setTempDateFrom: p.setTempDateFrom, tempDateTo: p.tempDateTo, setTempDateTo: p.setTempDateTo, onApply: p.onApply, onClear: p.onClear, classNames: {
21
+ popoverActions: styles.popoverActions,
22
+ clearButton: styles.clearButton,
23
+ applyButton: styles.applyButton,
24
+ } }) })),
25
+ }), [handlers]);
46
26
  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: isSorted ? (isSortedDescending ? _jsx(ArrowDownRegular, {}) : _jsx(ArrowUpRegular, {})) : (_jsx(ArrowSortRegular, {})) })), filterType !== 'none' && (_jsxs(_Fragment, { children: [_jsxs("button", { ref: filterBtnRef, type: "button", className: `${styles.filterIcon} ${hasActiveFilter ? styles.filterActive : ''} ${isFilterOpen ? styles.filterOpen : ''}`, onClick: handlers.handleFilterIconClick, "aria-label": `Filter ${columnName}`, title: `Filter ${columnName}`, children: [_jsx(FilterRegular, {}), hasActiveFilter && _jsx("span", { className: styles.filterBadge })] }), _jsx(Popover, { open: isFilterOpen, onOpenChange: (_, data) => { if (!data.open)
47
- setFilterOpen(false); }, positioning: { target: filterBtnRef.current ?? undefined, position: 'below', align: 'start', offset: 4 }, trapFocus: false, children: _jsxs(PopoverSurface, { ref: popoverRef, className: styles.filterPopover, onClick: handlers.handlePopoverClick, style: { padding: 0 }, children: [_jsxs("div", { className: styles.popoverHeader, children: ["Filter: ", columnName] }), renderPopoverContent()] }) })] }))] })] }));
27
+ setFilterOpen(false); }, positioning: { target: filterBtnRef.current ?? undefined, position: 'below', align: 'start', offset: 4 }, trapFocus: false, children: _jsxs(PopoverSurface, { ref: popoverRef, className: styles.filterPopover, onClick: handlers.handlePopoverClick, style: { padding: 0 }, children: [_jsxs("div", { className: styles.popoverHeader, children: ["Filter: ", columnName] }), renderFilterContent(filterType, state, options ?? [], isLoadingOptions, selectedUser, fluentRenderers)] }) })] }))] })] }));
48
28
  });
49
29
  ColumnHeaderFilter.displayName = 'ColumnHeaderFilter';
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React, { useMemo, useEffect, useState } from 'react';
2
+ import React, { useMemo, useEffect, useState, useRef } from 'react';
3
3
  import { createPortal } from 'react-dom';
4
4
  import { getColumnHeaderMenuItems } from '@alaarab/ogrid-core';
5
5
  import styles from './ColumnHeaderMenu.module.css';
@@ -10,6 +10,7 @@ import styles from './ColumnHeaderMenu.module.css';
10
10
  export function ColumnHeaderMenu(props) {
11
11
  const { isOpen, anchorElement, onClose, onPinLeft, onPinRight, onUnpin, onSortAsc, onSortDesc, onClearSort, onAutosizeThis, onAutosizeAll, canPinLeft, canPinRight, canUnpin, currentSort, isSortable, isResizable, } = props;
12
12
  const [position, setPosition] = useState(null);
13
+ const menuRef = useRef(null);
13
14
  useEffect(() => {
14
15
  if (!isOpen || !anchorElement) {
15
16
  setPosition(null);
@@ -22,6 +23,9 @@ export function ColumnHeaderMenu(props) {
22
23
  });
23
24
  const handleClickOutside = (e) => {
24
25
  const target = e.target;
26
+ // Don't close if clicking inside the menu itself (portal) — let onClick fire first
27
+ if (menuRef.current && menuRef.current.contains(target))
28
+ return;
25
29
  if (anchorElement && !anchorElement.contains(target)) {
26
30
  onClose();
27
31
  }
@@ -59,7 +63,9 @@ export function ColumnHeaderMenu(props) {
59
63
  };
60
64
  if (!isOpen || !position)
61
65
  return null;
62
- return createPortal(_jsx("div", { className: styles.content, style: {
66
+ // Portal into the closest FluentProvider so --ogrid-* bridged variables are available
67
+ const portalTarget = anchorElement?.closest('.fui-FluentProvider') ?? document.body;
68
+ return createPortal(_jsx("div", { ref: menuRef, className: styles.content, style: {
63
69
  position: 'fixed',
64
70
  top: position.top,
65
71
  left: position.left,
@@ -67,5 +73,5 @@ export function ColumnHeaderMenu(props) {
67
73
  }, children: items.map((item, idx) => (_jsxs(React.Fragment, { children: [_jsx("button", { className: styles.item, disabled: item.disabled, onClick: () => {
68
74
  handlers[item.id]();
69
75
  onClose();
70
- }, children: item.label }), item.divider && idx < items.length - 1 && (_jsx("div", { className: styles.separator }))] }, item.id))) }), document.body);
76
+ }, children: item.label }), item.divider && idx < items.length - 1 && (_jsx("div", { className: styles.separator }))] }, item.id))) }), portalTarget);
71
77
  }
@@ -1,7 +1,7 @@
1
1
  .content {
2
2
  min-width: 140px;
3
- background: white;
4
- border: 1px solid #e0e0e0;
3
+ background: var(--ogrid-bg, #ffffff);
4
+ border: 1px solid var(--ogrid-border, rgba(0, 0, 0, 0.12));
5
5
  border-radius: 6px;
6
6
  padding: 4px;
7
7
  box-shadow: 0px 10px 38px -10px rgba(22, 23, 24, 0.35), 0px 10px 20px -15px rgba(22, 23, 24, 0.2);
@@ -14,7 +14,7 @@
14
14
  .item {
15
15
  font-size: 13px;
16
16
  line-height: 1;
17
- color: #111;
17
+ color: var(--ogrid-fg, rgba(0, 0, 0, 0.87));
18
18
  border-radius: 4px;
19
19
  border: none;
20
20
  background: transparent;
@@ -30,17 +30,17 @@
30
30
  text-align: left;
31
31
  }
32
32
  .item:disabled {
33
- color: #999;
33
+ color: var(--ogrid-fg-muted, rgba(0, 0, 0, 0.5));
34
34
  pointer-events: none;
35
35
  cursor: not-allowed;
36
36
  }
37
37
  .item:hover:not(:disabled) {
38
- background-color: #f5f5f5;
39
- color: #111;
38
+ background-color: var(--ogrid-hover-bg, rgba(0, 0, 0, 0.04));
39
+ color: var(--ogrid-fg, rgba(0, 0, 0, 0.87));
40
40
  }
41
41
 
42
42
  .separator {
43
43
  height: 1px;
44
- background-color: #e0e0e0;
44
+ background-color: var(--ogrid-border, rgba(0, 0, 0, 0.12));
45
45
  margin: 4px 0;
46
46
  }