@shohojdhara/atomix 0.3.7 → 0.3.8

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 (53) hide show
  1. package/dist/atomix.css +77 -0
  2. package/dist/atomix.css.map +1 -1
  3. package/dist/atomix.min.css +77 -0
  4. package/dist/atomix.min.css.map +1 -1
  5. package/dist/charts.js.map +1 -1
  6. package/dist/core.d.ts +2 -2
  7. package/dist/core.js.map +1 -1
  8. package/dist/forms.js.map +1 -1
  9. package/dist/heavy.js.map +1 -1
  10. package/dist/index.d.ts +578 -515
  11. package/dist/index.esm.js +3157 -2626
  12. package/dist/index.esm.js.map +1 -1
  13. package/dist/index.js +10496 -9973
  14. package/dist/index.js.map +1 -1
  15. package/dist/index.min.js +1 -1
  16. package/dist/index.min.js.map +1 -1
  17. package/dist/theme.d.ts +237 -420
  18. package/dist/theme.js +1629 -1701
  19. package/dist/theme.js.map +1 -1
  20. package/package.json +1 -1
  21. package/src/components/DataTable/DataTable.stories.tsx +238 -0
  22. package/src/components/DataTable/DataTable.test.tsx +450 -0
  23. package/src/components/DataTable/DataTable.tsx +384 -61
  24. package/src/components/DatePicker/DatePicker.tsx +29 -38
  25. package/src/components/Upload/Upload.tsx +539 -40
  26. package/src/lib/composables/useDataTable.ts +355 -15
  27. package/src/lib/composables/useDatePicker.ts +19 -0
  28. package/src/lib/constants/components.ts +10 -0
  29. package/src/lib/theme/adapters/cssVariableMapper.ts +29 -14
  30. package/src/lib/theme/adapters/index.ts +1 -4
  31. package/src/lib/theme/config/configLoader.ts +53 -35
  32. package/src/lib/theme/core/composeTheme.ts +22 -30
  33. package/src/lib/theme/core/createTheme.ts +49 -26
  34. package/src/lib/theme/core/index.ts +0 -1
  35. package/src/lib/theme/generators/generateCSSNested.ts +4 -3
  36. package/src/lib/theme/generators/generateCSSVariables.ts +24 -16
  37. package/src/lib/theme/index.ts +10 -17
  38. package/src/lib/theme/runtime/ThemeApplicator.ts +6 -109
  39. package/src/lib/theme/runtime/ThemeErrorBoundary.tsx +3 -3
  40. package/src/lib/theme/runtime/ThemeProvider.tsx +186 -44
  41. package/src/lib/theme/runtime/useTheme.ts +1 -1
  42. package/src/lib/theme/runtime/useThemeTokens.ts +7 -16
  43. package/src/lib/theme/test/testTheme.ts +2 -1
  44. package/src/lib/theme/types.ts +14 -14
  45. package/src/lib/theme/utils/componentTheming.ts +35 -27
  46. package/src/lib/theme/utils/domUtils.ts +57 -15
  47. package/src/lib/theme/utils/injectCSS.ts +0 -1
  48. package/src/lib/theme/utils/themeHelpers.ts +1 -39
  49. package/src/lib/theme/utils/themeUtils.ts +1 -170
  50. package/src/lib/types/components.ts +145 -0
  51. package/src/lib/utils/dataTableExport.ts +143 -0
  52. package/src/styles/06-components/_components.data-table.scss +95 -0
  53. package/src/lib/hooks/useThemeTokens.ts +0 -105
@@ -1,13 +1,30 @@
1
- import React, { useRef, memo } from 'react';
2
- import { DataTableProps } from '../../lib/types/components';
1
+ import React, { useRef, memo, useState, useCallback, useEffect } from 'react';
2
+ import { DataTableProps, SelectionMode, ExportFormat } from '../../lib/types/components';
3
3
  import { useDataTable } from '../../lib/composables/useDataTable';
4
4
  import { DATA_TABLE_CLASSES } from '../../lib/constants/components';
5
5
  import { Spinner } from '../Spinner/Spinner';
6
6
  import { Icon } from '../Icon/Icon';
7
7
  import { Pagination } from '../Pagination/Pagination';
