@opencorestack/opengridx 0.1.6 → 0.1.7

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 (129) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +9 -0
  3. package/dist/index.d.ts +6 -0
  4. package/dist/opengridx.es.js +6 -6
  5. package/dist/opengridx.es.js.map +1 -1
  6. package/dist/opengridx.umd.js +4 -4
  7. package/dist/opengridx.umd.js.map +1 -1
  8. package/docs/API_REFERENCE.md +177 -0
  9. package/docs/README.md +60 -0
  10. package/docs/components/cell.md +44 -0
  11. package/docs/components/column-group-header.md +57 -0
  12. package/docs/components/column-resize.md +63 -0
  13. package/docs/components/column-visibility.md +61 -0
  14. package/docs/components/datagrid.md +70 -0
  15. package/docs/components/filter-panel.md +26 -0
  16. package/docs/components/header.md +39 -0
  17. package/docs/components/pagination.md +34 -0
  18. package/docs/components/row.md +31 -0
  19. package/docs/components/toolbar.md +40 -0
  20. package/docs/components/tooltip.md +68 -0
  21. package/docs/customization/slots-api.md +227 -0
  22. package/docs/customization/theming.md +293 -0
  23. package/docs/features/aggregation-pivot.md +79 -0
  24. package/docs/features/cell-spanning.md +520 -0
  25. package/docs/features/clipboard.md +76 -0
  26. package/docs/features/custom-pagination.md +143 -0
  27. package/docs/features/data-source.md +72 -0
  28. package/docs/features/editing-reordering.md +99 -0
  29. package/docs/features/export-guide.md +434 -0
  30. package/docs/features/filtering.md +83 -0
  31. package/docs/features/infinite-scroll.md +87 -0
  32. package/docs/features/list-view.md +65 -0
  33. package/docs/features/loading-states.md +68 -0
  34. package/docs/features/master-detail.md +46 -0
  35. package/docs/features/pinning.md +67 -0
  36. package/docs/features/selection.md +70 -0
  37. package/docs/features/sorting-pagination.md +66 -0
  38. package/docs/features/state-persistence.md +67 -0
  39. package/docs/features/tree-data-grouping.md +67 -0
  40. package/docs/features/virtualization.md +188 -0
  41. package/docs/roadmap.md +71 -0
  42. package/lib/components/Cell/Cell.css +119 -0
  43. package/lib/components/Cell/Cell.tsx +264 -0
  44. package/lib/components/Cell/GridEditInputCell.css +62 -0
  45. package/lib/components/Cell/GridEditInputCell.tsx +152 -0
  46. package/lib/components/ColumnGroupHeader/ColumnGroupHeader.css +67 -0
  47. package/lib/components/ColumnGroupHeader/ColumnGroupHeader.tsx +220 -0
  48. package/lib/components/ColumnResizeHandle/ColumnResizeHandle.css +31 -0
  49. package/lib/components/ColumnResizeHandle/ColumnResizeHandle.tsx +85 -0
  50. package/lib/components/ColumnVisibilityPanel/ColumnVisibilityPanel.css +137 -0
  51. package/lib/components/ColumnVisibilityPanel/ColumnVisibilityPanel.tsx +190 -0
  52. package/lib/components/DataGrid/DataGrid.css +224 -0
  53. package/lib/components/DataGrid/DataGrid.tsx +2253 -0
  54. package/lib/components/DetailPanel/DetailPanel.css +36 -0
  55. package/lib/components/DetailPanel/DetailPanel.tsx +34 -0
  56. package/lib/components/FilterPanel/FilterPanel.css +196 -0
  57. package/lib/components/FilterPanel/FilterPanel.tsx +183 -0
  58. package/lib/components/FilterPanel/index.ts +1 -0
  59. package/lib/components/Header/ColumnMenu.css +68 -0
  60. package/lib/components/Header/ColumnMenu.tsx +249 -0
  61. package/lib/components/Header/Header.css +224 -0
  62. package/lib/components/Header/Header.tsx +516 -0
  63. package/lib/components/ListView/ListView.css +112 -0
  64. package/lib/components/ListView/ListViewRow.tsx +63 -0
  65. package/lib/components/Pagination/Pagination.css +91 -0
  66. package/lib/components/Pagination/Pagination.tsx +151 -0
  67. package/lib/components/QuickFilter/QuickFilter.css +23 -0
  68. package/lib/components/QuickFilter/QuickFilter.tsx +79 -0
  69. package/lib/components/Row/Row.css +126 -0
  70. package/lib/components/Row/Row.tsx +377 -0
  71. package/lib/components/Skeleton/Skeleton.css +55 -0
  72. package/lib/components/Skeleton/Skeleton.tsx +22 -0
  73. package/lib/components/SkeletonRow/SkeletonRow.css +32 -0
  74. package/lib/components/SkeletonRow/SkeletonRow.tsx +89 -0
  75. package/lib/components/SkeletonRow/index.ts +1 -0
  76. package/lib/components/Toolbar/GlobalSearch.css +113 -0
  77. package/lib/components/Toolbar/GlobalSearch.tsx +120 -0
  78. package/lib/components/Toolbar/GridToolbar.css +204 -0
  79. package/lib/components/Toolbar/GridToolbar.tsx +647 -0
  80. package/lib/components/Toolbar/PivotPanel.css +212 -0
  81. package/lib/components/Toolbar/PivotPanel.tsx +239 -0
  82. package/lib/components/Tooltip/Tooltip.css +59 -0
  83. package/lib/components/Tooltip/Tooltip.tsx +106 -0
  84. package/lib/components/ui/Button.css +89 -0
  85. package/lib/components/ui/Button.tsx +39 -0
  86. package/lib/components/ui/Checkbox.css +71 -0
  87. package/lib/components/ui/Checkbox.tsx +66 -0
  88. package/lib/components/ui/DragHandleIcon.css +19 -0
  89. package/lib/components/ui/DragHandleIcon.tsx +18 -0
  90. package/lib/components/ui/ExpandIcon.css +38 -0
  91. package/lib/components/ui/ExpandIcon.tsx +75 -0
  92. package/lib/components/ui/Input.css +59 -0
  93. package/lib/components/ui/Input.tsx +45 -0
  94. package/lib/components/ui/index.ts +15 -0
  95. package/lib/hooks/core/useDataGrid.ts +392 -0
  96. package/lib/hooks/core/useGridApiRef.ts +10 -0
  97. package/lib/hooks/features/useAggregation.ts +143 -0
  98. package/lib/hooks/features/useGridClipboard.ts +133 -0
  99. package/lib/hooks/features/useGridDataSource.ts +190 -0
  100. package/lib/hooks/features/useGridEditing.ts +100 -0
  101. package/lib/hooks/features/useGridSpanning.ts +349 -0
  102. package/lib/hooks/features/usePivot.ts +149 -0
  103. package/lib/hooks/useColumnReorder.ts +114 -0
  104. package/lib/hooks/useRowGrouping.ts +302 -0
  105. package/lib/hooks/useRowReorder.ts +83 -0
  106. package/lib/hooks/useTreeData.ts +323 -0
  107. package/lib/index.ts +106 -0
  108. package/lib/state/index.ts +17 -0
  109. package/lib/state/types.ts +48 -0
  110. package/lib/state/useGridStateStorage.ts +113 -0
  111. package/lib/styles/base/reset.css +21 -0
  112. package/lib/styles/base/variables.css +161 -0
  113. package/lib/styles/opengridx.css +27 -0
  114. package/lib/styles/opengridx.css.d.ts +4 -0
  115. package/lib/theme/DataGridThemeProvider.tsx +276 -0
  116. package/lib/theme/defaultStyles.ts +0 -0
  117. package/lib/theme/index.ts +21 -0
  118. package/lib/theme/presets.ts +148 -0
  119. package/lib/theme/types.ts +155 -0
  120. package/lib/types/index.ts +764 -0
  121. package/lib/utils/export/exportToExcelAdvanced.ts +516 -0
  122. package/lib/utils/export/index.ts +550 -0
  123. package/lib/utils/filtering/index.ts +240 -0
  124. package/lib/utils/pinning/index.ts +282 -0
  125. package/lib/utils/sorting/index.ts +55 -0
  126. package/lib/utils/virtualization/index.ts +106 -0
  127. package/lib/vite-env.d.ts +1 -0
  128. package/llms.txt +2 -1
  129. package/package.json +13 -4
