@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,67 @@
1
+ /* ─── Group header row ───────────────────────────────────────────────────────
2
+ These rows live INSIDE .ogx__header-wrap, which is the single sticky block,
3
+ so they scroll horizontally together with the column header row below them.
4
+ Widths are computed dynamically from the live columnWidths state, so they
5
+ stay in sync with column resizing.
6
+ ────────────────────────────────────────────────────────────────────────────── */
7
+ .ogx-col-group-row {
8
+ display: flex;
9
+ align-items: stretch;
10
+ background: var(--ogx-grid-header-background);
11
+ border-bottom: 1px solid var(--ogx-grid-border-color);
12
+ overflow: hidden;
13
+ flex-shrink: 0;
14
+ min-height: var(--ogx-header-height, 48px);
15
+ }
16
+
17
+ /* ─── Individual group cell ──────────────────────────────────────────────────*/
18
+ .ogx-col-group-cell {
19
+ display: flex;
20
+ align-items: center;
21
+ justify-content: center;
22
+ flex-shrink: 0;
23
+ box-sizing: border-box;
24
+ position: relative;
25
+ overflow: hidden;
26
+ /* Same border UI as primary header cells: half-height right divider */
27
+ }
28
+ .ogx-col-group-cell:not(:last-child)::after {
29
+ content: '';
30
+ position: absolute;
31
+ right: 0;
32
+ top: 25%;
33
+ height: 50%;
34
+ width: 1px;
35
+ background-color: var(--ogx-color-gray-300, #cbd5e1);
36
+ opacity: 0.5;
37
+ }
38
+
39
+ /* ─── Group cell (has a label) ───────────────────────────────────────────────*/
40
+ .ogx-col-group-cell--group {
41
+ background: transparent;
42
+ }
43
+
44
+ /* ─── Filler cell (ungrouped / prefix spacer) ────────────────────────────────*/
45
+ .ogx-col-group-cell--filler {
46
+ background: transparent;
47
+ }
48
+
49
+ /* ─── Label text ─────────────────────────────────────────────────────────────*/
50
+ .ogx-col-group-cell__label {
51
+ font-size: 0.75rem;
52
+ font-weight: 600;
53
+ color: var(--ogx-color-gray-700, #334155);
54
+ padding: 0 16px;
55
+ white-space: nowrap;
56
+ overflow: hidden;
57
+ text-overflow: ellipsis;
58
+ pointer-events: none;
59
+ text-align: center;
60
+ width: 100%;
61
+ }
62
+
63
+ /* ─── Dark-mode overrides ────────────────────────────────────────────────────*/
64
+ [data-theme="dark"] .ogx-col-group-cell__label,
65
+ .ogx--dark .ogx-col-group-cell__label {
66
+ color: var(--ogx-color-gray-300, #cbd5e1);
67
+ }
@@ -0,0 +1,220 @@
1
+ import type { GridColumnGroup, GridColumnGroupingModel, GridColDef, GridRowModel } from '../../types';
2
+ import './ColumnGroupHeader.css';
3
+
4
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
5
+
6
+ /** All leaf field names reachable from a group node (recursive). */
7
+ function collectFields(group: GridColumnGroup): string[] {
8
+ const fields: string[] = [];
9
+ for (const child of group.children) {
10
+ if (typeof child === 'string') {
11
+ fields.push(child);
12
+ } else {
13
+ fields.push(...collectFields(child));
14
+ }
15
+ }
16
+ return fields;
17
+ }
18
+
19
+ /** Maximum nesting depth of any group tree. */
20
+ function maxDepth(groups: GridColumnGroupingModel): number {
21
+ let d = 0;
22
+ function walk(g: GridColumnGroup, depth: number) {
23
+ d = Math.max(d, depth);
24
+ for (const child of g.children) {
25
+ if (typeof child !== 'string') walk(child, depth + 1);
26
+ }
27
+ }
28
+ groups.forEach(g => walk(g, 1));
29
+ return d;
30
+ }
31
+
32
+ /**
33
+ * Flattened cell descriptor for one row of group headers.
34
+ * `span` is the pixel width, `level` is 0-indexed depth.
35
+ */
36
+ interface GroupCell {
37
+ key: string;
38
+ label: string;
39
+ width: number; // total pixel width spanned
40
+ isGroup: boolean; // false → empty "filler" cell for ungrouped columns
41
+ groupId?: string;
42
+ headerClassName?: string;
43
+ }
44
+
45
+ /** Build one row of GroupCell descriptors for a given depth level. */
46
+ function buildRow(
47
+ groups: GridColumnGroupingModel,
48
+ leafColumns: string[],
49
+ columnWidths: Record<string, number>,
50
+ level: number,
51
+ ): GroupCell[] {
52
+ const cells: GroupCell[] = [];
53
+
54
+ // Assign each leaf column to the group (at `level`) that contains it, if any.
55
+ // Consecutive columns in the same group merge into one cell.
56
+
57
+ // Build a map: field → group-at-this-level
58
+ const fieldToGroup = new Map<string, GridColumnGroup | null>();
59
+
60
+ function findGroupAtLevel(
61
+ groups: GridColumnGroupingModel,
62
+ field: string,
63
+ currentLevel: number,
64
+ ): GridColumnGroup | null {
65
+ for (const g of groups) {
66
+ const leafFields = collectFields(g);
67
+ if (!leafFields.includes(field)) continue;
68
+ if (currentLevel === level) return g;
69
+ // Go deeper
70
+ for (const child of g.children) {
71
+ if (typeof child !== 'string') {
72
+ const found = findGroupAtLevel([child], field, currentLevel + 1);
73
+ if (found) return found;
74
+ }
75
+ }
76
+ return null;
77
+ }
78
+ return null;
79
+ }
80
+
81
+ for (const f of leafColumns) {
82
+ fieldToGroup.set(f, findGroupAtLevel(groups, f, 0));
83
+ }
84
+
85
+ // Merge consecutive columns belonging to the same group
86
+ let i = 0;
87
+ while (i < leafColumns.length) {
88
+ const field = leafColumns[i];
89
+ const group = fieldToGroup.get(field) ?? null;
90
+
91
+ if (!group) {
92
+ // Ungrouped column — filler cell spanning just this column
93
+ cells.push({
94
+ key: `filler-${field}-${level}`,
95
+ label: '',
96
+ width: columnWidths[field] ?? 100,
97
+ isGroup: false,
98
+ });
99
+ i++;
100
+ } else {
101
+ // Collect all consecutive columns that belong to the same group instance
102
+ let totalWidth = 0;
103
+ let j = i;
104
+ while (j < leafColumns.length && fieldToGroup.get(leafColumns[j]) === group) {
105
+ totalWidth += columnWidths[leafColumns[j]] ?? 100;
106
+ j++;
107
+ }
108
+ cells.push({
109
+ key: `${group.groupId}-${level}`,
110
+ label: group.headerName,
111
+ width: totalWidth,
112
+ isGroup: true,
113
+ groupId: group.groupId,
114
+ headerClassName: group.headerClassName,
115
+ });
116
+ i = j;
117
+ }
118
+ }
119
+
120
+ return cells;
121
+ }
122
+
123
+ // ─── Component ────────────────────────────────────────────────────────────────
124
+
125
+ export interface ColumnGroupHeaderProps<R extends GridRowModel = GridRowModel> {
126
+ columnGroupingModel: GridColumnGroupingModel;
127
+ /** The ordered, effective columns (already filtered for visibility). */
128
+ columns: GridColDef<R>[];
129
+ /** Resolved pixel widths for every column. */
130
+ columnWidths: Record<string, number>;
131
+ /** Whether a checkbox column is prepended. */
132
+ checkboxSelection?: boolean;
133
+ /** Whether a row-reorder handle column is prepended. */
134
+ rowReordering?: boolean;
135
+ /** Whether a detail-panel expand column is prepended. */
136
+ hasDetailPanel?: boolean;
137
+ /** height (px) of each group header row */
138
+ groupHeaderHeight?: number;
139
+ }
140
+
141
+ export function ColumnGroupHeader<R extends GridRowModel = GridRowModel>({
142
+ columnGroupingModel,
143
+ columns,
144
+ columnWidths,
145
+ checkboxSelection = false,
146
+ rowReordering = false,
147
+ hasDetailPanel = false,
148
+ groupHeaderHeight = 36,
149
+ }: ColumnGroupHeaderProps<R>) {
150
+
151
+ const levels = maxDepth(columnGroupingModel);
152
+ if (levels === 0 || columns.length === 0) return null;
153
+
154
+ // Ordered leaf fields (visible columns only)
155
+ const leafFields = columns.map(c => c.field);
156
+
157
+ // Width of any prepended utility columns
158
+ const CHECKBOX_W = 52;
159
+ const REORDER_W = 40;
160
+ const EXPAND_W = 52;
161
+ const prefixWidth =
162
+ (checkboxSelection ? CHECKBOX_W : 0) +
163
+ (rowReordering ? REORDER_W : 0) +
164
+ (hasDetailPanel ? EXPAND_W : 0);
165
+
166
+ return (
167
+ <>
168
+ {Array.from({ length: levels }, (_, level) => {
169
+ const row = buildRow(
170
+ columnGroupingModel,
171
+ leafFields,
172
+ columnWidths,
173
+ level,
174
+ );
175
+
176
+ return (
177
+ <div
178
+ key={`group-row-${level}`}
179
+ className="ogx-col-group-row"
180
+ style={{ height: groupHeaderHeight }}
181
+ role="row"
182
+ aria-rowindex={level + 1}
183
+ >
184
+ {/* Spacer for checkbox / reorder / expand columns */}
185
+ {prefixWidth > 0 && (
186
+ <div
187
+ className="ogx-col-group-cell ogx-col-group-cell--filler"
188
+ style={{ width: prefixWidth, minWidth: prefixWidth }}
189
+ role="columnheader"
190
+ />
191
+ )}
192
+
193
+ {row.map((cell) => (
194
+ <div
195
+ key={cell.key}
196
+ role="columnheader"
197
+ aria-colspan={undefined /* pixel-based layout */}
198
+ className={[
199
+ 'ogx-col-group-cell',
200
+ cell.isGroup
201
+ ? 'ogx-col-group-cell--group'
202
+ : 'ogx-col-group-cell--filler',
203
+ cell.headerClassName ?? '',
204
+ ].join(' ').trim()}
205
+ style={{ width: cell.width, minWidth: cell.width }}
206
+ title={cell.isGroup ? cell.label : undefined}
207
+ >
208
+ {cell.isGroup && (
209
+ <span className="ogx-col-group-cell__label">
210
+ {cell.label}
211
+ </span>
212
+ )}
213
+ </div>
214
+ ))}
215
+ </div>
216
+ );
217
+ })}
218
+ </>
219
+ );
220
+ }
@@ -0,0 +1,31 @@
1
+ .ogx-column-resize-handle {
2
+ position: absolute;
3
+ top: 0;
4
+ right: -4px;
5
+ width: 8px;
6
+ height: 100%;
7
+ cursor: col-resize;
8
+ z-index: 10;
9
+ display: flex;
10
+ align-items: center;
11
+ justify-content: center;
12
+ user-select: none;
13
+ }
14
+ .ogx-column-resize-handle:hover .ogx-column-resize-handle__line,
15
+ .ogx-column-resize-handle--dragging .ogx-column-resize-handle__line {
16
+ background: var(--ogx-color-primary);
17
+ opacity: 1;
18
+ }
19
+ .ogx-column-resize-handle__line {
20
+ width: 2px;
21
+ height: 100%;
22
+ background: var(--ogx-color-gray-300);
23
+ opacity: 0;
24
+ transition: opacity var(--ogx-transition-duration-fast) var(--ogx-transition-easing);
25
+ }
26
+ .ogx-column-resize-handle--dragging {
27
+ cursor: col-resize;
28
+ }
29
+ .ogx__header-cell:hover .ogx-column-resize-handle__line {
30
+ opacity: 0.5;
31
+ }
@@ -0,0 +1,85 @@
1
+
2
+ import React, { useCallback, useRef, useState } from 'react';
3
+
4
+
5
+ export interface ColumnResizeHandleProps {
6
+ field: string;
7
+ currentWidth: number;
8
+ onResize: (field: string, newWidth: number) => void;
9
+ minWidth?: number;
10
+ maxWidth?: number;
11
+ }
12
+
13
+ export function ColumnResizeHandle(props: ColumnResizeHandleProps) {
14
+ const { field, currentWidth, onResize, minWidth = 50, maxWidth = 1000 } = props;
15
+ const [isDragging, setIsDragging] = useState(false);
16
+ const startXRef = useRef<number>(0);
17
+ const startWidthRef = useRef<number>(0);
18
+
19
+ const handleMouseDown = useCallback((event: React.MouseEvent) => {
20
+ event.preventDefault();
21
+ event.stopPropagation();
22
+
23
+ setIsDragging(true);
24
+ startXRef.current = event.clientX;
25
+ // Use the logical stored width — reliable for pinned, flex, and normal columns
26
+ startWidthRef.current = currentWidth;
27
+
28
+ let lastUpdateTime = 0;
29
+ const throttleMs = 16;
30
+
31
+ const handleMouseMove = (e: MouseEvent) => {
32
+ const deltaX = e.clientX - startXRef.current;
33
+ const newWidth = Math.max(minWidth, Math.min(maxWidth, startWidthRef.current + deltaX));
34
+
35
+ const now = Date.now();
36
+ if (now - lastUpdateTime >= throttleMs) {
37
+ onResize(field, newWidth);
38
+ lastUpdateTime = now;
39
+ }
40
+ };
41
+
42
+ const handleMouseUp = (e: MouseEvent) => {
43
+ setIsDragging(false);
44
+
45
+ const deltaX = e.clientX - startXRef.current;
46
+ const newWidth = Math.max(minWidth, Math.min(maxWidth, startWidthRef.current + deltaX));
47
+
48
+ onResize(field, newWidth);
49
+
50
+ document.removeEventListener('mousemove', handleMouseMove);
51
+ document.removeEventListener('mouseup', handleMouseUp);
52
+ };
53
+
54
+ document.addEventListener('mousemove', handleMouseMove);
55
+ document.addEventListener('mouseup', handleMouseUp);
56
+ }, [field, currentWidth, onResize, minWidth, maxWidth]);
57
+
58
+ const handleDoubleClick = useCallback((event: React.MouseEvent) => {
59
+ event.preventDefault();
60
+ event.stopPropagation();
61
+ }, []);
62
+
63
+ const classNames = [
64
+ 'ogx-column-resize-handle',
65
+ isDragging && 'ogx-column-resize-handle--dragging'
66
+ ].filter(Boolean).join(' ');
67
+
68
+ return (
69
+ <div
70
+ className={classNames}
71
+ onMouseDown={handleMouseDown}
72
+ onClick={(e) => e.stopPropagation()}
73
+ onDragStart={(e) => {
74
+ e.preventDefault();
75
+ e.stopPropagation();
76
+ }}
77
+ onDoubleClick={handleDoubleClick}
78
+ role="separator"
79
+ aria-orientation="vertical"
80
+ aria-label={`Resize ${field} column`}
81
+ >
82
+ <div className="ogx-column-resize-handle__line" />
83
+ </div>
84
+ );
85
+ }
@@ -0,0 +1,137 @@
1
+ .ogx-column-visibility-panel {
2
+ background: var(--ogx-overlay-background, var(--ogx-color-white));
3
+ border: var(--ogx-border-width-thin) solid var(--ogx-overlay-border, var(--ogx-border-color));
4
+ border-radius: var(--ogx-border-radius-sm);
5
+ min-width: 250px;
6
+ max-width: 350px;
7
+ box-shadow: var(--ogx-shadow-lg);
8
+ display: flex;
9
+ flex-direction: column;
10
+ }
11
+
12
+ .ogx-column-visibility-panel__search-section {
13
+ padding: var(--ogx-spacing-sm) var(--ogx-spacing-md);
14
+ border-bottom: var(--ogx-border-width-thin) solid var(--ogx-overlay-border, var(--ogx-border-color));
15
+ }
16
+
17
+ .ogx-column-visibility-panel__search-container {
18
+ display: flex;
19
+ align-items: center;
20
+ padding: 6px var(--ogx-spacing-sm);
21
+ border: 2px solid var(--ogx-overlay-border, var(--ogx-color-primary));
22
+ border-radius: var(--ogx-border-radius-md);
23
+ gap: var(--ogx-spacing-sm);
24
+ color: var(--ogx-overlay-text, var(--ogx-color-gray-500));
25
+ transition: border-color var(--ogx-transition-duration-fast) var(--ogx-transition-easing);
26
+ }
27
+
28
+ .ogx-column-visibility-panel__search-container:focus-within {
29
+ border-color: var(--ogx-color-primary-dark);
30
+ }
31
+
32
+ .ogx-column-visibility-panel__search-input {
33
+ border: none;
34
+ background: none;
35
+ outline: none;
36
+ font-family: inherit;
37
+ font-size: var(--ogx-font-size-md);
38
+ color: var(--ogx-overlay-text, var(--ogx-color-gray-900));
39
+ flex: 1;
40
+ }
41
+
42
+ .ogx-column-visibility-panel__search-input::placeholder {
43
+ color: var(--ogx-overlay-text, var(--ogx-color-gray-400));
44
+ opacity: 0.6;
45
+ }
46
+
47
+ .ogx-column-visibility-panel__list {
48
+ max-height: 350px;
49
+ overflow-y: auto;
50
+ padding: var(--ogx-spacing-sm) 0;
51
+ }
52
+
53
+ .ogx-column-visibility-panel__item {
54
+ display: flex;
55
+ align-items: center;
56
+ gap: var(--ogx-spacing-sm);
57
+ padding: var(--ogx-spacing-xs) var(--ogx-spacing-sm) var(--ogx-spacing-xs) var(--ogx-spacing-xs);
58
+ transition: background var(--ogx-transition-duration-fast) var(--ogx-transition-easing);
59
+ user-select: none;
60
+ }
61
+
62
+ .ogx-column-visibility-panel__item:hover:not(.ogx-column-visibility-panel__item--disabled) {
63
+ background: var(--ogx-overlay-item-hover-bg, var(--ogx-color-gray-50));
64
+ }
65
+
66
+ .ogx-column-visibility-panel__item--drag-over {
67
+ background: var(--ogx-color-primary-light, rgba(59, 130, 246, 0.08));
68
+ border-top: 2px solid var(--ogx-color-primary);
69
+ }
70
+
71
+ .ogx-column-visibility-panel__item--disabled {
72
+ opacity: 0.5;
73
+ }
74
+
75
+ /* ── Drag handle ──────────────────────────────────────────────────────────── */
76
+ .ogx-column-visibility-panel__drag-handle-wrapper {
77
+ display: flex;
78
+ align-items: center;
79
+ justify-content: center;
80
+ flex-shrink: 0;
81
+ width: 20px;
82
+ height: 100%;
83
+ cursor: grab;
84
+ color: var(--ogx-overlay-text, var(--ogx-color-gray-400));
85
+ border-radius: 3px;
86
+ opacity: 0.6;
87
+ transition: color var(--ogx-transition-duration-fast) var(--ogx-transition-easing),
88
+ background var(--ogx-transition-duration-fast) var(--ogx-transition-easing),
89
+ opacity var(--ogx-transition-duration-fast) var(--ogx-transition-easing);
90
+ }
91
+
92
+ .ogx-column-visibility-panel__drag-handle-wrapper:hover {
93
+ opacity: 1;
94
+ color: var(--ogx-overlay-text, var(--ogx-color-gray-600));
95
+ background: var(--ogx-overlay-item-hover-bg, var(--ogx-color-gray-100));
96
+ }
97
+
98
+ .ogx-column-visibility-panel__drag-handle-wrapper:active {
99
+ cursor: grabbing;
100
+ color: var(--ogx-color-primary);
101
+ }
102
+
103
+ .ogx-column-visibility-panel__drag-handle {
104
+ display: block;
105
+ pointer-events: none;
106
+ }
107
+
108
+ .ogx-column-visibility-panel__label {
109
+ font-size: var(--ogx-font-size-xs);
110
+ color: var(--ogx-overlay-text, var(--ogx-color-gray-900));
111
+ user-select: none;
112
+ }
113
+
114
+ .ogx-column-visibility-panel__footer {
115
+ border-top: var(--ogx-border-width-thin) solid var(--ogx-overlay-border, var(--ogx-border-color));
116
+ padding: var(--ogx-spacing-sm);
117
+ display: flex;
118
+ justify-content: space-between;
119
+ align-items: center;
120
+ }
121
+
122
+ .ogx-column-visibility-panel__reset-btn {
123
+ color: var(--ogx-overlay-text, var(--ogx-color-gray-400));
124
+ background: none;
125
+ border: none;
126
+ font-family: inherit;
127
+ font-weight: 600;
128
+ font-size: 13px;
129
+ letter-spacing: 0.05em;
130
+ text-transform: uppercase;
131
+ cursor: pointer;
132
+ transition: color var(--ogx-transition-duration-fast) var(--ogx-transition-easing);
133
+ }
134
+
135
+ .ogx-column-visibility-panel__reset-btn:hover {
136
+ color: var(--ogx-color-primary, var(--ogx-color-gray-600));
137
+ }