8
+ import { Checkbox } from '../Form/Checkbox';
9
+ import { Dropdown, DropdownItem, DropdownDivider } from '../Dropdown/Dropdown';
10
+ import { exportData } from '../../lib/utils/dataTableExport';
11
+ import { Button } from '../Button/Button';
8
12
 
9
13
  /**
10
- * DataTable - A flexible and accessible data table component
14
+ * Get unique row ID
15
+ */
16
+ function getRowId(row: any, rowKey?: string | ((row: any) => string | number)): string | number {
17
+ if (typeof rowKey === 'function') {
18
+ return rowKey(row);
19
+ }
20
+ if (typeof rowKey === 'string') {
21
+ return row[rowKey];
22
+ }
23
+ return row.id ?? row.key ?? JSON.stringify(row);
24
+ }
25
+
26
+ /**
27
+ * DataTable - A flexible and accessible data table component with advanced features
11
28
  *
12
29
  * @example
13
30
  * ```tsx
@@ -15,6 +32,7 @@ import { Pagination } from '../Pagination/Pagination';
15
32
  * data={users}
16
33
  * columns={columns}
17
34
  * sortable={true}
35
+ * selectionMode="multiple"
18
36
  * onRowClick={handleRowClick}
19
37
  * />
20
38
  * ```
@@ -35,9 +53,33 @@ export const DataTable: React.FC<DataTableProps> = memo(({
35
53
  emptyMessage = 'No data available',
36
54
  onRowClick,
37
55
  onSort,
56
+ selectionMode = 'none',
57
+ selectedRowIds: controlledSelectedRowIds,
58
+ onSelectionChange,
59
+ rowKey,
60
+ resizable = false,
61
+ reorderable = false,
62
+ onColumnReorder,
63
+ showColumnVisibility = false,
64
+ onColumnVisibilityChange,
65
+ stickyHeader = false,
66
+ stickyHeaderOffset = '0px',
67
+ virtualScrolling = false,
68
+ estimatedRowHeight = 50,
69
+ overscan = 5,
70
+ exportable = false,
71
+ exportFormats = ['csv', 'excel', 'json'],
72
+ exportFilename = 'data-table',
73
+ onExport,
74
+ columnFilters = false,
38
75
  ...props
39
76
  }) => {
40
77
  const tableRef = useRef<HTMLTableElement>(null);
78
+ const headerRef = useRef<HTMLTableSectionElement>(null);
79
+ const [resizingColumn, setResizingColumn] = useState<string | null>(null);
80
+ const [columnWidths, setColumnWidths] = useState<Record<string, number>>({});
81
+ const [dragStartIndex, setDragStartIndex] = useState<number | null>(null);
82
+ const [dragOverIndex, setDragOverIndex] = useState<number | null>(null);
41
83
 
42
84
  const {
43
85
  displayData,
@@ -47,6 +89,18 @@ export const DataTable: React.FC<DataTableProps> = memo(({
47
89
  handleSort,
48
90
  handlePageChange,
49
91
  handleSearch,
92
+ selectedRowIds,
93
+ selectedRows,
94
+ handleRowSelect,
95
+ handleSelectAll,
96
+ isAllSelected,
97
+ isIndeterminate,
98
+ visibleColumns,
99
+ columnVisibility,
100
+ handleColumnVisibilityToggle,
101
+ columnFilterValues,
102
+ handleColumnFilterChange,
103
+ clearColumnFilters,
50
104
  } = useDataTable({
51
105
  data,
52
106
  columns,
@@ -54,8 +108,92 @@ export const DataTable: React.FC<DataTableProps> = memo(({
54
108
  paginated,
55
109
  pageSize,
56
110
  onSort,
111
+ selectionMode,
112
+ selectedRowIds: controlledSelectedRowIds,
113
+ onSelectionChange,
114
+ rowKey,
115
+ columnFilters,
116
+ reorderable,
117
+ onColumnReorder,
118
+ onColumnVisibilityChange,
57
119
  });
58
120
 
121
+ // Initialize column widths
122
+ useEffect(() => {
123
+ const widths: Record<string, number> = {};
124
+ visibleColumns.forEach(col => {
125
+ if (col.width) {
126
+ const widthValue = parseInt(col.width, 10);
127
+ if (!isNaN(widthValue)) {
128
+ widths[col.key] = widthValue;
129
+ }
130
+ }
131
+ });
132
+ setColumnWidths(widths);
133
+ }, [visibleColumns]);
134
+
135
+ // Handle column resize start
136
+ const handleResizeStart = useCallback((columnKey: string, e: React.MouseEvent) => {
137
+ e.preventDefault();
138
+ e.stopPropagation();
139
+ setResizingColumn(columnKey);
140
+
141
+ const startX = e.clientX;
142
+ const startWidth = columnWidths[columnKey] || 100;
143
+
144
+ const handleMouseMove = (moveEvent: MouseEvent) => {
145
+ const diff = moveEvent.clientX - startX;
146
+ const newWidth = Math.max(50, startWidth + diff); // Minimum width of 50px
147
+
148
+ setColumnWidths(prev => ({
149
+ ...prev,
150
+ [columnKey]: newWidth,
151
+ }));
152
+ };
153
+
154
+ const handleMouseUp = () => {
155
+ setResizingColumn(null);
156
+ document.removeEventListener('mousemove', handleMouseMove);
157
+ document.removeEventListener('mouseup', handleMouseUp);
158
+ };
159
+
160
+ document.addEventListener('mousemove', handleMouseMove);
161
+ document.addEventListener('mouseup', handleMouseUp);
162
+ }, [columnWidths]);
163
+
164
+ // Handle column drag start
165
+ const handleDragStart = useCallback((index: number) => {
166
+ setDragStartIndex(index);
167
+ }, []);
168
+
169
+ // Handle column drag over
170
+ const handleDragOver = useCallback((e: React.DragEvent, index: number) => {
171
+ e.preventDefault();
172
+ setDragOverIndex(index);
173
+ }, []);
174
+
175
+ // Handle column drop
176
+ const handleDrop = useCallback((e: React.DragEvent, dropIndex: number) => {
177
+ e.preventDefault();
178
+ if (dragStartIndex !== null && dragStartIndex !== dropIndex && onColumnReorder) {
179
+ const newOrder = [...visibleColumns.map(col => col.key)];
180
+ const [removed] = newOrder.splice(dragStartIndex, 1);
181
+ newOrder.splice(dropIndex, 0, removed);
182
+ onColumnReorder(newOrder);
183
+ }
184
+ setDragStartIndex(null);
185
+ setDragOverIndex(null);
186
+ }, [dragStartIndex, visibleColumns, onColumnReorder]);
187
+
188
+ // Handle export
189
+ const handleExport = useCallback((format: ExportFormat) => {
190
+ if (onExport) {
191
+ onExport(format, displayData);
192
+ } else {
193
+ exportData(format, displayData, visibleColumns, exportFilename);
194
+ }
195
+ }, [displayData, visibleColumns, exportFilename, onExport]);
196
+
59
197
  // Generate component classes
60
198
  const tableClass = [
61
199
  DATA_TABLE_CLASSES.base,
@@ -63,46 +201,114 @@ export const DataTable: React.FC<DataTableProps> = memo(({
63
201
  bordered && DATA_TABLE_CLASSES.bordered,
64
202
  dense && DATA_TABLE_CLASSES.dense,
65
203
  loading && DATA_TABLE_CLASSES.loading,
204
+ stickyHeader && DATA_TABLE_CLASSES.stickyHeader,
66
205
  className,
67
206
  ]
68
207
  .filter(Boolean)
69
208
  .join(' ');
70
209
 
210
+ const containerStyle: React.CSSProperties = {
211
+ ...style,
212
+ ...(stickyHeader && {
213
+ '--sticky-header-offset': stickyHeaderOffset,
214
+ } as React.CSSProperties),
215
+ };
216
+
217
+ const renderSelectionHeader = () => {
218
+ if (selectionMode === 'none') return null;
219
+
220
+ return (
221
+ <th
222
+ className={`${DATA_TABLE_CLASSES.headerCell} ${DATA_TABLE_CLASSES.selectionCell}`}
223
+ style={{ width: '48px' }}
224
+ >
225
+ {selectionMode === 'multiple' && (
226
+ <Checkbox
227
+ checked={isAllSelected}
228
+ indeterminate={isIndeterminate}
229
+ onChange={(e) => handleSelectAll(e.target.checked)}
230
+ aria-label="Select all rows"
231
+ />
232
+ )}
233
+ </th>
234
+ );
235
+ };
236
+
71
237
  const renderHeader = () => {
72
238
  return (
73
- <thead className={DATA_TABLE_CLASSES.header}>
239
+ <thead
240
+ ref={headerRef}
241
+ className={DATA_TABLE_CLASSES.header}
242
+ style={stickyHeader ? { position: 'sticky', top: stickyHeaderOffset, zIndex: 10 } : undefined}
243
+ >
74
244
  <tr>
75
- {columns.map((column, index) => (
76
- <th
77
- key={`header-${index}`}
78
- className={`${DATA_TABLE_CLASSES.headerCell} ${column.sortable !== false && sortable ? DATA_TABLE_CLASSES.sortable : ''}`}
79
- onClick={() =>
80
- column.sortable !== false && sortable ? handleSort(column.key) : null
81
- }
82
- aria-sort={
83
- sortConfig?.key === column.key
84
- ? sortConfig.direction === 'asc'
85
- ? 'ascending'
86
- : 'descending'
87
- : undefined
88
- }
89
- >
90
- <div className={DATA_TABLE_CLASSES.headerContent}>
91
- <span>{column.title}</span>
92
- {column.sortable !== false && sortable && (
93
- <span className={DATA_TABLE_CLASSES.sortIcon}>
94
- {sortConfig?.key === column.key ? (
95
- sortConfig.direction === 'asc' ? (
96
- <Icon name="CaretUp" size="sm" />
97
- ) : (
98
- <Icon name="CaretDown" size="sm" />
99
- )
100
- ) : null}
101
- </span>
245
+ {renderSelectionHeader()}
246
+ {visibleColumns.map((column, index) => {
247
+ const isDragging = dragStartIndex === index;
248
+ const isDragOver = dragOverIndex === index;
249
+
250
+ return (
251
+ <th
252
+ key={`header-${column.key}`}
253
+ className={[
254
+ DATA_TABLE_CLASSES.headerCell,
255
+ column.sortable !== false && sortable ? DATA_TABLE_CLASSES.sortable : '',
256
+ isDragging ? DATA_TABLE_CLASSES.dragging : '',
257
+ isDragOver ? DATA_TABLE_CLASSES.dragOver : '',
258
+ ].filter(Boolean).join(' ')}
259
+ style={{
260
+ ...(columnWidths[column.key] && { width: `${columnWidths[column.key]}px` }),
261
+ ...(column.width && !columnWidths[column.key] && { width: column.width }),
262
+ }}
263
+ onClick={() => column.sortable !== false && sortable ? handleSort(column.key) : null}
264
+ onDragStart={reorderable ? () => handleDragStart(index) : undefined}
265
+ onDragOver={reorderable ? (e) => handleDragOver(e, index) : undefined}
266
+ onDrop={reorderable ? (e) => handleDrop(e, index) : undefined}
267
+ draggable={reorderable}
268
+ aria-sort={
269
+ sortConfig?.key === column.key
270
+ ? sortConfig.direction === 'asc'
271
+ ? 'ascending'
272
+ : 'descending'
273
+ : undefined
274
+ }
275
+ >
276
+ <div className={DATA_TABLE_CLASSES.headerContent}>
277
+ <span>{column.title}</span>
278
+ <div className={DATA_TABLE_CLASSES.headerActions}>
279
+ {column.sortable !== false && sortable && (
280
+ <span className={DATA_TABLE_CLASSES.sortIcon}>
281
+ {sortConfig?.key === column.key ? (
282
+ sortConfig.direction === 'asc' ? (
283
+ <Icon name="CaretUp" size="sm" />
284
+ ) : (
285
+ <Icon name="CaretDown" size="sm" />
286
+ )
287
+ ) : null}
288
+ </span>
289
+ )}
290
+ {columnFilters && column.filterable !== false && (
291
+ <input
292
+ type="text"
293
+ className={DATA_TABLE_CLASSES.columnFilter}
294
+ placeholder="Filter..."
295
+ value={columnFilterValues[column.key] || ''}
296
+ onChange={(e) => handleColumnFilterChange(column.key, e.target.value)}
297
+ onClick={(e) => e.stopPropagation()}
298
+ aria-label={`Filter ${column.title}`}
299
+ />
300
+ )}
301
+ </div>
302
+ </div>
303
+ {resizable && (column.resizable !== false) && (
304
+ <div
305
+ className={DATA_TABLE_CLASSES.resizeHandle}
306
+ onMouseDown={(e) => handleResizeStart(column.key, e)}
307
+ />
102
308
  )}
103
- </div>
104
- </th>
105
- ))}
309
+ </th>
310
+ );
311
+ })}
106
312
  </tr>
107
313
  </thead>
108
314
  );
@@ -113,7 +319,7 @@ export const DataTable: React.FC<DataTableProps> = memo(({
113
319
  return (
114
320
  <tbody>
115
321
  <tr>
116
- <td colSpan={columns.length} className={DATA_TABLE_CLASSES.loadingCell}>
322
+ <td colSpan={visibleColumns.length + (selectionMode !== 'none' ? 1 : 0)} className={DATA_TABLE_CLASSES.loadingCell}>
117
323
  <div className={DATA_TABLE_CLASSES.loadingIndicator}>
118
324
  <Spinner size="md" variant="primary" />
119
325
  </div>
@@ -127,7 +333,7 @@ export const DataTable: React.FC<DataTableProps> = memo(({
127
333
  return (
128
334
  <tbody>
129
335
  <tr>
130
- <td colSpan={columns.length} className={DATA_TABLE_CLASSES.emptyCell}>
336
+ <td colSpan={visibleColumns.length + (selectionMode !== 'none' ? 1 : 0)} className={DATA_TABLE_CLASSES.emptyCell}>
131
337
  {emptyMessage}
132
338
  </td>
133
339
  </tr>
@@ -137,21 +343,58 @@ export const DataTable: React.FC<DataTableProps> = memo(({
137
343
 
138
344
  return (
139
345
  <tbody>
140
- {displayData.map((row: any, rowIndex: number) => (
141
- <tr
142
- key={`row-${rowIndex}`}
143
- className={DATA_TABLE_CLASSES.row}
144
- onClick={onRowClick ? () => onRowClick(row) : undefined}
145
- tabIndex={onRowClick ? 0 : undefined}
146
- role={onRowClick ? 'button' : undefined}
147
- >
148
- {columns.map((column, colIndex) => (
149
- <td key={`cell-${rowIndex}-${colIndex}`} className={DATA_TABLE_CLASSES.cell}>
150
- {column.render ? column.render(row[column.key], row) : row[column.key]}
151
- </td>
152
- ))}
153
- </tr>
154
- ))}
346
+ {displayData.map((row: any, rowIndex: number) => {
347
+ const rowId = getRowId(row, rowKey);
348
+ const isSelected = selectedRowIds.includes(rowId);
349
+
350
+ return (
351
+ <tr
352
+ key={`row-${rowId}`}
353
+ className={[
354
+ DATA_TABLE_CLASSES.row,
355
+ isSelected ? DATA_TABLE_CLASSES.rowSelected : '',
356
+ ].filter(Boolean).join(' ')}
357
+ onClick={onRowClick ? () => onRowClick(row) : undefined}
358
+ tabIndex={onRowClick ? 0 : undefined}
359
+ role={onRowClick ? 'button' : undefined}
360
+ >
361
+ {selectionMode !== 'none' && (
362
+ <td className={`${DATA_TABLE_CLASSES.cell} ${DATA_TABLE_CLASSES.selectionCell}`}>
363
+ {selectionMode === 'multiple' ? (
364
+ <Checkbox
365
+ checked={isSelected}
366
+ onChange={(e) => handleRowSelect(rowId, e.target.checked)}
367
+ onClick={(e) => e.stopPropagation()}
368
+ aria-label={`Select row ${rowIndex + 1}`}
369
+ />
370
+ ) : (
371
+ <input
372
+ type="radio"
373
+ checked={isSelected}
374
+ onChange={() => handleRowSelect(rowId, true)}
375
+ onClick={(e) => e.stopPropagation()}
376
+ name="data-table-row-selection"
377
+ aria-label={`Select row ${rowIndex + 1}`}
378
+ className="c-data-table__radio"
379
+ />
380
+ )}
381
+ </td>
382
+ )}
383
+ {visibleColumns.map((column) => (
384
+ <td
385
+ key={`cell-${rowId}-${column.key}`}
386
+ className={DATA_TABLE_CLASSES.cell}
387
+ style={{
388
+ ...(columnWidths[column.key] && { width: `${columnWidths[column.key]}px` }),
389
+ ...(column.width && !columnWidths[column.key] && { width: column.width }),
390
+ }}
391
+ >
392
+ {column.render ? column.render(row[column.key], row) : row[column.key]}
393
+ </td>
394
+ ))}
395
+ </tr>
396
+ );
397
+ })}
155
398
  </tbody>
156
399
  );
157
400
  };
@@ -176,25 +419,105 @@ export const DataTable: React.FC<DataTableProps> = memo(({
176
419
  };
177
420
 
178
421
  const renderToolbar = () => {
179
- if (!filterable) return null;
422
+ const hasToolbar = filterable || exportable || showColumnVisibility || columnFilters;
423
+ if (!hasToolbar) return null;
180
424
 
181
425
  return (
182
426
  <div className={DATA_TABLE_CLASSES.toolbar}>
183
- <div className={DATA_TABLE_CLASSES.search}>
184
- <input
185
- type="text"
186
- placeholder="Search..."
187
- className={`${DATA_TABLE_CLASSES.searchInput} c-input`}
188
- onChange={e => handleSearch(e.target.value)}
189
- aria-label="Search table"
190
- />
427
+ <div className={DATA_TABLE_CLASSES.toolbarLeft}>
428
+ {filterable && (
429
+ <div className={DATA_TABLE_CLASSES.search}>
430
+ <input
431
+ type="text"
432
+ placeholder="Search..."
433
+ className={`${DATA_TABLE_CLASSES.searchInput} c-input`}
434
+ onChange={e => handleSearch(e.target.value)}
435
+ aria-label="Search table"
436
+ />
437
+ </div>
438
+ )}
439
+ {columnFilters && Object.keys(columnFilterValues).length > 0 && (
440
+ <Button
441
+ size="sm"
442
+ variant="secondary"
443
+ onClick={clearColumnFilters}
444
+ >
445
+ Clear Filters
446
+ </Button>
447
+ )}
448
+ </div>
449
+ <div className={DATA_TABLE_CLASSES.toolbarRight}>
450
+ {showColumnVisibility && (
451
+ <Dropdown
452
+ trigger="click"
453
+ placement="bottom-end"
454
+ menu={
455
+ <>
456
+ {columns.map((column) => (
457
+ <DropdownItem
458
+ key={column.key}
459
+ onClick={(e) => {
460
+ e.preventDefault();
461
+ e.stopPropagation();
462
+ handleColumnVisibilityToggle(column.key);
463
+ }}
464
+ >
465
+ <label style={{ display: 'flex', alignItems: 'center', cursor: 'pointer', width: '100%' }}>
466
+ <Checkbox
467
+ checked={columnVisibility[column.key] !== false}
468
+ onChange={() => handleColumnVisibilityToggle(column.key)}
469
+ onClick={(e) => e.stopPropagation()}
470
+ />
471
+ <span style={{ marginLeft: '0.5rem' }}>{column.title}</span>
472
+ </label>
473
+ </DropdownItem>
474
+ ))}
475
+ </>
476
+ }
477
+ >
478
+ <Button size="sm" variant="secondary">
479
+ <Icon name="Columns" size="sm" />
480
+ Columns
481
+ </Button>
482
+ </Dropdown>
483
+ )}
484
+ {exportable && (
485
+ <Dropdown
486
+ trigger="click"
487
+ placement="bottom-end"
488
+ menu={
489
+ <>
490
+ {exportFormats.includes('csv') && (
491
+ <DropdownItem onClick={() => handleExport('csv')}>
492
+ Export as CSV
493
+ </DropdownItem>
494
+ )}
495
+ {exportFormats.includes('excel') && (
496
+ <DropdownItem onClick={() => handleExport('excel')}>
497
+ Export as Excel
498
+ </DropdownItem>
499
+ )}
500
+ {exportFormats.includes('json') && (
501
+ <DropdownItem onClick={() => handleExport('json')}>
502
+ Export as JSON
503
+ </DropdownItem>
504
+ )}
505
+ </>
506
+ }
507
+ >
508
+ <Button size="sm" variant="secondary">
509
+ <Icon name="Download" size="sm" />
510
+ Export
511
+ </Button>
512
+ </Dropdown>
513
+ )}
191
514
  </div>
192
515
  </div>
193
516
  );
194
517
  };
195
518
 
196
519
  return (
197
- <div className={DATA_TABLE_CLASSES.container} style={style} {...props}>
520
+ <div className={DATA_TABLE_CLASSES.container} style={containerStyle} {...props}>
198
521
  {renderToolbar()}
199
522
  <div className={DATA_TABLE_CLASSES.tableWrapper}>
200
523
  <table ref={tableRef} className={tableClass}>
@@ -73,8 +73,11 @@ export const DatePicker = forwardRef<DatePickerRef, DatePickerProps>(
73
73
  // View mode handlers
74
74
  switchToMonthView,
75
75
  switchToYearView,
76
+ switchToDayView,
76
77
  selectMonth,
77
78
  selectYear,
79
+ handlePrevDecade,
80
+ handleNextDecade,
78
81
 
79
82
  // Utility methods
80
83
  generateDays,
@@ -186,21 +189,6 @@ export const DatePicker = forwardRef<DatePickerRef, DatePickerProps>(
186
189
  return weeks;
187
190
  };
188
191
 
189
- const handlePrevDecade = () => {
190
- // This would need to be implemented in the hook or handled differently
191
- // For now, we'll work with what we have
192
- };
193
-
194
- const handleNextDecade = () => {
195
- // This would need to be implemented in the hook or handled differently
196
- // For now, we'll work with what we have
197
- };
198
-
199
- const switchToDayView = () => {
200
- // This would need to be implemented in the hook or handled differently
201
- // For now, we'll work with what we have
202
- };
203
-
204
192
  // Helper function to render calendar content
205
193
  const renderCalendarContent = () => (
206
194
  <>
@@ -279,29 +267,32 @@ export const DatePicker = forwardRef<DatePickerRef, DatePickerProps>(
279
267
  </>
280
268
  )}
281
269
 
282
- {viewMode === 'years' && (
283
- <>
284
- <button
285
- type="button"
286
- className="c-datepicker__nav-button c-datepicker__nav-button--prev-decade"
287
- onClick={handlePrevDecade}
288
- aria-label="Previous decade"
289
- >
290
- <Icon name="CaretDoubleLeft" size="sm" />
291
- </button>
292
- <button type="button" className="c-datepicker__view-switch" onClick={switchToDayView}>
293
- {generateYears()[0]} - {generateYears()[generateYears().length - 1]}
294
- </button>
295
- <button
296
- type="button"
297
- className="c-datepicker__nav-button c-datepicker__nav-button--next-decade"
298
- onClick={handleNextDecade}
299
- aria-label="Next decade"
300
- >
301
- <Icon name="CaretDoubleRight" size="sm" />
302
- </button>
303
- </>
304
- )}
270
+ {viewMode === 'years' && (() => {
271
+ const years = generateYears();
272
+ return (
273
+ <>
274
+ <button
275
+ type="button"
276
+ className="c-datepicker__nav-button c-datepicker__nav-button--prev-decade"
277
+ onClick={handlePrevDecade}
278
+ aria-label="Previous decade"
279
+ >
280
+ <Icon name="CaretDoubleLeft" size="sm" />
281
+ </button>
282
+ <button type="button" className="c-datepicker__view-switch" onClick={switchToDayView}>
283
+ {years[0]} - {years[years.length - 1]}
284
+ </button>
285
+ <button
286
+ type="button"
287
+ className="c-datepicker__nav-button c-datepicker__nav-button--next-decade"
288
+ onClick={handleNextDecade}
289
+ aria-label="Next decade"
290
+ >
291
+ <Icon name="CaretDoubleRight" size="sm" />
292
+ </button>
293
+ </>
294
+ );
295
+ })()}
305
296
  </div>
306
297
 
307
298
  <div className="c-datepicker__body">