@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.
- package/CHANGELOG.md +15 -0
- package/README.md +9 -0
- package/dist/index.d.ts +6 -0
- package/dist/opengridx.es.js +6 -6
- package/dist/opengridx.es.js.map +1 -1
- package/dist/opengridx.umd.js +4 -4
- package/dist/opengridx.umd.js.map +1 -1
- package/docs/API_REFERENCE.md +177 -0
- package/docs/README.md +60 -0
- package/docs/components/cell.md +44 -0
- package/docs/components/column-group-header.md +57 -0
- package/docs/components/column-resize.md +63 -0
- package/docs/components/column-visibility.md +61 -0
- package/docs/components/datagrid.md +70 -0
- package/docs/components/filter-panel.md +26 -0
- package/docs/components/header.md +39 -0
- package/docs/components/pagination.md +34 -0
- package/docs/components/row.md +31 -0
- package/docs/components/toolbar.md +40 -0
- package/docs/components/tooltip.md +68 -0
- package/docs/customization/slots-api.md +227 -0
- package/docs/customization/theming.md +293 -0
- package/docs/features/aggregation-pivot.md +79 -0
- package/docs/features/cell-spanning.md +520 -0
- package/docs/features/clipboard.md +76 -0
- package/docs/features/custom-pagination.md +143 -0
- package/docs/features/data-source.md +72 -0
- package/docs/features/editing-reordering.md +99 -0
- package/docs/features/export-guide.md +434 -0
- package/docs/features/filtering.md +83 -0
- package/docs/features/infinite-scroll.md +87 -0
- package/docs/features/list-view.md +65 -0
- package/docs/features/loading-states.md +68 -0
- package/docs/features/master-detail.md +46 -0
- package/docs/features/pinning.md +67 -0
- package/docs/features/selection.md +70 -0
- package/docs/features/sorting-pagination.md +66 -0
- package/docs/features/state-persistence.md +67 -0
- package/docs/features/tree-data-grouping.md +67 -0
- package/docs/features/virtualization.md +188 -0
- package/docs/roadmap.md +71 -0
- package/lib/components/Cell/Cell.css +119 -0
- package/lib/components/Cell/Cell.tsx +264 -0
- package/lib/components/Cell/GridEditInputCell.css +62 -0
- package/lib/components/Cell/GridEditInputCell.tsx +152 -0
- package/lib/components/ColumnGroupHeader/ColumnGroupHeader.css +67 -0
- package/lib/components/ColumnGroupHeader/ColumnGroupHeader.tsx +220 -0
- package/lib/components/ColumnResizeHandle/ColumnResizeHandle.css +31 -0
- package/lib/components/ColumnResizeHandle/ColumnResizeHandle.tsx +85 -0
- package/lib/components/ColumnVisibilityPanel/ColumnVisibilityPanel.css +137 -0
- package/lib/components/ColumnVisibilityPanel/ColumnVisibilityPanel.tsx +190 -0
- package/lib/components/DataGrid/DataGrid.css +224 -0
- package/lib/components/DataGrid/DataGrid.tsx +2253 -0
- package/lib/components/DetailPanel/DetailPanel.css +36 -0
- package/lib/components/DetailPanel/DetailPanel.tsx +34 -0
- package/lib/components/FilterPanel/FilterPanel.css +196 -0
- package/lib/components/FilterPanel/FilterPanel.tsx +183 -0
- package/lib/components/FilterPanel/index.ts +1 -0
- package/lib/components/Header/ColumnMenu.css +68 -0
- package/lib/components/Header/ColumnMenu.tsx +249 -0
- package/lib/components/Header/Header.css +224 -0
- package/lib/components/Header/Header.tsx +516 -0
- package/lib/components/ListView/ListView.css +112 -0
- package/lib/components/ListView/ListViewRow.tsx +63 -0
- package/lib/components/Pagination/Pagination.css +91 -0
- package/lib/components/Pagination/Pagination.tsx +151 -0
- package/lib/components/QuickFilter/QuickFilter.css +23 -0
- package/lib/components/QuickFilter/QuickFilter.tsx +79 -0
- package/lib/components/Row/Row.css +126 -0
- package/lib/components/Row/Row.tsx +377 -0
- package/lib/components/Skeleton/Skeleton.css +55 -0
- package/lib/components/Skeleton/Skeleton.tsx +22 -0
- package/lib/components/SkeletonRow/SkeletonRow.css +32 -0
- package/lib/components/SkeletonRow/SkeletonRow.tsx +89 -0
- package/lib/components/SkeletonRow/index.ts +1 -0
- package/lib/components/Toolbar/GlobalSearch.css +113 -0
- package/lib/components/Toolbar/GlobalSearch.tsx +120 -0
- package/lib/components/Toolbar/GridToolbar.css +204 -0
- package/lib/components/Toolbar/GridToolbar.tsx +647 -0
- package/lib/components/Toolbar/PivotPanel.css +212 -0
- package/lib/components/Toolbar/PivotPanel.tsx +239 -0
- package/lib/components/Tooltip/Tooltip.css +59 -0
- package/lib/components/Tooltip/Tooltip.tsx +106 -0
- package/lib/components/ui/Button.css +89 -0
- package/lib/components/ui/Button.tsx +39 -0
- package/lib/components/ui/Checkbox.css +71 -0
- package/lib/components/ui/Checkbox.tsx +66 -0
- package/lib/components/ui/DragHandleIcon.css +19 -0
- package/lib/components/ui/DragHandleIcon.tsx +18 -0
- package/lib/components/ui/ExpandIcon.css +38 -0
- package/lib/components/ui/ExpandIcon.tsx +75 -0
- package/lib/components/ui/Input.css +59 -0
- package/lib/components/ui/Input.tsx +45 -0
- package/lib/components/ui/index.ts +15 -0
- package/lib/hooks/core/useDataGrid.ts +392 -0
- package/lib/hooks/core/useGridApiRef.ts +10 -0
- package/lib/hooks/features/useAggregation.ts +143 -0
- package/lib/hooks/features/useGridClipboard.ts +133 -0
- package/lib/hooks/features/useGridDataSource.ts +190 -0
- package/lib/hooks/features/useGridEditing.ts +100 -0
- package/lib/hooks/features/useGridSpanning.ts +349 -0
- package/lib/hooks/features/usePivot.ts +149 -0
- package/lib/hooks/useColumnReorder.ts +114 -0
- package/lib/hooks/useRowGrouping.ts +302 -0
- package/lib/hooks/useRowReorder.ts +83 -0
- package/lib/hooks/useTreeData.ts +323 -0
- package/lib/index.ts +106 -0
- package/lib/state/index.ts +17 -0
- package/lib/state/types.ts +48 -0
- package/lib/state/useGridStateStorage.ts +113 -0
- package/lib/styles/base/reset.css +21 -0
- package/lib/styles/base/variables.css +161 -0
- package/lib/styles/opengridx.css +27 -0
- package/lib/styles/opengridx.css.d.ts +4 -0
- package/lib/theme/DataGridThemeProvider.tsx +276 -0
- package/lib/theme/defaultStyles.ts +0 -0
- package/lib/theme/index.ts +21 -0
- package/lib/theme/presets.ts +148 -0
- package/lib/theme/types.ts +155 -0
- package/lib/types/index.ts +764 -0
- package/lib/utils/export/exportToExcelAdvanced.ts +516 -0
- package/lib/utils/export/index.ts +550 -0
- package/lib/utils/filtering/index.ts +240 -0
- package/lib/utils/pinning/index.ts +282 -0
- package/lib/utils/sorting/index.ts +55 -0
- package/lib/utils/virtualization/index.ts +106 -0
- package/lib/vite-env.d.ts +1 -0
- package/llms.txt +2 -1
- 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
|
+
}
|