@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,377 @@
1
+
2
+ import React from 'react';
3
+ import { Cell } from '../Cell/Cell';
4
+ import { Checkbox } from '../ui/Checkbox';
5
+ import { ExpandIcon } from '../ui/ExpandIcon';
6
+ import { DragHandleIcon } from '../ui/DragHandleIcon';
7
+ import { DetailPanel } from '../DetailPanel/DetailPanel';
8
+ import type { GridRowModel, GridColDef, GridRowId, GridColumnPinning, GridRowPinning, GridRowParams, GridCellParams, GridDetailPanelHeight } from '../../types';
9
+ import type { CellColSpanInfo, RowSpanningCaches } from '../../hooks/features/useGridSpanning';
10
+ import { isColumnPinned, calculatePinnedPositions, isRowPinned } from '../../utils/pinning';
11
+
12
+
13
+ export interface RowProps<R extends GridRowModel = GridRowModel> {
14
+ row: R;
15
+ columns: GridColDef<R>[];
16
+ rowIndex: number;
17
+ isSelected?: boolean;
18
+ checkboxSelection?: boolean;
19
+ onRowClick?: (params: GridRowParams<R>) => void;
20
+ onCellClick?: (params: GridCellParams<R>) => void;
21
+ onSelectionChange?: (rowId: GridRowId, isSelected: boolean) => void;
22
+ columnWidths?: Record<string, number>;
23
+ pinnedColumns?: GridColumnPinning;
24
+ pinnedRows?: GridRowPinning;
25
+
26
+ hasDetailPanel?: boolean;
27
+ isDetailPanelExpanded?: boolean;
28
+ onDetailPanelToggle?: (rowId: GridRowId) => void;
29
+ detailPanelContent?: React.ReactNode;
30
+ detailPanelHeight?: GridDetailPanelHeight;
31
+
32
+ pinCheckboxColumn?: boolean;
33
+ pinExpandColumn?: boolean;
34
+
35
+ rowReordering?: boolean;
36
+ onDragStart?: (id: GridRowId) => (event: React.DragEvent) => void;
37
+ onDragOver?: (id: GridRowId) => (event: React.DragEvent) => void;
38
+ onDragEnd?: () => void;
39
+ onDrop?: (targetId: GridRowId) => (event: React.DragEvent) => void;
40
+ isDragging?: boolean;
41
+ isDragOver?: boolean;
42
+
43
+ editingCell?: { id: GridRowId; field: string; value: any; } | null;
44
+ onEditStart?: (params: { id: GridRowId, field: string, value: any }) => void;
45
+ onEditStop?: (params?: { cancel?: boolean }) => void;
46
+ onEditCellValueChange?: (params: { id: GridRowId, field: string, value: any }) => void;
47
+
48
+ focusedCellField?: string | null;
49
+ isFocusVisible?: boolean;
50
+
51
+ colspanMap?: Map<GridRowId, Record<string, CellColSpanInfo>>;
52
+ rowSpanningCaches?: RowSpanningCaches;
53
+ rowHeight?: number;
54
+ }
55
+
56
+ export function Row<R extends GridRowModel = GridRowModel>(props: RowProps<R>) {
57
+ const {
58
+ row,
59
+ columns,
60
+ rowIndex,
61
+ isSelected = false,
62
+ checkboxSelection = false,
63
+ onRowClick,
64
+ onCellClick,
65
+ onSelectionChange,
66
+ columnWidths = {},
67
+ pinnedColumns,
68
+ pinnedRows,
69
+ hasDetailPanel = false,
70
+ isDetailPanelExpanded = false,
71
+ onDetailPanelToggle,
72
+ detailPanelContent,
73
+ detailPanelHeight = 200,
74
+ pinCheckboxColumn = true,
75
+ pinExpandColumn = true,
76
+ rowReordering = false,
77
+ onDragStart,
78
+ onDragOver,
79
+ onDragEnd,
80
+ onDrop,
81
+ isDragging,
82
+ isDragOver,
83
+
84
+ editingCell,
85
+ onEditStart,
86
+ onEditStop,
87
+ onEditCellValueChange,
88
+
89
+ focusedCellField,
90
+ isFocusVisible,
91
+
92
+ colspanMap,
93
+ rowSpanningCaches,
94
+ rowHeight = 52
95
+ } = props;
96
+
97
+ const expandCellRef = React.useRef<HTMLDivElement>(null);
98
+ const checkboxCellRef = React.useRef<HTMLDivElement>(null);
99
+
100
+ // ── Skeleton row (shown during infinite-scroll fetch) ─────────────────────
101
+ if ((row as any)._isSkeleton) {
102
+ // Render shimmer placeholders — one per visible column, matching row height
103
+ const cellCount = Math.max(columns.length, 5);
104
+ return (
105
+ <div
106
+ className="ogx__row ogx__row--skeleton"
107
+ role="row"
108
+ aria-hidden="true"
109
+ style={{ height: rowHeight, minHeight: rowHeight }}
110
+ >
111
+ {Array.from({ length: cellCount }).map((_, i) => (
112
+ <div key={i} className="ogx__skeleton-cell">
113
+ <div className="ogx__skeleton-bar" />
114
+ </div>
115
+ ))}
116
+ </div>
117
+ );
118
+ }
119
+
120
+ React.useLayoutEffect(() => {
121
+ if (focusedCellField === '__expand_col__' && expandCellRef.current) {
122
+ expandCellRef.current.focus({ preventScroll: true });
123
+ }
124
+ if (focusedCellField === '__checkbox_col__' && checkboxCellRef.current) {
125
+ checkboxCellRef.current.focus({ preventScroll: true });
126
+ }
127
+ }, [focusedCellField]);
128
+
129
+ const handleRowClick = (event: React.MouseEvent) => {
130
+
131
+ if ((event.target as HTMLElement).closest('.ogx-checkbox-wrapper, .ogx-expand-icon, .ogx-drag-handle, .ogx__edit-cell')) {
132
+ return;
133
+ }
134
+ onRowClick?.({ row, id: row.id, rowIndex });
135
+ };
136
+
137
+ const handleCheckboxChange = (event: React.ChangeEvent<HTMLInputElement>) => {
138
+ event.stopPropagation();
139
+ onSelectionChange?.(row.id, event.target.checked);
140
+ };
141
+
142
+ const handleDetailPanelToggle = (event: React.MouseEvent) => {
143
+ event.stopPropagation();
144
+ onDetailPanelToggle?.(row.id);
145
+ };
146
+
147
+ const pinnedPositions = React.useMemo(() => {
148
+ return calculatePinnedPositions(
149
+ columns,
150
+ columnWidths,
151
+ pinnedColumns,
152
+ checkboxSelection,
153
+ pinCheckboxColumn,
154
+ hasDetailPanel,
155
+ pinExpandColumn,
156
+ rowReordering
157
+ );
158
+ }, [columns, columnWidths, pinnedColumns, checkboxSelection, pinCheckboxColumn, hasDetailPanel, pinExpandColumn, rowReordering]);
159
+
160
+ const rowPinnedPosition = isRowPinned(row.id, pinnedRows);
161
+ const isRowPinnedTop = rowPinnedPosition === 'top';
162
+ const isRowPinnedBottom = rowPinnedPosition === 'bottom';
163
+
164
+ const isGroupRow = (row as any)._hasChildren === true;
165
+
166
+ const handleCellEditStart = React.useCallback((field: string, value: any) => {
167
+ if (!isGroupRow) {
168
+ onEditStart?.({ id: row.id, field, value });
169
+ }
170
+ }, [isGroupRow, row.id, onEditStart]);
171
+
172
+ const handleCellValueChange = React.useCallback((field: string, newValue: any) => {
173
+ onEditCellValueChange?.({ id: row.id, field, value: newValue });
174
+ }, [onEditCellValueChange, row.id]);
175
+
176
+ const handleEditStopWrapper = React.useCallback((cancel?: boolean) => {
177
+ onEditStop?.({ cancel });
178
+ }, [onEditStop]);
179
+
180
+ const classNames = [
181
+ 'ogx__row',
182
+ isSelected && 'ogx__row--selected',
183
+ rowIndex % 2 === 0 && 'ogx__row--even',
184
+ rowIndex % 2 === 1 && 'ogx__row--odd',
185
+ isRowPinnedTop && 'ogx__row--pinned-top',
186
+ isRowPinnedBottom && 'ogx__row--pinned-bottom',
187
+ isDragging && 'ogx__row--dragging',
188
+ isDragOver && 'ogx__row--drag-over',
189
+ isGroupRow && 'ogx__row--group'
190
+ ].filter(Boolean).join(' ');
191
+
192
+ return (
193
+ <>
194
+ <div
195
+ className={classNames}
196
+ onClick={handleRowClick}
197
+ role="row"
198
+ aria-rowindex={rowIndex + 1}
199
+ aria-selected={isSelected}
200
+ data-rowindex={rowIndex}
201
+ onDragOver={onDragOver ? onDragOver(row.id) : undefined}
202
+ onDrop={onDrop ? onDrop(row.id) : undefined}
203
+ style={{
204
+ maxHeight: `${rowHeight}px`,
205
+ minHeight: `${rowHeight}px`,
206
+ '--height': `${rowHeight}px`,
207
+ '--border-width': '1px'
208
+ } as React.CSSProperties}
209
+ >
210
+ { }
211
+ {rowReordering && (
212
+ <div
213
+ className="ogx__cell ogx__cell--drag-handle"
214
+ role="gridcell"
215
+ aria-label="Drag to reorder row"
216
+ style={{
217
+ position: 'sticky',
218
+ left: 0,
219
+ zIndex: 4
220
+ }}
221
+ draggable={true}
222
+ onDragStart={onDragStart ? onDragStart(row.id) : undefined}
223
+ onDragEnd={onDragEnd}
224
+ onClick={(e) => e.stopPropagation()}
225
+ >
226
+ <DragHandleIcon />
227
+ </div>
228
+ )}
229
+
230
+ { }
231
+ {hasDetailPanel && (
232
+ <div
233
+ className={`ogx__cell ogx__cell--expand ${pinExpandColumn ? 'ogx__cell--pinned-left' : ''
234
+ } ${focusedCellField === '__expand_col__' ? 'ogx__cell--focused' : ''} ${(focusedCellField === '__expand_col__' && isFocusVisible) ? 'ogx__cell--focus-visible' : ''
235
+ }`}
236
+ role="gridcell"
237
+ aria-label={isDetailPanelExpanded ? 'Collapse row details' : 'Expand row details'}
238
+ aria-expanded={isDetailPanelExpanded}
239
+ style={{
240
+ position: pinExpandColumn ? 'sticky' : undefined,
241
+ left: pinExpandColumn ? (rowReordering ? 48 : 0) : undefined,
242
+ zIndex: pinExpandColumn ? 5 : undefined
243
+ }}
244
+ ref={expandCellRef}
245
+ tabIndex={-1}
246
+ onClick={(e) => {
247
+ e.stopPropagation();
248
+ onCellClick?.({
249
+ row,
250
+ field: '__expand_col__',
251
+ value: isDetailPanelExpanded,
252
+ colDef: { field: '__expand_col__', width: 48 } as any,
253
+ rowIndex,
254
+ colIndex: -1
255
+ });
256
+ }}
257
+ >
258
+ <ExpandIcon
259
+ isExpanded={isDetailPanelExpanded}
260
+ onClick={handleDetailPanelToggle}
261
+ variant="plus-minus"
262
+ />
263
+ </div>
264
+ )}
265
+
266
+ { }
267
+ {checkboxSelection && (
268
+ <div
269
+ className={`ogx__cell ogx__cell--checkbox ${pinCheckboxColumn ? 'ogx__cell--pinned-left' : ''
270
+ } ${focusedCellField === '__checkbox_col__' ? 'ogx__cell--focused' : ''} ${(focusedCellField === '__checkbox_col__' && isFocusVisible) ? 'ogx__cell--focus-visible' : ''
271
+ }`}
272
+ role="gridcell"
273
+ style={{
274
+ position: pinCheckboxColumn ? 'sticky' : undefined,
275
+ left: pinCheckboxColumn ? ((rowReordering ? 48 : 0) + (hasDetailPanel && pinExpandColumn ? 48 : 0)) : undefined,
276
+ zIndex: pinCheckboxColumn ? 11 : undefined
277
+ }}
278
+ ref={checkboxCellRef}
279
+ tabIndex={-1}
280
+ onClick={(e) => {
281
+ e.stopPropagation();
282
+ onCellClick?.({
283
+ row,
284
+ field: '__checkbox_col__',
285
+ value: isSelected,
286
+ colDef: { field: '__checkbox_col__', width: 48 } as any,
287
+ rowIndex,
288
+ colIndex: -1
289
+ });
290
+ }}
291
+ >
292
+ <Checkbox
293
+ id={`ogx-select-row-${row.id}`}
294
+ name={`ogx-select-row-${row.id}`}
295
+ checked={isSelected}
296
+ onChange={handleCheckboxChange}
297
+ onClick={(e) => e.stopPropagation()}
298
+ aria-label={isSelected ? `Deselect row ${row.id}` : `Select row ${row.id}`}
299
+ tabIndex={-1}
300
+ onMouseDown={(e) => e.preventDefault()}
301
+ />
302
+ </div>
303
+ )}
304
+
305
+ { }
306
+ {columns.map((colDef, colIndex) => {
307
+ if ((colDef as any).isSpacer) {
308
+ return (
309
+ <div
310
+ key={colDef.field}
311
+ style={{ width: colDef.width, minWidth: colDef.width, flexShrink: 0 }}
312
+ aria-hidden="true"
313
+ />
314
+ );
315
+ }
316
+ const value = colDef.valueGetter
317
+ ? colDef.valueGetter({ row, field: colDef.field, value: row[colDef.field] })
318
+ : row[colDef.field];
319
+
320
+ const effectiveWidth = columnWidths[colDef.field] ?? colDef.width;
321
+
322
+ const pinnedPosition = isColumnPinned(colDef.field, pinnedColumns);
323
+ const pinnedOffset = pinnedPosition ? pinnedPositions[colDef.field] : undefined;
324
+
325
+ const isEditing = editingCell?.id === row.id && editingCell?.field === colDef.field;
326
+
327
+ const cellValue = isEditing ? editingCell?.value : value;
328
+
329
+ const colSpanInfo = colspanMap?.get(row.id)?.[colDef.field];
330
+ const rowSpan = rowSpanningCaches?.spannedCells[row.id]?.[colDef.field];
331
+ const isHiddenByRowSpan = rowSpanningCaches?.hiddenCells[row.id]?.[colDef.field] || false;
332
+
333
+ return (
334
+ <Cell
335
+ key={colDef.field}
336
+ value={cellValue}
337
+ row={row}
338
+ colDef={colDef}
339
+ rowIndex={rowIndex}
340
+ colIndex={colIndex}
341
+ isSelected={isSelected}
342
+ onCellClick={onCellClick}
343
+ width={effectiveWidth}
344
+ pinnedPosition={pinnedPosition}
345
+ pinnedOffset={pinnedOffset}
346
+
347
+ isFocused={focusedCellField === colDef.field}
348
+ isFocusVisible={isFocusVisible}
349
+
350
+ isEditable={colDef.editable}
351
+ isEditing={isEditing}
352
+ onCellEditStart={handleCellEditStart}
353
+ onEditStop={handleEditStopWrapper}
354
+ onCellValueChange={handleCellValueChange}
355
+
356
+ colSpanInfo={colSpanInfo}
357
+ rowSpan={rowSpan}
358
+ isHiddenByRowSpan={isHiddenByRowSpan}
359
+ />
360
+ );
361
+ })}
362
+ </div>
363
+
364
+ { }
365
+ {hasDetailPanel && (
366
+ <DetailPanel
367
+ row={row}
368
+ rowId={row.id}
369
+ rowIndex={rowIndex}
370
+ content={detailPanelContent}
371
+ height={detailPanelHeight}
372
+ isExpanded={isDetailPanelExpanded}
373
+ />
374
+ )}
375
+ </>
376
+ );
377
+ }
@@ -0,0 +1,55 @@
1
+ .ogx__skeleton {
2
+ display: flex;
3
+ flex-direction: column;
4
+ width: 100%;
5
+ }
6
+ .ogx__skeleton-row {
7
+ display: flex;
8
+ align-items: center;
9
+ min-height: var(--ogx-row-height);
10
+ border-bottom: 1px solid var(--ogx-grid-border-color);
11
+ padding: var(--ogx-grid-cell-padding-y) var(--ogx-grid-cell-padding-x);
12
+ background-color: var(--ogx-grid-background);
13
+ }
14
+ .ogx__skeleton-cell {
15
+ flex: 1;
16
+ margin-right: 16px;
17
+ }
18
+ .ogx__skeleton-cell:last-child {
19
+ margin-right: 0;
20
+ }
21
+ .ogx__skeleton-content {
22
+ height: 16px;
23
+ background: linear-gradient(
24
+ 90deg,
25
+ var(--ogx-skeleton-base) 0%,
26
+ var(--ogx-skeleton-highlight) 50%,
27
+ var(--ogx-skeleton-base) 100%
28
+ );
29
+ background-size: 200% 100%;
30
+ border-radius: 4px;
31
+ animation: skeleton-loading 1.5s ease-in-out infinite;
32
+ }
33
+ .ogx__skeleton-cell:nth-child(1) .ogx__skeleton-content {
34
+ width: 60%;
35
+ }
36
+ .ogx__skeleton-cell:nth-child(2) .ogx__skeleton-content {
37
+ width: 80%;
38
+ }
39
+ .ogx__skeleton-cell:nth-child(3) .ogx__skeleton-content {
40
+ width: 70%;
41
+ }
42
+ .ogx__skeleton-cell:nth-child(4) .ogx__skeleton-content {
43
+ width: 90%;
44
+ }
45
+ .ogx__skeleton-cell:nth-child(5) .ogx__skeleton-content {
46
+ width: 75%;
47
+ }
48
+ @keyframes skeleton-loading {
49
+ 0% {
50
+ background-position: 200% 0;
51
+ }
52
+ 100% {
53
+ background-position: -200% 0;
54
+ }
55
+ }
@@ -0,0 +1,22 @@
1
+
2
+
3
+ export interface SkeletonProps {
4
+ rows?: number;
5
+ columns?: number;
6
+ }
7
+
8
+ export function Skeleton({ rows = 5, columns = 5 }: SkeletonProps) {
9
+ return (
10
+ <div className="ogx__skeleton">
11
+ {Array.from({ length: rows }).map((_, rowIndex) => (
12
+ <div key={rowIndex} className="ogx__skeleton-row">
13
+ {Array.from({ length: columns }).map((_, colIndex) => (
14
+ <div key={colIndex} className="ogx__skeleton-cell">
15
+ <div className="ogx__skeleton-content" />
16
+ </div>
17
+ ))}
18
+ </div>
19
+ ))}
20
+ </div>
21
+ );
22
+ }
@@ -0,0 +1,32 @@
1
+ .ogx__skeleton-row {
2
+ display: flex;
3
+ align-items: center;
4
+ border-bottom: 1px solid var(--ogx-grid-border-color);
5
+ background-color: var(--ogx-grid-background);
6
+ }
7
+ .ogx__skeleton-cell {
8
+ display: flex;
9
+ align-items: center;
10
+ padding: 0 16px;
11
+ box-sizing: border-box;
12
+ overflow: hidden;
13
+ }
14
+ .ogx-skeleton-box {
15
+ background: linear-gradient(
16
+ 90deg,
17
+ var(--ogx-skeleton-base) 0%,
18
+ var(--ogx-skeleton-highlight) 50%,
19
+ var(--ogx-skeleton-base) 100%
20
+ );
21
+ background-size: 200% 100%;
22
+ animation: skeleton-loading 1.5s ease-in-out infinite;
23
+ border-radius: 4px;
24
+ }
25
+ @keyframes skeleton-loading {
26
+ 0% {
27
+ background-position: 200% 0;
28
+ }
29
+ 100% {
30
+ background-position: -200% 0;
31
+ }
32
+ }
@@ -0,0 +1,89 @@
1
+ import React from 'react';
2
+
3
+
4
+ interface SkeletonRowProps {
5
+ columns: any[];
6
+ rowHeight: number;
7
+ checkboxSelection?: boolean;
8
+ hasDetailPanel?: boolean;
9
+ rowReordering?: boolean;
10
+ }
11
+
12
+ export const SkeletonRow: React.FC<SkeletonRowProps> = ({
13
+ columns,
14
+ rowHeight,
15
+ checkboxSelection = false,
16
+ hasDetailPanel = false,
17
+ rowReordering = false
18
+ }) => {
19
+ return (
20
+ <div
21
+ className="ogx__row ogx__skeleton-row"
22
+ style={{ height: rowHeight }}
23
+ role="row"
24
+ >
25
+ { }
26
+ {checkboxSelection && (
27
+ <div
28
+ className="ogx__cell ogx__skeleton-cell"
29
+ style={{ width: 48, minWidth: 48 }}
30
+ >
31
+ <div className="ogx-skeleton-box" style={{ width: 18, height: 18, borderRadius: 2 }} />
32
+ </div>
33
+ )}
34
+
35
+ { }
36
+ {hasDetailPanel && (
37
+ <div
38
+ className="ogx__cell ogx__skeleton-cell"
39
+ style={{ width: 48, minWidth: 48 }}
40
+ >
41
+ <div className="ogx-skeleton-box" style={{ width: 18, height: 18, borderRadius: 2 }} />
42
+ </div>
43
+ )}
44
+
45
+ { }
46
+ {rowReordering && (
47
+ <div
48
+ className="ogx__cell ogx__skeleton-cell"
49
+ style={{ width: 48, minWidth: 48 }}
50
+ >
51
+ <div className="ogx-skeleton-box" style={{ width: 18, height: 18, borderRadius: 2 }} />
52
+ </div>
53
+ )}
54
+
55
+ { }
56
+ {columns.map((col, index) => {
57
+ const width = col.width || 150;
58
+ const skeletonWidth = Math.min(width * 0.7, width - 20);
59
+
60
+ return (
61
+ <div
62
+ key={col.field || index}
63
+ className="ogx__cell ogx__skeleton-cell"
64
+ style={{
65
+ width,
66
+ minWidth: col.minWidth,
67
+ maxWidth: col.maxWidth,
68
+ left: col.left,
69
+ right: col.right,
70
+ position: col.isSpacer ? 'relative' : (col.pinned ? 'sticky' : 'relative'),
71
+ zIndex: col.zIndex
72
+ }}
73
+ >
74
+ {!col.isSpacer && (
75
+ <div
76
+ className="ogx-skeleton-box"
77
+ style={{
78
+ width: skeletonWidth,
79
+ height: 16,
80
+ borderRadius: 4
81
+ }}
82
+ />
83
+ )}
84
+ </div>
85
+ );
86
+ })}
87
+ </div>
88
+ );
89
+ };
@@ -0,0 +1 @@
1
+ export { SkeletonRow } from './SkeletonRow';
@@ -0,0 +1,113 @@
1
+ /* ─── Container ─────────────────────────────────────────────────────────────
2
+ Always in normal flex flow so the toolbar spacer absorbs expansion,
3
+ keeping every icon pinned to the right edge.
4
+ ─────────────────────────────────────────────────────────────────────────── */
5
+ .ogx-global-search {
6
+ display: inline-flex;
7
+ align-items: center;
8
+ border-radius: 8px;
9
+ border: 1.5px solid transparent;
10
+ background: transparent;
11
+ width: 34px;
12
+ height: 34px;
13
+ flex-shrink: 0;
14
+ cursor: pointer;
15
+ overflow: hidden; /* clips invisible input while collapsed */
16
+ /* Animate width + visual props — NO position changes, so focus is stable */
17
+ transition: width 0.25s cubic-bezier(0.4, 0, 0.2, 1),
18
+ background 0.2s ease,
19
+ border-color 0.2s ease,
20
+ box-shadow 0.2s ease;
21
+ }
22
+
23
+ /* hover feedback only when collapsed */
24
+ .ogx-global-search:not(.ogx-global-search--expanded):hover {
25
+ background: var(--ogx-grid-header-hover-background, #f1f5f9);
26
+ }
27
+
28
+ /* ─── Expanded state ────────────────────────────────────────────────────────
29
+ Width grows leftwards into the spacer; icons stay where they are.
30
+ overflow must stay `hidden` so the blue border-radius is respected.
31
+ ─────────────────────────────────────────────────────────────────────────── */
32
+ .ogx-global-search--expanded {
33
+ width: 220px;
34
+ border-color: var(--ogx-toolbar-input-focus-border, var(--ogx-color-primary, #3b82f6));
35
+ background: var(--ogx-toolbar-input-bg, var(--ogx-grid-background, #fff));
36
+ box-shadow: var(--ogx-toolbar-input-focus-shadow, 0 0 0 3px rgba(59, 130, 246, 0.12));
37
+ cursor: default;
38
+ overflow: hidden;
39
+ }
40
+
41
+ /* ─── Icon wrapper ────────────────────────────────────────────────────────── */
42
+ .ogx-global-search__icon-wrapper {
43
+ /* Fixed 34×34 square — never shrinks so the icon is always centred */
44
+ flex: 0 0 34px;
45
+ width: 34px;
46
+ height: 34px;
47
+ display: flex;
48
+ align-items: center;
49
+ justify-content: center;
50
+ color: var(--ogx-grid-header-text, #64748b);
51
+ pointer-events: none;
52
+ flex-shrink: 0;
53
+ transition: color 0.15s;
54
+ /* No border, no background — the unit looks seamless */
55
+ }
56
+ .ogx-global-search--expanded .ogx-global-search__icon-wrapper {
57
+ color: var(--ogx-color-primary, #3b82f6);
58
+ }
59
+
60
+ /* Focus-within: tint icon blue whenever the input is focused */
61
+ .ogx-global-search:focus-within .ogx-global-search__icon-wrapper {
62
+ color: var(--ogx-color-primary, #3b82f6);
63
+ }
64
+
65
+ /* ─── Input ───────────────────────────────────────────────────────────────── */
66
+ .ogx-global-search__input {
67
+ flex: 1 1 auto;
68
+ min-width: 0;
69
+ height: 100%;
70
+ padding: 0 28px 0 0; /* right pad for clear button, NO left border */
71
+ border: none;
72
+ background: transparent;
73
+ font-family: var(--ogx-font-family, system-ui, sans-serif);
74
+ font-size: var(--ogx-font-size-sm, 13px);
75
+ color: var(--ogx-grid-row-text, #1e293b);
76
+ outline: none;
77
+ /* Hide with opacity, not display:none, so focus events still fire */
78
+ opacity: 0;
79
+ pointer-events: none;
80
+ transition: opacity 0.18s ease;
81
+ }
82
+ .ogx-global-search--expanded .ogx-global-search__input {
83
+ opacity: 1;
84
+ pointer-events: auto;
85
+ }
86
+ .ogx-global-search__input::placeholder {
87
+ color: var(--ogx-color-gray-400, #94a3b8);
88
+ font-size: var(--ogx-font-size-sm, 13px);
89
+ }
90
+
91
+ /* ─── Clear button ────────────────────────────────────────────────────────── */
92
+ .ogx-global-search__clear {
93
+ position: absolute;
94
+ right: 6px;
95
+ top: 50%;
96
+ transform: translateY(-50%);
97
+ background: transparent;
98
+ border: none;
99
+ color: var(--ogx-grid-header-text, #94a3b8);
100
+ cursor: pointer;
101
+ padding: 4px;
102
+ border-radius: 50%;
103
+ display: flex;
104
+ align-items: center;
105
+ justify-content: center;
106
+ z-index: 3;
107
+ line-height: 0;
108
+ transition: background 0.15s, color 0.15s;
109
+ }
110
+ .ogx-global-search__clear:hover {
111
+ background: rgba(239, 68, 68, 0.08);
112
+ color: #ef4444;
113
+ }