@fragments-sdk/ui 0.9.3 → 0.9.5

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 (99) hide show
  1. package/dist/assets/ui.css +247 -48
  2. package/dist/blocks/components/index.d.ts +0 -2
  3. package/dist/blocks/components/index.d.ts.map +1 -1
  4. package/dist/codeblock.cjs +7 -3
  5. package/dist/codeblock.cjs.map +1 -1
  6. package/dist/codeblock.js +7 -3
  7. package/dist/codeblock.js.map +1 -1
  8. package/dist/components/Box/Box.module.scss.cjs +73 -0
  9. package/dist/components/Box/Box.module.scss.cjs.map +1 -1
  10. package/dist/components/Box/Box.module.scss.js +73 -0
  11. package/dist/components/Box/Box.module.scss.js.map +1 -1
  12. package/dist/components/ButtonGroup/ButtonGroup.module.scss.cjs +6 -0
  13. package/dist/components/ButtonGroup/ButtonGroup.module.scss.cjs.map +1 -1
  14. package/dist/components/ButtonGroup/ButtonGroup.module.scss.js +6 -0
  15. package/dist/components/ButtonGroup/ButtonGroup.module.scss.js.map +1 -1
  16. package/dist/components/CodeBlock/index.d.ts +5 -1
  17. package/dist/components/CodeBlock/index.d.ts.map +1 -1
  18. package/dist/components/DataTable/DataTable.module.scss.cjs +84 -0
  19. package/dist/components/DataTable/DataTable.module.scss.cjs.map +1 -0
  20. package/dist/components/DataTable/DataTable.module.scss.js +84 -0
  21. package/dist/components/DataTable/DataTable.module.scss.js.map +1 -0
  22. package/dist/components/DataTable/index.cjs +383 -0
  23. package/dist/components/DataTable/index.cjs.map +1 -0
  24. package/dist/components/DataTable/index.d.ts +78 -0
  25. package/dist/components/DataTable/index.d.ts.map +1 -0
  26. package/dist/components/DataTable/index.js +366 -0
  27. package/dist/components/DataTable/index.js.map +1 -0
  28. package/dist/components/Drawer/Drawer.module.scss.cjs +9 -0
  29. package/dist/components/Drawer/Drawer.module.scss.cjs.map +1 -1
  30. package/dist/components/Drawer/Drawer.module.scss.js +9 -0
  31. package/dist/components/Drawer/Drawer.module.scss.js.map +1 -1
  32. package/dist/components/Image/Image.module.scss.cjs +12 -0
  33. package/dist/components/Image/Image.module.scss.cjs.map +1 -1
  34. package/dist/components/Image/Image.module.scss.js +12 -0
  35. package/dist/components/Image/Image.module.scss.js.map +1 -1
  36. package/dist/components/Link/Link.module.scss.cjs +3 -0
  37. package/dist/components/Link/Link.module.scss.cjs.map +1 -1
  38. package/dist/components/Link/Link.module.scss.js +3 -0
  39. package/dist/components/Link/Link.module.scss.js.map +1 -1
  40. package/dist/components/List/List.module.scss.cjs +5 -0
  41. package/dist/components/List/List.module.scss.cjs.map +1 -1
  42. package/dist/components/List/List.module.scss.js +5 -0
  43. package/dist/components/List/List.module.scss.js.map +1 -1
  44. package/dist/components/Loading/Loading.module.scss.cjs +5 -0
  45. package/dist/components/Loading/Loading.module.scss.cjs.map +1 -1
  46. package/dist/components/Loading/Loading.module.scss.js +5 -0
  47. package/dist/components/Loading/Loading.module.scss.js.map +1 -1
  48. package/dist/components/Skeleton/Skeleton.module.scss.cjs +14 -0
  49. package/dist/components/Skeleton/Skeleton.module.scss.cjs.map +1 -1
  50. package/dist/components/Skeleton/Skeleton.module.scss.js +14 -0
  51. package/dist/components/Skeleton/Skeleton.module.scss.js.map +1 -1
  52. package/dist/components/Stack/Stack.module.scss.cjs +14 -0
  53. package/dist/components/Stack/Stack.module.scss.cjs.map +1 -1
  54. package/dist/components/Stack/Stack.module.scss.js +14 -0
  55. package/dist/components/Stack/Stack.module.scss.js.map +1 -1
  56. package/dist/components/Table/Table.module.scss.cjs +21 -36
  57. package/dist/components/Table/Table.module.scss.cjs.map +1 -1
  58. package/dist/components/Table/Table.module.scss.js +21 -36
  59. package/dist/components/Table/Table.module.scss.js.map +1 -1
  60. package/dist/components/Table/index.d.ts +35 -55
  61. package/dist/components/Table/index.d.ts.map +1 -1
  62. package/dist/components/Text/Text.module.scss.cjs +14 -0
  63. package/dist/components/Text/Text.module.scss.cjs.map +1 -1
  64. package/dist/components/Text/Text.module.scss.js +14 -0
  65. package/dist/components/Text/Text.module.scss.js.map +1 -1
  66. package/dist/components/Textarea/Textarea.module.scss.cjs +4 -0
  67. package/dist/components/Textarea/Textarea.module.scss.cjs.map +1 -1
  68. package/dist/components/Textarea/Textarea.module.scss.js +4 -0
  69. package/dist/components/Textarea/Textarea.module.scss.js.map +1 -1
  70. package/dist/components/ToggleGroup/ToggleGroup.module.scss.cjs +5 -0
  71. package/dist/components/ToggleGroup/ToggleGroup.module.scss.cjs.map +1 -1
  72. package/dist/components/ToggleGroup/ToggleGroup.module.scss.js +5 -0
  73. package/dist/components/ToggleGroup/ToggleGroup.module.scss.js.map +1 -1
  74. package/dist/index.cjs +119 -117
  75. package/dist/index.cjs.map +1 -1
  76. package/dist/index.d.ts +2 -1
  77. package/dist/index.d.ts.map +1 -1
  78. package/dist/index.js +3 -1
  79. package/dist/index.js.map +1 -1
  80. package/dist/table.cjs +44 -262
  81. package/dist/table.cjs.map +1 -1
  82. package/dist/table.js +47 -248
  83. package/dist/table.js.map +1 -1
  84. package/fragments.json +1 -1
  85. package/package.json +110 -118
  86. package/src/blocks/components/index.ts +0 -3
  87. package/src/components/CodeBlock/index.tsx +9 -1
  88. package/src/components/DataTable/DataTable.fragment.tsx +754 -0
  89. package/src/components/DataTable/DataTable.module.scss +300 -0
  90. package/src/components/DataTable/DataTable.test.tsx +224 -0
  91. package/src/components/DataTable/index.tsx +533 -0
  92. package/src/components/Table/Table.fragment.tsx +190 -175
  93. package/src/components/Table/Table.module.scss +15 -88
  94. package/src/components/Table/Table.test.tsx +184 -94
  95. package/src/components/Table/index.tsx +105 -374
  96. package/src/index.ts +15 -4
  97. package/dist/blocks/components/DataTable.d.ts +0 -19
  98. package/dist/blocks/components/DataTable.d.ts.map +0 -1
  99. package/src/blocks/components/DataTable.tsx +0 -124