@@ -0,0 +1,249 @@
1
+ import React, { useRef, useEffect, useState } from 'react';
2
+ import ReactDOM from 'react-dom';
3
+ import type { GridColDef, GridSortDirection, GridColumnPinning } from '../../types';
4
+
5
+
6
+ interface ColumnMenuProps {
7
+ colDef: GridColDef<any>;
8
+ sortModel?: Array<{ field: string; sort: 'asc' | 'desc' }>;
9
+ onSort?: (field: string, direction: GridSortDirection) => void;
10
+ onHide?: (field: string) => void;
11
+ onPin?: (field: string, side: 'left' | 'right' | null) => void;
12
+ pinnedColumns?: GridColumnPinning;
13
+ onManageColumns?: () => void;
14
+ onClose: () => void;
15
+ anchorEl: HTMLElement | null;
16
+ }
17
+
18
+ function ActiveDotIcon() {
19
+ return (
20
+ <svg width="8" height="8" viewBox="0 0 8 8" fill="currentColor">
21
+ <circle cx="4" cy="4" r="4" />
22
+ </svg>
23
+ );
24
+ }
25
+
26
+ function InactiveDotIcon() {
27
+ return (
28
+ <svg width="8" height="8" viewBox="0 0 8 8" fill="none" stroke="currentColor" strokeWidth="1.5">
29
+ <circle cx="4" cy="4" r="3" />
30
+ </svg>
31
+ );
32
+ }
33
+
34
+ function ManageColumnsIcon() {
35
+ return (
36
+ <svg focusable="false" aria-hidden="true" viewBox="0 0 24 24" width="13" height="13" fill="none">
37
+ <rect x="2" y="4" width="20" height="16" rx="2" stroke="currentColor" strokeWidth="1.8" />
38
+ <rect x="8.5" y="4" width="7" height="16" fill="currentColor" opacity="0.3" />
39
+ <line x1="8.5" y1="4" x2="8.5" y2="20" stroke="currentColor" strokeWidth="1.8" />
40
+ <line x1="15.5" y1="4" x2="15.5" y2="20" stroke="currentColor" strokeWidth="1.8" />
41
+ <line x1="2" y1="9" x2="22" y2="9" stroke="currentColor" strokeWidth="1.2" />
42
+ <line x1="2" y1="14" x2="22" y2="14" stroke="currentColor" strokeWidth="1.2" />
43
+ </svg>
44
+ );
45
+ }
46
+
47
+ function AscIcon() {
48
+ return (
49
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
50
+ <line x1="12" y1="19" x2="12" y2="5" />
51
+ <polyline points="5 12 12 5 19 12" />
52
+ </svg>
53
+ );
54
+ }
55
+
56
+ function DescIcon() {
57
+ return (
58
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
59
+ <line x1="12" y1="5" x2="12" y2="19" />
60
+ <polyline points="19 12 12 19 5 12" />
61
+ </svg>
62
+ );
63
+ }
64
+
65
+ function UnsortIcon() {
66
+ return (
67
+ <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
68
+ <line x1="5" y1="12" x2="19" y2="12" />
69
+ </svg>
70
+ );
71
+ }
72
+
73
+ function HideIcon() {
74
+ return (
75
+ <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
76
+ <path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94" />
77
+ <path d="M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19" />
78
+ <line x1="1" y1="1" x2="23" y2="23" />
79
+ </svg>
80
+ );
81
+ }
82
+
83
+ function PinLeftIcon() {
84
+ return (
85
+ <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
86
+ { }
87
+ <line x1="4" y1="2" x2="4" y2="22" />
88
+ { }
89
+ <polyline points="11 8 4 12 11 16" />
90
+ { }
91
+ <line x1="4" y1="12" x2="20" y2="12" />
92
+ </svg>
93
+ );
94
+ }
95
+
96
+ function PinRightIcon() {
97
+ return (
98
+ <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
99
+ { }
100
+ <line x1="20" y1="2" x2="20" y2="22" />
101
+ { }
102
+ <polyline points="13 8 20 12 13 16" />
103
+ { }
104
+ <line x1="20" y1="12" x2="4" y2="12" />
105
+ </svg>
106
+ );
107
+ }
108
+
109
+ function UnpinIcon() {
110
+ return (
111
+ <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
112
+ <line x1="2" y1="2" x2="22" y2="22" />
113
+ <line x1="4" y1="4" x2="4" y2="20" />
114
+ <line x1="4" y1="12" x2="20" y2="12" />
115
+ </svg>
116
+ );
117
+ }
118
+
119
+ export function ColumnMenu({ colDef, sortModel, onSort, onHide, onPin, pinnedColumns, onManageColumns, onClose, anchorEl }: ColumnMenuProps) {
120
+ const menuRef = useRef<HTMLDivElement>(null);
121
+ const [style, setStyle] = useState<React.CSSProperties>({ position: 'fixed', zIndex: 1300, top: 0, left: 0, visibility: 'hidden' });
122
+
123
+ React.useLayoutEffect(() => {
124
+ if (!anchorEl || !menuRef.current) return;
125
+
126
+ const anchorRect = anchorEl.getBoundingClientRect();
127
+ const menuRect = menuRef.current.getBoundingClientRect();
128
+ const vw = window.innerWidth;
129
+ const vh = window.innerHeight;
130
+ const margin = 8;
131
+
132
+ let left = anchorRect.left;
133
+ if (left + menuRect.width + margin > vw) {
134
+ left = vw - menuRect.width - margin;
135
+ }
136
+ if (left < margin) left = margin;
137
+
138
+ let top = anchorRect.bottom + 4;
139
+ if (top + menuRect.height + margin > vh) {
140
+ top = anchorRect.top - menuRect.height - 4;
141
+ }
142
+
143
+ setStyle({ position: 'fixed', zIndex: 1300, top, left, visibility: 'visible' });
144
+ }, [anchorEl]);
145
+
146
+
147
+ useEffect(() => {
148
+ function handleClickOutside(event: MouseEvent) {
149
+ if (
150
+ menuRef.current && !menuRef.current.contains(event.target as Node) &&
151
+ anchorEl && !anchorEl.contains(event.target as Node)
152
+ ) onClose();
153
+ }
154
+ function handleKey(e: KeyboardEvent) {
155
+ if (e.key === 'Escape') onClose();
156
+ }
157
+ document.addEventListener('mousedown', handleClickOutside);
158
+ document.addEventListener('keydown', handleKey);
159
+ // Close menu on any scroll or resize so it doesn't float detached from its anchor
160
+ window.addEventListener('scroll', onClose, true);
161
+ window.addEventListener('resize', onClose);
162
+ return () => {
163
+ document.removeEventListener('mousedown', handleClickOutside);
164
+ document.removeEventListener('keydown', handleKey);
165
+ window.removeEventListener('scroll', onClose, true);
166
+ window.removeEventListener('resize', onClose);
167
+ };
168
+ }, [anchorEl, onClose]);
169
+
170
+ const handleSort = (direction: GridSortDirection) => { onSort?.(colDef.field, direction); onClose(); };
171
+ const handleHide = () => { onHide?.(colDef.field); onClose(); };
172
+ const handlePin = (side: 'left' | 'right' | null) => { onPin?.(colDef.field, side); onClose(); };
173
+
174
+ const currentSort = sortModel?.find(item => item.field === colDef.field)?.sort;
175
+ const isUnsorted = currentSort === undefined || currentSort === null;
176
+
177
+ const isPinnedLeft = pinnedColumns?.left?.includes(colDef.field) ?? false;
178
+ const isPinnedRight = pinnedColumns?.right?.includes(colDef.field) ?? false;
179
+ const isUnpinned = !isPinnedLeft && !isPinnedRight;
180
+
181
+ const colLabel = colDef.headerName || colDef.field;
182
+
183
+ return ReactDOM.createPortal(
184
+ <div ref={menuRef} className="ogx-column-menu" style={style} role="menu" aria-label={`Column options for ${colLabel}`}>
185
+
186
+ { }
187
+ {onSort && colDef.sortable !== false && (
188
+ <>
189
+ <button className={`ogx-menu-item${currentSort === 'asc' ? ' ogx-menu-item--active' : ''}`}
190
+ onClick={() => handleSort('asc')} role="menuitemradio" aria-checked={currentSort === 'asc'}>
191
+ <span className="ogx-menu-item__indicator" aria-hidden="true">{currentSort === 'asc' ? <ActiveDotIcon /> : <InactiveDotIcon />}</span>
192
+ <span className="ogx-menu-item__icon" aria-hidden="true"><AscIcon /></span>
193
+ Sort Ascending
194
+ </button>
195
+ <button className={`ogx-menu-item${currentSort === 'desc' ? ' ogx-menu-item--active' : ''}`}
196
+ onClick={() => handleSort('desc')} role="menuitemradio" aria-checked={currentSort === 'desc'}>
197
+ <span className="ogx-menu-item__indicator" aria-hidden="true">{currentSort === 'desc' ? <ActiveDotIcon /> : <InactiveDotIcon />}</span>
198
+ <span className="ogx-menu-item__icon" aria-hidden="true"><DescIcon /></span>
199
+ Sort Descending
200
+ </button>
201
+ <button className={`ogx-menu-item${isUnsorted ? ' ogx-menu-item--active' : ''}`}
202
+ onClick={() => handleSort(null)} role="menuitemradio" aria-checked={isUnsorted}>
203
+ <span className="ogx-menu-item__indicator" aria-hidden="true">{isUnsorted ? <ActiveDotIcon /> : <InactiveDotIcon />}</span>
204
+ <span className="ogx-menu-item__icon" aria-hidden="true"><UnsortIcon /></span>
205
+ Unsort
206
+ </button>
207
+ <div className="ogx-menu-divider" />
208
+ </>
209
+ )}
210
+
211
+ { }
212
+ <div className="ogx-menu-section-label" aria-hidden="true">Pin column</div>
213
+ <button className={`ogx-menu-item${isPinnedLeft ? ' ogx-menu-item--active' : ''}`}
214
+ onClick={() => handlePin('left')} role="menuitemradio" aria-checked={isPinnedLeft} aria-label="Pin to left">
215
+ <span className="ogx-menu-item__indicator" aria-hidden="true">{isPinnedLeft ? <ActiveDotIcon /> : <InactiveDotIcon />}</span>
216
+ <span className="ogx-menu-item__icon" aria-hidden="true"><PinLeftIcon /></span>
217
+ Pin to Left
218
+ </button>
219
+ <button className={`ogx-menu-item${isPinnedRight ? ' ogx-menu-item--active' : ''}`}
220
+ onClick={() => handlePin('right')} role="menuitemradio" aria-checked={isPinnedRight} aria-label="Pin to right">
221
+ <span className="ogx-menu-item__indicator" aria-hidden="true">{isPinnedRight ? <ActiveDotIcon /> : <InactiveDotIcon />}</span>
222
+ <span className="ogx-menu-item__icon" aria-hidden="true"><PinRightIcon /></span>
223
+ Pin to Right
224
+ </button>
225
+ <button className={`ogx-menu-item${isUnpinned ? ' ogx-menu-item--active' : ''}`}
226
+ onClick={() => handlePin(null)} role="menuitemradio" aria-checked={isUnpinned} aria-label="Unpin column">
227
+ <span className="ogx-menu-item__indicator" aria-hidden="true">{isUnpinned ? <ActiveDotIcon /> : <InactiveDotIcon />}</span>
228
+ <span className="ogx-menu-item__icon" aria-hidden="true"><UnpinIcon /></span>
229
+ Unpin
230
+ </button>
231
+ <div className="ogx-menu-divider" />
232
+
233
+ { }
234
+ <button className="ogx-menu-item" onClick={handleHide} role="menuitem" aria-label={`Hide ${colLabel} column`}>
235
+ <span className="ogx-menu-item__indicator" aria-hidden="true" />
236
+ <span className="ogx-menu-item__icon" aria-hidden="true"><HideIcon /></span>
237
+ Hide Column
238
+ </button>
239
+
240
+ <div className="ogx-menu-divider" />
241
+ <button className="ogx-menu-item" onClick={() => { onManageColumns?.(); onClose(); }} role="menuitem" aria-label="Manage all columns">
242
+ <span className="ogx-menu-item__indicator" aria-hidden="true" />
243
+ <span className="ogx-menu-item__icon" aria-hidden="true"><ManageColumnsIcon /></span>
244
+ Manage columns
245
+ </button>
246
+ </div>,
247
+ (anchorEl?.closest('.ogx-theme-provider') || document.body)
248
+ );
249
+ }
@@ -0,0 +1,224 @@
1
+ /* Sticky wrapper — holds group header rows AND the column header row */
2
+ .ogx__header-wrap {
3
+ display: flex;
4
+ flex-direction: column;
5
+ position: sticky;
6
+ top: 0;
7
+ z-index: 10;
8
+ flex-shrink: 0;
9
+ }
10
+
11
+ .ogx__header {
12
+ display: flex;
13
+ align-items: stretch;
14
+ background: var(--ogx-grid-header-background);
15
+ border-bottom: 1px solid var(--ogx-grid-border-color);
16
+ min-height: var(--ogx-header-height);
17
+ box-shadow: var(--ogx-shadow-sm, 0 1px 2px rgba(0, 0, 0, 0.03));
18
+ }
19
+ .ogx__header-cell--expand {
20
+ width: 48px;
21
+ min-width: 48px;
22
+ flex-shrink: 0;
23
+ border-right: 1px solid transparent;
24
+ }
25
+ .ogx__header-cell {
26
+ display: flex;
27
+ align-items: center;
28
+ padding: var(--ogx-grid-cell-padding-y) var(--ogx-grid-cell-padding-x);
29
+ position: relative;
30
+ font-size: var(--ogx-grid-header-font-size);
31
+ font-weight: 600;
32
+ color: var(--ogx-grid-header-text);
33
+ overflow: hidden;
34
+ box-sizing: border-box;
35
+ flex-shrink: 0;
36
+ user-select: none;
37
+ transition: background-color 0.2s;
38
+ }
39
+ .ogx__header-cell:not(:last-child)::after {
40
+ content: '';
41
+ position: absolute;
42
+ right: 0;
43
+ top: 25%;
44
+ height: 50%;
45
+ width: 1px;
46
+ background-color: var(--ogx-color-gray-300);
47
+ opacity: 0.5;
48
+ }
49
+ .ogx__header-cell-content {
50
+ display: flex;
51
+ align-items: center;
52
+ gap: var(--ogx-spacing-xs);
53
+ width: 100%;
54
+ overflow: hidden;
55
+ }
56
+ .ogx__header-cell-title-wrapper {
57
+ display: flex;
58
+ flex-direction: column;
59
+ align-items: flex-start;
60
+ flex: 1;
61
+ overflow: hidden;
62
+ }
63
+ .ogx__header-cell-title {
64
+ overflow: hidden;
65
+ text-overflow: ellipsis;
66
+ white-space: nowrap;
67
+ width: 100%;
68
+ font-weight: 600;
69
+ letter-spacing: -0.01em;
70
+ }
71
+ .ogx__header-cell-aggregation {
72
+ font-size: 0.6rem;
73
+ font-weight: 500;
74
+ color: var(--ogx-color-gray-500);
75
+ text-transform: uppercase;
76
+ letter-spacing: 0.05em;
77
+ margin-top: 2px;
78
+ }
79
+ .ogx__header-cell--align-left {
80
+ justify-content: flex-start;
81
+ }
82
+ .ogx__header-cell--align-center {
83
+ justify-content: center;
84
+ }
85
+ .ogx__header-cell--align-right {
86
+ justify-content: flex-start;
87
+ }
88
+ /* Right-aligned header: reverse the content row so the title appears on the right */
89
+ .ogx__header-cell--align-center .ogx__header-cell-content {
90
+ text-align: center;
91
+ }
92
+ .ogx__header-cell--align-right .ogx__header-cell-content {
93
+ flex-direction: row-reverse;
94
+ }
95
+ .ogx__header-cell--align-right .ogx__header-cell-title-wrapper {
96
+ align-items: flex-end;
97
+ text-align: right;
98
+ }
99
+ .ogx__header-cell--align-right .ogx__menu-icon-btn {
100
+ margin-left: 0;
101
+ margin-right: auto;
102
+ }
103
+ .ogx__header-cell--sortable {
104
+ cursor: pointer;
105
+ }
106
+ .ogx__header-cell--sortable:hover {
107
+ background: var(--ogx-grid-header-hover-background);
108
+ }
109
+ .ogx__header-cell--sorted {
110
+ background: var(--ogx-grid-header-sorted-background);
111
+ color: var(--ogx-color-primary-dark);
112
+ }
113
+ .ogx__header-cell--sorted .ogx__header-cell-title {
114
+ color: var(--ogx-color-primary-dark);
115
+ }
116
+ .ogx__sort-icon {
117
+ display: inline-flex;
118
+ align-items: center;
119
+ justify-content: center;
120
+ width: 16px;
121
+ height: 16px;
122
+ margin-left: 4px;
123
+ flex-shrink: 0;
124
+ font-size: 14px;
125
+ line-height: 1;
126
+ }
127
+ .ogx__sort-icon::before {
128
+ transition: all 0.2s;
129
+ }
130
+ .ogx__sort-icon--asc::before {
131
+ content: '↑';
132
+ }
133
+ .ogx__sort-icon--desc::before {
134
+ content: '↓';
135
+ }
136
+ .ogx__header-cell--checkbox, .ogx__cell--checkbox {
137
+ width: 48px;
138
+ min-width: 48px;
139
+ max-width: 48px;
140
+ flex: 0 0 48px;
141
+ justify-content: center;
142
+ padding: 0;
143
+ }
144
+ .ogx__header-cell--pinned {
145
+ background: var(--ogx-grid-header-background);
146
+ z-index: 12;
147
+ }
148
+ .ogx__header-cell--pinned-left {
149
+ box-shadow: var(--ogx-grid-pinned-left-shadow, 4px 0 12px -4px rgba(0, 0, 0, 0.05));
150
+ }
151
+ .ogx__header-cell--pinned-right {
152
+ box-shadow: var(--ogx-grid-pinned-right-shadow, -4px 0 12px -4px rgba(0, 0, 0, 0.05));
153
+ }
154
+ .ogx__header-cell--pinned::after {
155
+ display: none;
156
+ }
157
+ .ogx__header-cell--dragging {
158
+ cursor: move !important;
159
+ opacity: 0.5;
160
+ background: var(--ogx-color-gray-200);
161
+ }
162
+ .ogx__header-drag-icon {
163
+ display: flex;
164
+ align-items: center;
165
+ justify-content: center;
166
+ margin-right: 8px;
167
+ color: var(--ogx-color-primary);
168
+ animation: ogx-pulse 1.5s infinite ease-in-out;
169
+ }
170
+ @keyframes ogx-pulse {
171
+ 0% { transform: scale(1); }
172
+ 50% { transform: scale(1.1); }
173
+ 100% { transform: scale(1); }
174
+ }
175
+ .ogx__header-cell--drag-over {
176
+ border-left: 2px solid var(--ogx-color-primary) !important;
177
+ background: var(--ogx-color-blue-50);
178
+ }
179
+ /* Show ring on header cells during keyboard navigation */
180
+ .ogx--kb .ogx__header-cell--focused {
181
+ outline: 2px solid var(--ogx-color-primary, #2563eb);
182
+ outline-offset: -2px;
183
+ background-color: var(--ogx-grid-header-hover-background);
184
+ z-index: 6;
185
+ }
186
+ .ogx__header-cell--focus-visible {
187
+ outline: 2px solid var(--ogx-color-primary);
188
+ outline-offset: -2px;
189
+ background-color: var(--ogx-grid-header-hover-background);
190
+ z-index: 6;
191
+ }
192
+ .ogx__header-cell:focus, .ogx__header-cell:focus-visible {
193
+ outline: none !important;
194
+ }
195
+ .ogx__menu-icon-btn {
196
+ display: inline-flex;
197
+ align-items: center;
198
+ justify-content: center;
199
+ flex-shrink: 0;
200
+ width: 26px;
201
+ height: 26px;
202
+ padding: 0;
203
+ border: none;
204
+ background: transparent;
205
+ border-radius: 4px;
206
+ color: var(--ogx-color-gray-500);
207
+ cursor: pointer;
208
+ opacity: 0;
209
+ transition: opacity 0.15s ease, background 0.15s ease, color 0.15s ease;
210
+ margin-left: auto;
211
+ }
212
+ .ogx__header-cell:hover .ogx__menu-icon-btn,
213
+ .ogx__header-cell--sorted .ogx__menu-icon-btn {
214
+ opacity: 1;
215
+ }
216
+ .ogx__menu-icon-btn:hover {
217
+ background: var(--ogx-grid-header-hover-background);
218
+ color: var(--ogx-grid-header-text);
219
+ }
220
+ .ogx__menu-icon-btn--active {
221
+ opacity: 1;
222
+ background: var(--ogx-color-primary-light);
223
+ color: var(--ogx-color-primary);
224
+ }