@idealyst/datagrid 1.2.32 → 1.2.33
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/README.md +2 -1
- package/package.json +5 -5
- package/src/DataGrid/DataGrid.tsx +258 -54
- package/src/DataGrid/types.ts +4 -0
- package/src/examples/DataGridShowcase.tsx +34 -5
package/README.md
CHANGED
|
@@ -170,6 +170,7 @@ function SortableDataGrid() {
|
|
|
170
170
|
| `onRowClick` | `(row: T, index: number) => void` | - | Row click handler |
|
|
171
171
|
| `onSelectionChange` | `(rows: number[]) => void` | - | Selection change handler |
|
|
172
172
|
| `onSort` | `(column: Column, direction: 'asc' \| 'desc') => void` | - | Sort handler |
|
|
173
|
+
| `onColumnResize` | `(columnKey: string, width: number) => void` | - | Column resize handler (web only) |
|
|
173
174
|
| `style` | `ViewStyle` | - | Container style |
|
|
174
175
|
| `headerStyle` | `ViewStyle` | - | Header style |
|
|
175
176
|
| `rowStyle` | `ViewStyle \| ((row: T, index: number) => ViewStyle)` | - | Row style |
|
|
@@ -184,7 +185,7 @@ function SortableDataGrid() {
|
|
|
184
185
|
| `minWidth` | `number` | Minimum column width |
|
|
185
186
|
| `maxWidth` | `number` | Maximum column width |
|
|
186
187
|
| `sortable` | `boolean` | Enable sorting for this column |
|
|
187
|
-
| `resizable` | `boolean` | Enable resizing (
|
|
188
|
+
| `resizable` | `boolean` | Enable column resizing (web only) |
|
|
188
189
|
| `accessor` | `(row: T) => any` | Custom value accessor |
|
|
189
190
|
| `render` | `(value: any, row: T, index: number) => ReactNode` | Custom cell renderer |
|
|
190
191
|
| `headerStyle` | `ViewStyle` | Header cell style |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@idealyst/datagrid",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.33",
|
|
4
4
|
"description": "High-performance datagrid component for React and React Native",
|
|
5
5
|
"documentation": "https://github.com/IdealystIO/idealyst-framework/tree/main/packages/datagrid#readme",
|
|
6
6
|
"readme": "README.md",
|
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
"publish:npm": "npm publish"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
|
-
"@idealyst/components": "^1.2.
|
|
40
|
-
"@idealyst/theme": "^1.2.
|
|
39
|
+
"@idealyst/components": "^1.2.33",
|
|
40
|
+
"@idealyst/theme": "^1.2.33",
|
|
41
41
|
"react": ">=16.8.0",
|
|
42
42
|
"react-native": ">=0.60.0",
|
|
43
43
|
"react-native-unistyles": "^3.0.4",
|
|
@@ -61,8 +61,8 @@
|
|
|
61
61
|
}
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
|
-
"@idealyst/components": "^1.2.
|
|
65
|
-
"@idealyst/theme": "^1.2.
|
|
64
|
+
"@idealyst/components": "^1.2.33",
|
|
65
|
+
"@idealyst/theme": "^1.2.33",
|
|
66
66
|
"@types/react": "^19.1.0",
|
|
67
67
|
"@types/react-window": "^1.8.8",
|
|
68
68
|
"react": "^19.1.0",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { useState, useCallback, useMemo } from 'react';
|
|
1
|
+
import React, { useState, useCallback, useMemo, useRef, useEffect } from 'react';
|
|
2
2
|
import { Platform } from 'react-native';
|
|
3
3
|
import { View, Text } from '@idealyst/components';
|
|
4
4
|
import { ScrollView } from '../primitives/ScrollView';
|
|
@@ -13,6 +13,8 @@ export function DataGrid<T extends Record<string, any>>({
|
|
|
13
13
|
headerHeight = 56,
|
|
14
14
|
onRowClick,
|
|
15
15
|
onSort,
|
|
16
|
+
onColumnResize,
|
|
17
|
+
columnResizeMode = 'indicator',
|
|
16
18
|
virtualized = true,
|
|
17
19
|
height = 400,
|
|
18
20
|
width = '100%',
|
|
@@ -29,12 +31,123 @@ export function DataGrid<T extends Record<string, any>>({
|
|
|
29
31
|
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('asc');
|
|
30
32
|
const [scrollTop, setScrollTop] = useState(0);
|
|
31
33
|
|
|
34
|
+
// Column resize state
|
|
35
|
+
const [columnWidths, setColumnWidths] = useState<Record<string, number>>(() => {
|
|
36
|
+
const widths: Record<string, number> = {};
|
|
37
|
+
columns.forEach(col => {
|
|
38
|
+
widths[col.key] = col.width || col.minWidth || 120;
|
|
39
|
+
});
|
|
40
|
+
return widths;
|
|
41
|
+
});
|
|
42
|
+
const [isResizing, setIsResizing] = useState(false);
|
|
43
|
+
const resizeRef = useRef<{ columnKey: string; startX: number; startWidth: number } | null>(null);
|
|
44
|
+
const indicatorRef = useRef<HTMLDivElement>(null);
|
|
45
|
+
const columnWidthsRef = useRef(columnWidths);
|
|
46
|
+
|
|
47
|
+
// Keep ref in sync with state
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
columnWidthsRef.current = columnWidths;
|
|
50
|
+
}, [columnWidths]);
|
|
51
|
+
|
|
52
|
+
// Sync column widths when columns prop changes
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
setColumnWidths(prev => {
|
|
55
|
+
const widths: Record<string, number> = {};
|
|
56
|
+
columns.forEach(col => {
|
|
57
|
+
// Keep existing width if we have one, otherwise use column definition
|
|
58
|
+
widths[col.key] = prev[col.key] ?? col.width ?? col.minWidth ?? 120;
|
|
59
|
+
});
|
|
60
|
+
return widths;
|
|
61
|
+
});
|
|
62
|
+
}, [columns]);
|
|
63
|
+
|
|
64
|
+
// Calculate indicator position without triggering re-render
|
|
65
|
+
const calculateIndicatorPosition = useCallback((currentX: number) => {
|
|
66
|
+
if (!resizeRef.current) return 0;
|
|
67
|
+
const { columnKey, startX, startWidth } = resizeRef.current;
|
|
68
|
+
const column = columns.find(c => c.key === columnKey);
|
|
69
|
+
const minW = column?.minWidth || 50;
|
|
70
|
+
const maxW = column?.maxWidth || Infinity;
|
|
71
|
+
const delta = currentX - startX;
|
|
72
|
+
const newWidth = Math.min(maxW, Math.max(minW, startWidth + delta));
|
|
73
|
+
|
|
74
|
+
// Calculate X position: sum of widths of columns before this one + new width
|
|
75
|
+
let xPos = 0;
|
|
76
|
+
for (const col of columns) {
|
|
77
|
+
if (col.key === columnKey) {
|
|
78
|
+
xPos += newWidth;
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
xPos += columnWidthsRef.current[col.key] || col.width || 120;
|
|
82
|
+
}
|
|
83
|
+
return xPos;
|
|
84
|
+
}, [columns]);
|
|
85
|
+
|
|
86
|
+
// Handle resize mouse events - uses DOM manipulation for indicator mode, state for live mode
|
|
87
|
+
const handleResizeStart = useCallback((e: React.MouseEvent, columnKey: string) => {
|
|
88
|
+
e.preventDefault();
|
|
89
|
+
e.stopPropagation();
|
|
90
|
+
const startWidth = columnWidthsRef.current[columnKey] || 120;
|
|
91
|
+
resizeRef.current = { columnKey, startX: e.clientX, startWidth };
|
|
92
|
+
setIsResizing(true);
|
|
93
|
+
|
|
94
|
+
// Set initial indicator position (indicator mode only)
|
|
95
|
+
if (columnResizeMode === 'indicator' && indicatorRef.current) {
|
|
96
|
+
indicatorRef.current.style.left = `${calculateIndicatorPosition(e.clientX)}px`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const handleMouseMove = (moveEvent: MouseEvent) => {
|
|
100
|
+
if (!resizeRef.current) return;
|
|
101
|
+
|
|
102
|
+
if (columnResizeMode === 'live') {
|
|
103
|
+
// Live mode: update column width state during drag
|
|
104
|
+
const { columnKey: key, startX, startWidth: sw } = resizeRef.current;
|
|
105
|
+
const column = columns.find(c => c.key === key);
|
|
106
|
+
const minW = column?.minWidth || 50;
|
|
107
|
+
const maxW = column?.maxWidth || Infinity;
|
|
108
|
+
const delta = moveEvent.clientX - startX;
|
|
109
|
+
const newWidth = Math.min(maxW, Math.max(minW, sw + delta));
|
|
110
|
+
setColumnWidths(prev => ({ ...prev, [key]: newWidth }));
|
|
111
|
+
} else {
|
|
112
|
+
// Indicator mode: update indicator position directly in DOM - no React re-render
|
|
113
|
+
if (indicatorRef.current) {
|
|
114
|
+
indicatorRef.current.style.left = `${calculateIndicatorPosition(moveEvent.clientX)}px`;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const handleMouseUp = (upEvent: MouseEvent) => {
|
|
120
|
+
if (resizeRef.current) {
|
|
121
|
+
const { columnKey: key, startX, startWidth: sw } = resizeRef.current;
|
|
122
|
+
const column = columns.find(c => c.key === key);
|
|
123
|
+
const minW = column?.minWidth || 50;
|
|
124
|
+
const maxW = column?.maxWidth || Infinity;
|
|
125
|
+
const delta = upEvent.clientX - startX;
|
|
126
|
+
const newWidth = Math.min(maxW, Math.max(minW, sw + delta));
|
|
127
|
+
|
|
128
|
+
// Apply the new width on mouse up (indicator mode) or just finalize (live mode)
|
|
129
|
+
if (columnResizeMode === 'indicator') {
|
|
130
|
+
setColumnWidths(prev => ({ ...prev, [key]: newWidth }));
|
|
131
|
+
}
|
|
132
|
+
// onColumnResize is always called on release, regardless of mode
|
|
133
|
+
onColumnResize?.(key, newWidth);
|
|
134
|
+
}
|
|
135
|
+
resizeRef.current = null;
|
|
136
|
+
setIsResizing(false);
|
|
137
|
+
document.removeEventListener('mousemove', handleMouseMove);
|
|
138
|
+
document.removeEventListener('mouseup', handleMouseUp);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
document.addEventListener('mousemove', handleMouseMove);
|
|
142
|
+
document.addEventListener('mouseup', handleMouseUp);
|
|
143
|
+
}, [columns, onColumnResize, calculateIndicatorPosition, columnResizeMode]);
|
|
144
|
+
|
|
32
145
|
// Calculate minimum table width for horizontal scrolling
|
|
33
146
|
const minTableWidth = useMemo(() => {
|
|
34
147
|
return columns.reduce((total, column) => {
|
|
35
|
-
return total + (column.
|
|
148
|
+
return total + (columnWidths[column.key] || column.width || 120);
|
|
36
149
|
}, 0);
|
|
37
|
-
}, [columns]);
|
|
150
|
+
}, [columns, columnWidths]);
|
|
38
151
|
|
|
39
152
|
// Virtualization calculations
|
|
40
153
|
const visibleRange = useMemo(() => {
|
|
@@ -43,7 +156,7 @@ export function DataGrid<T extends Record<string, any>>({
|
|
|
43
156
|
}
|
|
44
157
|
|
|
45
158
|
const containerHeight = height - headerHeight;
|
|
46
|
-
const overscan =
|
|
159
|
+
const overscan = 5; // Render extra rows above and below visible area to prevent flickering
|
|
47
160
|
|
|
48
161
|
// Calculate the raw start index based on scroll position
|
|
49
162
|
const rawStartIndex = Math.floor(scrollTop / rowHeight);
|
|
@@ -80,8 +193,8 @@ export function DataGrid<T extends Record<string, any>>({
|
|
|
80
193
|
|
|
81
194
|
// Helper function to get consistent column styles
|
|
82
195
|
// Always use fixed widths to ensure header and body tables stay aligned
|
|
83
|
-
const getColumnStyle = (column: Column<T>) => {
|
|
84
|
-
const width = column.width || column.minWidth || 120;
|
|
196
|
+
const getColumnStyle = useCallback((column: Column<T>) => {
|
|
197
|
+
const width = columnWidths[column.key] || column.width || column.minWidth || 120;
|
|
85
198
|
return {
|
|
86
199
|
boxSizing: 'border-box' as const,
|
|
87
200
|
flexShrink: 0,
|
|
@@ -90,7 +203,7 @@ export function DataGrid<T extends Record<string, any>>({
|
|
|
90
203
|
minWidth: width,
|
|
91
204
|
maxWidth: column.maxWidth || width,
|
|
92
205
|
};
|
|
93
|
-
};
|
|
206
|
+
}, [columnWidths]);
|
|
94
207
|
|
|
95
208
|
const handleSort = useCallback((column: Column<T>) => {
|
|
96
209
|
if (!column.sortable) return;
|
|
@@ -118,6 +231,8 @@ export function DataGrid<T extends Record<string, any>>({
|
|
|
118
231
|
onRowClick?.(row, index);
|
|
119
232
|
}, [selectedRows, onSelectionChange, multiSelect, onRowClick]);
|
|
120
233
|
|
|
234
|
+
const isWeb = Platform.OS === 'web';
|
|
235
|
+
|
|
121
236
|
const renderHeader = () => (
|
|
122
237
|
<TableRow style={{
|
|
123
238
|
...(dataGridStyles.headerRow as any)({ stickyHeader }),
|
|
@@ -127,11 +242,13 @@ export function DataGrid<T extends Record<string, any>>({
|
|
|
127
242
|
{columns.map((column) => (
|
|
128
243
|
<TableCell
|
|
129
244
|
key={column.key}
|
|
130
|
-
width={column.width}
|
|
245
|
+
width={columnWidths[column.key] || column.width}
|
|
131
246
|
style={{
|
|
132
247
|
...dataGridStyles.headerCell,
|
|
133
248
|
...getColumnStyle(column),
|
|
134
249
|
...cellStyle,
|
|
250
|
+
...column.headerStyle,
|
|
251
|
+
position: 'relative' as const,
|
|
135
252
|
}}
|
|
136
253
|
onPress={column.sortable ? () => handleSort(column) : undefined}
|
|
137
254
|
>
|
|
@@ -150,20 +267,42 @@ export function DataGrid<T extends Record<string, any>>({
|
|
|
150
267
|
)}
|
|
151
268
|
</Text>
|
|
152
269
|
)}
|
|
270
|
+
{/* Resize handle */}
|
|
271
|
+
{isWeb && column.resizable && (
|
|
272
|
+
<div
|
|
273
|
+
onMouseDown={(e) => handleResizeStart(e, column.key)}
|
|
274
|
+
style={{
|
|
275
|
+
position: 'absolute',
|
|
276
|
+
right: 0,
|
|
277
|
+
top: 0,
|
|
278
|
+
bottom: 0,
|
|
279
|
+
width: 8,
|
|
280
|
+
cursor: 'col-resize',
|
|
281
|
+
backgroundColor: 'transparent',
|
|
282
|
+
zIndex: 1,
|
|
283
|
+
}}
|
|
284
|
+
onMouseEnter={(e) => {
|
|
285
|
+
(e.currentTarget as HTMLDivElement).style.backgroundColor = 'rgba(0, 0, 0, 0.1)';
|
|
286
|
+
}}
|
|
287
|
+
onMouseLeave={(e) => {
|
|
288
|
+
(e.currentTarget as HTMLDivElement).style.backgroundColor = 'transparent';
|
|
289
|
+
}}
|
|
290
|
+
/>
|
|
291
|
+
)}
|
|
153
292
|
</TableCell>
|
|
154
293
|
))}
|
|
155
294
|
</TableRow>
|
|
156
295
|
);
|
|
157
296
|
|
|
158
297
|
// Render colgroup to define fixed column widths for table-layout: fixed
|
|
159
|
-
const renderColGroup = () => (
|
|
298
|
+
const renderColGroup = useCallback(() => (
|
|
160
299
|
<colgroup>
|
|
161
300
|
{columns.map((column) => {
|
|
162
|
-
const width = column.width || column.minWidth || 120;
|
|
301
|
+
const width = columnWidths[column.key] || column.width || column.minWidth || 120;
|
|
163
302
|
return <col key={column.key} style={{ width, minWidth: width, maxWidth: width }} />;
|
|
164
303
|
})}
|
|
165
304
|
</colgroup>
|
|
166
|
-
);
|
|
305
|
+
), [columns, columnWidths]);
|
|
167
306
|
|
|
168
307
|
const renderRow = (item: T, virtualIndex: number) => {
|
|
169
308
|
const actualIndex = virtualized ? visibleRange.start + virtualIndex : virtualIndex;
|
|
@@ -213,7 +352,6 @@ export function DataGrid<T extends Record<string, any>>({
|
|
|
213
352
|
};
|
|
214
353
|
|
|
215
354
|
const containerHeight = typeof height === 'number' ? height : undefined;
|
|
216
|
-
const isWeb = Platform.OS === 'web';
|
|
217
355
|
|
|
218
356
|
// For web with sticky header, use a single table with sticky thead
|
|
219
357
|
if (isWeb && stickyHeader) {
|
|
@@ -223,6 +361,7 @@ export function DataGrid<T extends Record<string, any>>({
|
|
|
223
361
|
width,
|
|
224
362
|
height,
|
|
225
363
|
...style,
|
|
364
|
+
position: 'relative' as const,
|
|
226
365
|
}}>
|
|
227
366
|
<div
|
|
228
367
|
style={{
|
|
@@ -233,6 +372,23 @@ export function DataGrid<T extends Record<string, any>>({
|
|
|
233
372
|
} as React.CSSProperties}
|
|
234
373
|
onScroll={handleScroll as any}
|
|
235
374
|
>
|
|
375
|
+
{/* Resize indicator line - only shown in indicator mode, positioned via ref for performance */}
|
|
376
|
+
{columnResizeMode === 'indicator' && (
|
|
377
|
+
<div
|
|
378
|
+
ref={indicatorRef}
|
|
379
|
+
style={{
|
|
380
|
+
position: 'absolute',
|
|
381
|
+
top: 0,
|
|
382
|
+
bottom: 0,
|
|
383
|
+
left: 0,
|
|
384
|
+
width: 2,
|
|
385
|
+
backgroundColor: '#3b82f6',
|
|
386
|
+
zIndex: 1000,
|
|
387
|
+
pointerEvents: 'none',
|
|
388
|
+
display: isResizing ? 'block' : 'none',
|
|
389
|
+
}}
|
|
390
|
+
/>
|
|
391
|
+
)}
|
|
236
392
|
<Table style={{ width: minTableWidth, minWidth: minTableWidth }}>
|
|
237
393
|
{renderColGroup()}
|
|
238
394
|
<TableHeader style={{
|
|
@@ -245,29 +401,44 @@ export function DataGrid<T extends Record<string, any>>({
|
|
|
245
401
|
}}>
|
|
246
402
|
{renderHeader()}
|
|
247
403
|
</TableHeader>
|
|
248
|
-
|
|
249
|
-
{
|
|
250
|
-
<
|
|
251
|
-
<
|
|
252
|
-
style={{ ...dataGridStyles.spacerCell, height: visibleRange.offsetY }}
|
|
404
|
+
{virtualized ? (
|
|
405
|
+
<TableBody style={{ position: 'relative' } as any}>
|
|
406
|
+
<tr style={{ height: 0, padding: 0, margin: 0, border: 'none' }}>
|
|
407
|
+
<td
|
|
253
408
|
colSpan={columns.length}
|
|
409
|
+
style={{
|
|
410
|
+
height: data.length * rowHeight,
|
|
411
|
+
padding: 0,
|
|
412
|
+
margin: 0,
|
|
413
|
+
border: 'none',
|
|
414
|
+
position: 'relative',
|
|
415
|
+
}}
|
|
254
416
|
>
|
|
255
|
-
<
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
417
|
+
<div
|
|
418
|
+
style={{
|
|
419
|
+
position: 'absolute',
|
|
420
|
+
top: 0,
|
|
421
|
+
left: 0,
|
|
422
|
+
right: 0,
|
|
423
|
+
transform: `translateY(${visibleRange.offsetY}px)`,
|
|
424
|
+
willChange: 'transform',
|
|
425
|
+
}}
|
|
426
|
+
>
|
|
427
|
+
<table style={{ width: '100%', borderCollapse: 'collapse', tableLayout: 'fixed' }}>
|
|
428
|
+
{renderColGroup()}
|
|
429
|
+
<tbody>
|
|
430
|
+
{visibleData.map((item, index) => renderRow(item, index))}
|
|
431
|
+
</tbody>
|
|
432
|
+
</table>
|
|
433
|
+
</div>
|
|
434
|
+
</td>
|
|
435
|
+
</tr>
|
|
436
|
+
</TableBody>
|
|
437
|
+
) : (
|
|
438
|
+
<TableBody>
|
|
439
|
+
{visibleData.map((item, index) => renderRow(item, index))}
|
|
440
|
+
</TableBody>
|
|
441
|
+
)}
|
|
271
442
|
</Table>
|
|
272
443
|
</div>
|
|
273
444
|
</View>
|
|
@@ -281,7 +452,25 @@ export function DataGrid<T extends Record<string, any>>({
|
|
|
281
452
|
width,
|
|
282
453
|
height,
|
|
283
454
|
...style,
|
|
455
|
+
position: 'relative' as const,
|
|
284
456
|
}}>
|
|
457
|
+
{/* Resize indicator line (web only, indicator mode) - positioned via ref for performance */}
|
|
458
|
+
{isWeb && columnResizeMode === 'indicator' && (
|
|
459
|
+
<div
|
|
460
|
+
ref={indicatorRef}
|
|
461
|
+
style={{
|
|
462
|
+
position: 'absolute',
|
|
463
|
+
top: 0,
|
|
464
|
+
bottom: 0,
|
|
465
|
+
left: 0,
|
|
466
|
+
width: 2,
|
|
467
|
+
backgroundColor: '#3b82f6',
|
|
468
|
+
zIndex: 1000,
|
|
469
|
+
pointerEvents: 'none',
|
|
470
|
+
display: isResizing ? 'block' : 'none',
|
|
471
|
+
}}
|
|
472
|
+
/>
|
|
473
|
+
)}
|
|
285
474
|
<ScrollView
|
|
286
475
|
style={{
|
|
287
476
|
...dataGridStyles.scrollView,
|
|
@@ -301,29 +490,44 @@ export function DataGrid<T extends Record<string, any>>({
|
|
|
301
490
|
<TableHeader style={(dataGridStyles.header as any)({ stickyHeader: false })}>
|
|
302
491
|
{renderHeader()}
|
|
303
492
|
</TableHeader>
|
|
304
|
-
|
|
305
|
-
{
|
|
306
|
-
<
|
|
307
|
-
<
|
|
308
|
-
style={{ ...dataGridStyles.spacerCell, height: visibleRange.offsetY }}
|
|
493
|
+
{virtualized ? (
|
|
494
|
+
<TableBody style={{ position: 'relative' } as any}>
|
|
495
|
+
<tr style={{ height: 0, padding: 0, margin: 0, border: 'none' }}>
|
|
496
|
+
<td
|
|
309
497
|
colSpan={columns.length}
|
|
498
|
+
style={{
|
|
499
|
+
height: data.length * rowHeight,
|
|
500
|
+
padding: 0,
|
|
501
|
+
margin: 0,
|
|
502
|
+
border: 'none',
|
|
503
|
+
position: 'relative',
|
|
504
|
+
}}
|
|
310
505
|
>
|
|
311
|
-
<
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
506
|
+
<div
|
|
507
|
+
style={{
|
|
508
|
+
position: 'absolute',
|
|
509
|
+
top: 0,
|
|
510
|
+
left: 0,
|
|
511
|
+
right: 0,
|
|
512
|
+
transform: `translateY(${visibleRange.offsetY}px)`,
|
|
513
|
+
willChange: 'transform',
|
|
514
|
+
}}
|
|
515
|
+
>
|
|
516
|
+
<table style={{ width: '100%', borderCollapse: 'collapse', tableLayout: 'fixed' }}>
|
|
517
|
+
{renderColGroup()}
|
|
518
|
+
<tbody>
|
|
519
|
+
{visibleData.map((item, index) => renderRow(item, index))}
|
|
520
|
+
</tbody>
|
|
521
|
+
</table>
|
|
522
|
+
</div>
|
|
523
|
+
</td>
|
|
524
|
+
</tr>
|
|
525
|
+
</TableBody>
|
|
526
|
+
) : (
|
|
527
|
+
<TableBody>
|
|
528
|
+
{visibleData.map((item, index) => renderRow(item, index))}
|
|
529
|
+
</TableBody>
|
|
530
|
+
)}
|
|
327
531
|
</Table>
|
|
328
532
|
</View>
|
|
329
533
|
</ScrollView>
|
package/src/DataGrid/types.ts
CHANGED
|
@@ -24,6 +24,10 @@ export interface DataGridProps<T = any> {
|
|
|
24
24
|
headerHeight?: number;
|
|
25
25
|
onRowClick?: (row: T, index: number) => void;
|
|
26
26
|
onSort?: (column: Column<T>, direction: 'asc' | 'desc') => void;
|
|
27
|
+
/** Callback when a column is resized. Receives the column key and the new width. Only called on mouse release. */
|
|
28
|
+
onColumnResize?: (columnKey: string, width: number) => void;
|
|
29
|
+
/** Resize mode: 'indicator' shows a line during drag (default), 'live' updates column width during drag */
|
|
30
|
+
columnResizeMode?: 'indicator' | 'live';
|
|
27
31
|
virtualized?: boolean;
|
|
28
32
|
height?: number | string;
|
|
29
33
|
width?: number | string;
|
|
@@ -59,19 +59,28 @@ export function DataGridShowcase({
|
|
|
59
59
|
key: 'id',
|
|
60
60
|
header: 'ID',
|
|
61
61
|
width: showAllColumns ? 60 : 50,
|
|
62
|
+
minWidth: 40,
|
|
63
|
+
maxWidth: 100,
|
|
62
64
|
sortable: true,
|
|
65
|
+
resizable: true,
|
|
63
66
|
},
|
|
64
67
|
{
|
|
65
68
|
key: 'name',
|
|
66
69
|
header: showAllColumns ? 'Product Name' : 'Product',
|
|
67
70
|
width: showAllColumns ? 150 : 120,
|
|
71
|
+
minWidth: 80,
|
|
72
|
+
maxWidth: 300,
|
|
68
73
|
sortable: true,
|
|
74
|
+
resizable: true,
|
|
69
75
|
},
|
|
70
76
|
{
|
|
71
77
|
key: 'category',
|
|
72
78
|
header: 'Category',
|
|
73
79
|
width: showAllColumns ? 120 : 100,
|
|
80
|
+
minWidth: 80,
|
|
81
|
+
maxWidth: 200,
|
|
74
82
|
sortable: true,
|
|
83
|
+
resizable: true,
|
|
75
84
|
render: (value) => (
|
|
76
85
|
<Badge variant="outlined" size="sm">
|
|
77
86
|
{value}
|
|
@@ -82,7 +91,10 @@ export function DataGridShowcase({
|
|
|
82
91
|
key: 'price',
|
|
83
92
|
header: 'Price',
|
|
84
93
|
width: showAllColumns ? 100 : 80,
|
|
94
|
+
minWidth: 60,
|
|
95
|
+
maxWidth: 150,
|
|
85
96
|
sortable: true,
|
|
97
|
+
resizable: true,
|
|
86
98
|
render: (value) => (
|
|
87
99
|
<Text weight="semibold" size={showAllColumns ? 'md' : 'sm'}>
|
|
88
100
|
${value.toFixed(2)}
|
|
@@ -93,13 +105,16 @@ export function DataGridShowcase({
|
|
|
93
105
|
key: 'stock',
|
|
94
106
|
header: 'Stock',
|
|
95
107
|
width: showAllColumns ? 80 : 60,
|
|
108
|
+
minWidth: 50,
|
|
109
|
+
maxWidth: 120,
|
|
96
110
|
sortable: true,
|
|
111
|
+
resizable: true,
|
|
97
112
|
render: (value, row) => (
|
|
98
113
|
<Text
|
|
99
114
|
size="sm"
|
|
100
|
-
style={{
|
|
101
|
-
color: row.status === 'out-of-stock' ? '#ef4444' :
|
|
102
|
-
row.status === 'low-stock' ? '#f59e0b' : '#22c55e'
|
|
115
|
+
style={{
|
|
116
|
+
color: row.status === 'out-of-stock' ? '#ef4444' :
|
|
117
|
+
row.status === 'low-stock' ? '#f59e0b' : '#22c55e'
|
|
103
118
|
}}
|
|
104
119
|
>
|
|
105
120
|
{value}
|
|
@@ -110,8 +125,11 @@ export function DataGridShowcase({
|
|
|
110
125
|
key: 'status',
|
|
111
126
|
header: 'Status',
|
|
112
127
|
width: showAllColumns ? 120 : 100,
|
|
128
|
+
minWidth: 80,
|
|
129
|
+
maxWidth: 180,
|
|
130
|
+
resizable: true,
|
|
113
131
|
render: (value) => {
|
|
114
|
-
const intent = value === 'in-stock' ? 'success' :
|
|
132
|
+
const intent = value === 'in-stock' ? 'success' :
|
|
115
133
|
value === 'low-stock' ? 'warning' : 'error';
|
|
116
134
|
return (
|
|
117
135
|
<Badge variant="filled" intent={intent} size="sm">
|
|
@@ -127,16 +145,26 @@ export function DataGridShowcase({
|
|
|
127
145
|
key: 'vendor',
|
|
128
146
|
header: 'Vendor',
|
|
129
147
|
width: 100,
|
|
148
|
+
minWidth: 70,
|
|
149
|
+
maxWidth: 180,
|
|
130
150
|
sortable: true,
|
|
151
|
+
resizable: true,
|
|
131
152
|
},
|
|
132
153
|
{
|
|
133
154
|
key: 'lastUpdated',
|
|
134
155
|
header: 'Last Updated',
|
|
135
156
|
width: 120,
|
|
157
|
+
minWidth: 90,
|
|
158
|
+
maxWidth: 180,
|
|
136
159
|
sortable: true,
|
|
160
|
+
resizable: true,
|
|
137
161
|
},
|
|
138
162
|
] : [];
|
|
139
163
|
|
|
164
|
+
const handleColumnResize = (columnKey: string, newWidth: number) => {
|
|
165
|
+
console.log(`Column "${columnKey}" resized to ${newWidth}px`);
|
|
166
|
+
};
|
|
167
|
+
|
|
140
168
|
const columns = [...baseColumns, ...additionalColumns];
|
|
141
169
|
|
|
142
170
|
const handleSort = (column: Column<Product>, direction: 'asc' | 'desc') => {
|
|
@@ -269,6 +297,7 @@ export function DataGridShowcase({
|
|
|
269
297
|
onSelectionChange={setSelectedRows}
|
|
270
298
|
multiSelect={true}
|
|
271
299
|
onSort={handleSort}
|
|
300
|
+
onColumnResize={handleColumnResize}
|
|
272
301
|
stickyHeader={true}
|
|
273
302
|
rowHeight={showAllColumns ? 48 : 44}
|
|
274
303
|
headerHeight={48}
|
|
@@ -283,11 +312,11 @@ export function DataGridShowcase({
|
|
|
283
312
|
<Text weight="semibold" size="md">Features {showAllColumns ? 'Demonstrated' : ''}</Text>
|
|
284
313
|
<Text size="sm">✓ Virtualized rendering{showAllColumns ? ` with ${productCount} rows` : ''}</Text>
|
|
285
314
|
<Text size="sm">✓ Sortable columns{showAllColumns ? ' (ID, Name, Category, Price, Stock, Vendor, Date)' : ''}</Text>
|
|
315
|
+
<Text size="sm">✓ Resizable columns{showAllColumns ? ' (drag column borders to resize)' : ''}</Text>
|
|
286
316
|
<Text size="sm">✓ Multi-row selection{showAllColumns ? ' with visual feedback' : ''}</Text>
|
|
287
317
|
<Text size="sm">✓ Custom cell rendering{showAllColumns ? ' (badges, colored text)' : ''}</Text>
|
|
288
318
|
<Text size="sm">✓ Sticky header{showAllColumns ? ' while scrolling' : ''}</Text>
|
|
289
319
|
<Text size="sm">✓ Alternating row colors</Text>
|
|
290
|
-
{showAllColumns && <Text size="sm">✓ Responsive column widths</Text>}
|
|
291
320
|
</View>
|
|
292
321
|
</Card>
|
|
293
322
|
</View>
|