@idealyst/datagrid 1.2.32 → 1.2.34

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 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 (coming soon) |
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.32",
3
+ "version": "1.2.34",
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.32",
40
- "@idealyst/theme": "^1.2.32",
39
+ "@idealyst/components": "^1.2.34",
40
+ "@idealyst/theme": "^1.2.34",
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.32",
65
- "@idealyst/theme": "^1.2.32",
64
+ "@idealyst/components": "^1.2.34",
65
+ "@idealyst/theme": "^1.2.34",
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.width ? (typeof column.width === 'number' ? column.width : 120) : 120);
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 = 3; // Render extra rows above and below visible area to prevent flickering
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
- <TableBody>
249
- {virtualized && visibleRange.offsetY > 0 && (
250
- <TableRow style={{ ...dataGridStyles.spacerRow, height: visibleRange.offsetY }}>
251
- <TableCell
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
- <View />
256
- </TableCell>
257
- </TableRow>
258
- )}
259
- {visibleData.map((item, index) => renderRow(item, index))}
260
- {virtualized && (data.length - visibleRange.end - 1) > 0 && (
261
- <TableRow style={{ ...dataGridStyles.spacerRow, height: (data.length - visibleRange.end - 1) * rowHeight }}>
262
- <TableCell
263
- style={{ ...dataGridStyles.spacerCell, height: (data.length - visibleRange.end - 1) * rowHeight }}
264
- colSpan={columns.length}
265
- >
266
- <View />
267
- </TableCell>
268
- </TableRow>
269
- )}
270
- </TableBody>
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
- <TableBody>
305
- {virtualized && visibleRange.offsetY > 0 && (
306
- <TableRow style={{ ...dataGridStyles.spacerRow, height: visibleRange.offsetY }}>
307
- <TableCell
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
- <View />
312
- </TableCell>
313
- </TableRow>
314
- )}
315
- {visibleData.map((item, index) => renderRow(item, index))}
316
- {virtualized && (data.length - visibleRange.end - 1) > 0 && (
317
- <TableRow style={{ ...dataGridStyles.spacerRow, height: (data.length - visibleRange.end - 1) * rowHeight }}>
318
- <TableCell
319
- style={{ ...dataGridStyles.spacerCell, height: (data.length - visibleRange.end - 1) * rowHeight }}
320
- colSpan={columns.length}
321
- >
322
- <View />
323
- </TableCell>
324
- </TableRow>
325
- )}
326
- </TableBody>
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>
@@ -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>