@@ -0,0 +1,533 @@
1
+ 'use client';
2
+
3
+ import * as React from 'react';
4
+ // Import globals to ensure CSS variables are defined
5
+ import '../../styles/globals.scss';
6
+ import styles from './DataTable.module.scss';
7
+ import { Checkbox } from '../Checkbox';
8
+
9
+ // ============================================
10
+ // Types (self-owned — no external dependency for types)
11
+ // ============================================
12
+
13
+ /** Column definition compatible with @tanstack/react-table */
14
+ export type ColumnDef<TData = unknown, TValue = unknown> = {
15
+ id?: string;
16
+ accessorKey?: string;
17
+ accessorFn?: (row: TData) => TValue;
18
+ header?: string | ((context: any) => React.ReactNode);
19
+ cell?: string | ((context: any) => React.ReactNode);
20
+ size?: number;
21
+ minSize?: number;
22
+ maxSize?: number;
23
+ enableSorting?: boolean;
24
+ [key: string]: unknown;
25
+ };
26
+
27
+ export type SortingState = Array<{ id: string; desc: boolean }>;
28
+ export type RowSelectionState = Record<string, boolean>;
29
+ export type ExpandedState = true | Record<string, boolean>;
30
+ type OnChangeFn<T> = ((updaterOrValue: T | ((prev: T) => T)) => void);
31
+
32
+ export type DataTableColumn<T> = ColumnDef<T, unknown>;
33
+
34
+ // ============================================
35
+ // Lazy-loaded dependency (@tanstack/react-table)
36
+ // ============================================
37
+
38
+ let _useReactTable: any = null;
39
+ let _getCoreRowModel: any = null;
40
+ let _getSortedRowModel: any = null;
41
+ let _getExpandedRowModel: any = null;
42
+ let _flexRender: any = null;
43
+ let _tableLoaded = false;
44
+ let _tableFailed = false;
45
+
46
+ function loadTableDeps() {
47
+ if (_tableLoaded) return;
48
+ _tableLoaded = true;
49
+ try {
50
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
51
+ const rt = require('@tanstack/react-table');
52
+ _useReactTable = rt.useReactTable;
53
+ _getCoreRowModel = rt.getCoreRowModel;
54
+ _getSortedRowModel = rt.getSortedRowModel;
55
+ _getExpandedRowModel = rt.getExpandedRowModel;
56
+ _flexRender = rt.flexRender;
57
+ } catch {
58
+ _tableFailed = true;
59
+ }
60
+ }
61
+
62
+ export interface DataTableProps<T> extends Omit<React.HTMLAttributes<HTMLTableElement>, 'onClick'> {
63
+ /** Column definitions */
64
+ columns: DataTableColumn<T>[];
65
+ /** Data array */
66
+ data: T[];
67
+ /** Unique key extractor for each row */
68
+ getRowId?: (row: T) => string;
69
+ /** Enable sorting */
70
+ sortable?: boolean;
71
+ /** Controlled sorting state */
72
+ sorting?: SortingState;
73
+ /** Sorting change handler */
74
+ onSortingChange?: OnChangeFn<SortingState>;
75
+ /** Enable row selection */
76
+ selectable?: boolean;
77
+ /** Show checkbox column for row selection */
78
+ showCheckbox?: boolean;
79
+ /** Controlled selection state */
80
+ rowSelection?: RowSelectionState;
81
+ /** Selection change handler */
82
+ onRowSelectionChange?: OnChangeFn<RowSelectionState>;
83
+ /** Row click handler */
84
+ onRowClick?: (row: T) => void;
85
+ /** Extract sub-rows from a row for expandable tree tables */
86
+ getSubRows?: (row: T) => T[] | undefined;
87
+ /** Controlled expanded state */
88
+ expanded?: ExpandedState;
89
+ /** Expanded state change handler */
90
+ onExpandedChange?: OnChangeFn<ExpandedState>;
91
+ /** Empty state message */
92
+ emptyMessage?: string;
93
+ /** Size variant */
94
+ size?: 'sm' | 'md';
95
+ /** Visible caption for the table (recommended for accessibility) */
96
+ caption?: string;
97
+ /** Hide the caption visually but keep it for screen readers */
98
+ captionHidden?: boolean;
99
+ /** Show alternating row backgrounds */
100
+ striped?: boolean;
101
+ /** Wrap table in a bordered container */
102
+ bordered?: boolean;
103
+ }
104
+
105
+ function DataTableRoot<T>({
106
+ columns: userColumns,
107
+ data,
108
+ getRowId,
109
+ sortable = false,
110
+ sorting: controlledSorting,
111
+ onSortingChange,
112
+ selectable = false,
113
+ showCheckbox = false,
114
+ rowSelection: controlledRowSelection,
115
+ onRowSelectionChange,
116
+ onRowClick,
117
+ getSubRows,
118
+ expanded: controlledExpanded,
119
+ onExpandedChange,
120
+ emptyMessage = 'No data available',
121
+ size = 'md',
122
+ className,
123
+ caption,
124
+ captionHidden = false,
125
+ striped = false,
126
+ bordered = false,
127
+ 'aria-label': ariaLabel,
128
+ 'aria-describedby': ariaDescribedBy,
129
+ ...htmlProps
130
+ }: DataTableProps<T>) {
131
+ loadTableDeps();
132
+
133
+ // Internal sorting state when uncontrolled
134
+ const [internalSorting, setInternalSorting] = React.useState<SortingState>([]);
135
+ const sorting = controlledSorting ?? internalSorting;
136
+ const handleSortingChange = onSortingChange ?? setInternalSorting;
137
+
138
+ // Internal selection state when uncontrolled
139
+ const [internalRowSelection, setInternalRowSelection] = React.useState<RowSelectionState>({});
140
+ const rowSelection = controlledRowSelection ?? internalRowSelection;
141
+ const handleRowSelectionChange = onRowSelectionChange ?? setInternalRowSelection;
142
+
143
+ // Internal expanded state when uncontrolled
144
+ const [internalExpanded, setInternalExpanded] = React.useState<ExpandedState>({});
145
+ const expanded = controlledExpanded ?? internalExpanded;
146
+ const handleExpandedChange = onExpandedChange ?? setInternalExpanded;
147
+
148
+ // Build columns with optional checkbox prepended
149
+ const columns = React.useMemo(() => {
150
+ if (!showCheckbox || !selectable) return userColumns;
151
+
152
+ const checkboxColumn: DataTableColumn<T> = {
153
+ id: '__checkbox',
154
+ size: 40,
155
+ minSize: 40,
156
+ maxSize: 40,
157
+ enableSorting: false,
158
+ header: ({ table }: any) => (
159
+ <Checkbox
160
+ size="sm"
161
+ checked={table.getIsAllRowsSelected()}
162
+ indeterminate={table.getIsSomeRowsSelected()}
163
+ onCheckedChange={() => table.toggleAllRowsSelected()}
164
+ aria-label="Select all rows"
165
+ />
166
+ ),
167
+ cell: ({ row }: any) => (
168
+ <Checkbox
169
+ size="sm"
170
+ checked={row.getIsSelected()}
171
+ disabled={!row.getCanSelect()}
172
+ onCheckedChange={() => row.toggleSelected()}
173
+ aria-label={`Select row ${row.id}`}
174
+ />
175
+ ),
176
+ };
177
+
178
+ return [checkboxColumn, ...userColumns];
179
+ }, [userColumns, showCheckbox, selectable]);
180
+
181
+ if (_tableFailed || !_useReactTable) {
182
+ if (_tableFailed && process.env.NODE_ENV === 'development') {
183
+ console.warn(
184
+ '[@fragments-sdk/ui] DataTable: @tanstack/react-table is not installed. ' +
185
+ 'Install it with: npm install @tanstack/react-table'
186
+ );
187
+ }
188
+ return null;
189
+ }
190
+
191
+ const hasSubRows = !!getSubRows;
192
+
193
+ const table = _useReactTable({
194
+ data,
195
+ columns,
196
+ getRowId,
197
+ getSubRows: getSubRows as any,
198
+ getCoreRowModel: _getCoreRowModel(),
199
+ getSortedRowModel: sortable ? _getSortedRowModel() : undefined,
200
+ getExpandedRowModel: hasSubRows && _getExpandedRowModel ? _getExpandedRowModel() : undefined,
201
+ state: {
202
+ sorting: sortable ? sorting : undefined,
203
+ rowSelection: selectable ? rowSelection : undefined,
204
+ expanded: hasSubRows ? expanded : undefined,
205
+ },
206
+ onSortingChange: sortable ? handleSortingChange : undefined,
207
+ onRowSelectionChange: selectable ? handleRowSelectionChange : undefined,
208
+ onExpandedChange: hasSubRows ? handleExpandedChange : undefined,
209
+ enableRowSelection: selectable,
210
+ enableSorting: sortable,
211
+ enableExpanding: hasSubRows,
212
+ });
213
+
214
+ const isEmpty = data.length === 0;
215
+
216
+ const hasExplicitColumnSizing = React.useMemo(
217
+ () =>
218
+ columns.some((column) =>
219
+ column.size !== undefined ||
220
+ column.minSize !== undefined ||
221
+ column.maxSize !== undefined
222
+ ),
223
+ [columns]
224
+ );
225
+
226
+ const rootClasses = [
227
+ styles.table,
228
+ hasExplicitColumnSizing && styles.fixedLayout,
229
+ styles[size],
230
+ striped && styles.striped,
231
+ className,
232
+ ]
233
+ .filter(Boolean)
234
+ .join(' ');
235
+
236
+ const getColumnSizeStyle = (
237
+ column: {
238
+ getSize: () => number;
239
+ columnDef: { size?: number; minSize?: number; maxSize?: number };
240
+ }
241
+ ): React.CSSProperties | undefined => {
242
+ const { size, minSize, maxSize } = column.columnDef;
243
+ const hasExplicitSize = size !== undefined || minSize !== undefined || maxSize !== undefined;
244
+
245
+ if (!hasExplicitSize) {
246
+ return undefined;
247
+ }
248
+
249
+ const resolvedSize = column.getSize();
250
+
251
+ return {
252
+ width: resolvedSize,
253
+ minWidth: minSize ?? resolvedSize,
254
+ maxWidth: maxSize ?? resolvedSize,
255
+ };
256
+ };
257
+
258
+ if (isEmpty) {
259
+ return (
260
+ <div className={styles.emptyState}>
261
+ <span className={styles.emptyMessage}>{emptyMessage}</span>
262
+ </div>
263
+ );
264
+ }
265
+
266
+ const isInteractiveTarget = (
267
+ target: EventTarget | null,
268
+ currentTarget: HTMLTableRowElement
269
+ ) => {
270
+ if (!(target instanceof Element)) return false;
271
+
272
+ const interactiveElement = target.closest(
273
+ 'button, a, input, select, textarea, [role="button"], [role="link"], [role="checkbox"], [role="switch"]'
274
+ );
275
+
276
+ return Boolean(interactiveElement && currentTarget.contains(interactiveElement));
277
+ };
278
+
279
+ return (
280
+ <div className={[styles.wrapper, bordered && styles.bordered].filter(Boolean).join(' ')}>
281
+ <table
282
+ {...htmlProps}
283
+ className={rootClasses}
284
+ aria-label={ariaLabel}
285
+ aria-describedby={ariaDescribedBy}
286
+ >
287
+ {caption && (
288
+ <caption className={captionHidden ? styles.captionHidden : styles.caption}>
289
+ {caption}
290
+ </caption>
291
+ )}
292
+ <thead className={styles.thead}>
293
+ {table.getHeaderGroups().map((headerGroup: any) => (
294
+ <tr key={headerGroup.id} className={styles.headerRow}>
295
+ {headerGroup.headers.map((header: any) => {
296
+ const canSort = sortable && header.column.getCanSort();
297
+ const sortDirection = header.column.getIsSorted();
298
+ const toggleSorting = canSort ? header.column.getToggleSortingHandler() : undefined;
299
+
300
+ return (
301
+ <th
302
+ key={header.id}
303
+ className={[styles.th, canSort && styles.thSortable].filter(Boolean).join(' ')}
304
+ style={getColumnSizeStyle(header.column)}
305
+ scope="col"
306
+ aria-sort={
307
+ sortDirection
308
+ ? sortDirection === 'asc'
309
+ ? 'ascending'
310
+ : 'descending'
311
+ : canSort
312
+ ? 'none'
313
+ : undefined
314
+ }
315
+ >
316
+ {canSort ? (
317
+ <button
318
+ type="button"
319
+ className={styles.sortButton}
320
+ onClick={toggleSorting}
321
+ >
322
+ <span className={styles.headerContent}>
323
+ {header.isPlaceholder
324
+ ? null
325
+ : _flexRender(
326
+ header.column.columnDef.header,
327
+ header.getContext()
328
+ )}
329
+ </span>
330
+ <span className={styles.sortIndicator} aria-hidden="true">
331
+ {sortDirection === 'asc' ? (
332
+ <SortAscIcon />
333
+ ) : sortDirection === 'desc' ? (
334
+ <SortDescIcon />
335
+ ) : (
336
+ <SortIcon />
337
+ )}
338
+ </span>
339
+ </button>
340
+ ) : (
341
+ <div className={styles.headerContent}>
342
+ {header.isPlaceholder
343
+ ? null
344
+ : _flexRender(
345
+ header.column.columnDef.header,
346
+ header.getContext()
347
+ )}
348
+ </div>
349
+ )}
350
+ </th>
351
+ );
352
+ })}
353
+ </tr>
354
+ ))}
355
+ </thead>
356
+ <tbody className={styles.tbody}>
357
+ {table.getRowModel().rows.map((row: any) => {
358
+ const isClickable = !!onRowClick;
359
+ const isSelected = selectable ? row.getIsSelected() : false;
360
+ const depth: number = row.depth ?? 0;
361
+ const canExpand = hasSubRows && row.getCanExpand();
362
+ const handleRowClick = (event: React.MouseEvent<HTMLTableRowElement>) => {
363
+ if (!onRowClick) return;
364
+ if (isInteractiveTarget(event.target, event.currentTarget)) return;
365
+ onRowClick(row.original);
366
+ };
367
+
368
+ const handleRowKeyDown = (event: React.KeyboardEvent<HTMLTableRowElement>) => {
369
+ if (!onRowClick) return;
370
+ if (isInteractiveTarget(event.target, event.currentTarget)) return;
371
+ if (event.key === 'Enter' || event.key === ' ') {
372
+ event.preventDefault();
373
+ onRowClick(row.original);
374
+ }
375
+ };
376
+
377
+ return (
378
+ <tr
379
+ key={row.id}
380
+ className={[
381
+ styles.row,
382
+ isClickable && styles.clickable,
383
+ isSelected && styles.selected,
384
+ depth > 0 && styles.subRow,
385
+ ]
386
+ .filter(Boolean)
387
+ .join(' ')}
388
+ onClick={isClickable ? handleRowClick : undefined}
389
+ onKeyDown={isClickable ? handleRowKeyDown : undefined}
390
+ tabIndex={isClickable ? 0 : undefined}
391
+ data-selected={isSelected || undefined}
392
+ data-depth={depth > 0 ? depth : undefined}
393
+ >
394
+ {row.getVisibleCells().map((cell: any, cellIndex: number) => {
395
+ const isFirstDataCell = hasSubRows && cellIndex === (showCheckbox && selectable ? 1 : 0);
396
+ return (
397
+ <td
398
+ key={cell.id}
399
+ className={styles.td}
400
+ style={{
401
+ ...getColumnSizeStyle(cell.column),
402
+ ...(isFirstDataCell && depth > 0 ? { paddingLeft: `${depth * 24 + 12}px` } : undefined),
403
+ }}
404
+ >
405
+ {isFirstDataCell && canExpand ? (
406
+ <span className={styles.expandCell}>
407
+ <button
408
+ type="button"
409
+ className={styles.expandButton}
410
+ onClick={row.getToggleExpandedHandler()}
411
+ aria-label={row.getIsExpanded() ? 'Collapse row' : 'Expand row'}
412
+ aria-expanded={row.getIsExpanded()}
413
+ >
414
+ <ExpandIcon expanded={row.getIsExpanded()} />
415
+ </button>
416
+ {_flexRender(cell.column.columnDef.cell, cell.getContext())}
417
+ </span>
418
+ ) : (
419
+ _flexRender(cell.column.columnDef.cell, cell.getContext())
420
+ )}
421
+ </td>
422
+ );
423
+ })}
424
+ </tr>
425
+ );
426
+ })}
427
+ </tbody>
428
+ </table>
429
+ </div>
430
+ );
431
+ }
432
+
433
+ // Expand/collapse icon for sub-rows
434
+ function ExpandIcon({ expanded }: { expanded: boolean }) {
435
+ return (
436
+ <svg
437
+ width="12"
438
+ height="12"
439
+ viewBox="0 0 12 12"
440
+ fill="none"
441
+ xmlns="http://www.w3.org/2000/svg"
442
+ aria-hidden="true"
443
+ style={{
444
+ transform: expanded ? 'rotate(90deg)' : undefined,
445
+ transition: 'transform 150ms ease',
446
+ }}
447
+ >
448
+ <path d="M4.5 2.5L8 6L4.5 9.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
449
+ </svg>
450
+ );
451
+ }
452
+
453
+ // Sort icons - minimal and functional
454
+ function SortIcon() {
455
+ return (
456
+ <svg
457
+ width="12"
458
+ height="12"
459
+ viewBox="0 0 12 12"
460
+ fill="none"
461
+ xmlns="http://www.w3.org/2000/svg"
462
+ aria-hidden="true"
463
+ >
464
+ <path
465
+ d="M6 2L8.5 5H3.5L6 2Z"
466
+ fill="currentColor"
467
+ opacity="0.3"
468
+ />
469
+ <path
470
+ d="M6 10L3.5 7H8.5L6 10Z"
471
+ fill="currentColor"
472
+ opacity="0.3"
473
+ />
474
+ </svg>
475
+ );
476
+ }
477
+
478
+ function SortAscIcon() {
479
+ return (
480
+ <svg
481
+ width="12"
482
+ height="12"
483
+ viewBox="0 0 12 12"
484
+ fill="none"
485
+ xmlns="http://www.w3.org/2000/svg"
486
+ aria-hidden="true"
487
+ >
488
+ <path d="M6 2L8.5 5H3.5L6 2Z" fill="currentColor" />
489
+ </svg>
490
+ );
491
+ }
492
+
493
+ function SortDescIcon() {
494
+ return (
495
+ <svg
496
+ width="12"
497
+ height="12"
498
+ viewBox="0 0 12 12"
499
+ fill="none"
500
+ xmlns="http://www.w3.org/2000/svg"
501
+ aria-hidden="true"
502
+ >
503
+ <path d="M6 10L3.5 7H8.5L6 10Z" fill="currentColor" />
504
+ </svg>
505
+ );
506
+ }
507
+
508
+ // Helper to create simple columns without TanStack's createColumnHelper
509
+ export function createColumns<T>(
510
+ columns: Array<{
511
+ key: string;
512
+ header: string;
513
+ width?: number;
514
+ cell?: (row: T) => React.ReactNode;
515
+ }>
516
+ ): DataTableColumn<T>[] {
517
+ return columns.map((col) => ({
518
+ id: col.key,
519
+ accessorKey: col.key,
520
+ header: col.header,
521
+ size: col.width,
522
+ minSize: col.width,
523
+ maxSize: col.width,
524
+ cell: col.cell
525
+ ? ({ row }) => col.cell!(row.original)
526
+ : ({ getValue }) => getValue() ?? '--',
527
+ }));
528
+ }
529
+
530
+ export const DataTable = Object.assign(DataTableRoot, {
531
+ Root: DataTableRoot,
532
+ Columns: createColumns,
533
+ });