@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.
- package/dist/atomix.css +77 -0
- package/dist/atomix.css.map +1 -1
- package/dist/atomix.min.css +77 -0
- package/dist/atomix.min.css.map +1 -1
- package/dist/charts.js.map +1 -1
- package/dist/core.d.ts +2 -2
- package/dist/core.js.map +1 -1
- package/dist/forms.js.map +1 -1
- package/dist/heavy.js.map +1 -1
- package/dist/index.d.ts +578 -515
- package/dist/index.esm.js +3157 -2626
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +10496 -9973
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/theme.d.ts +237 -420
- package/dist/theme.js +1629 -1701
- package/dist/theme.js.map +1 -1
- package/package.json +1 -1
- package/src/components/DataTable/DataTable.stories.tsx +238 -0
- package/src/components/DataTable/DataTable.test.tsx +450 -0
- package/src/components/DataTable/DataTable.tsx +384 -61
- package/src/components/DatePicker/DatePicker.tsx +29 -38
- package/src/components/Upload/Upload.tsx +539 -40
- package/src/lib/composables/useDataTable.ts +355 -15
- package/src/lib/composables/useDatePicker.ts +19 -0
- package/src/lib/constants/components.ts +10 -0
- package/src/lib/theme/adapters/cssVariableMapper.ts +29 -14
- package/src/lib/theme/adapters/index.ts +1 -4
- package/src/lib/theme/config/configLoader.ts +53 -35
- package/src/lib/theme/core/composeTheme.ts +22 -30
- package/src/lib/theme/core/createTheme.ts +49 -26
- package/src/lib/theme/core/index.ts +0 -1
- package/src/lib/theme/generators/generateCSSNested.ts +4 -3
- package/src/lib/theme/generators/generateCSSVariables.ts +24 -16
- package/src/lib/theme/index.ts +10 -17
- package/src/lib/theme/runtime/ThemeApplicator.ts +6 -109
- package/src/lib/theme/runtime/ThemeErrorBoundary.tsx +3 -3
- package/src/lib/theme/runtime/ThemeProvider.tsx +186 -44
- package/src/lib/theme/runtime/useTheme.ts +1 -1
- package/src/lib/theme/runtime/useThemeTokens.ts +7 -16
- package/src/lib/theme/test/testTheme.ts +2 -1
- package/src/lib/theme/types.ts +14 -14
- package/src/lib/theme/utils/componentTheming.ts +35 -27
- package/src/lib/theme/utils/domUtils.ts +57 -15
- package/src/lib/theme/utils/injectCSS.ts +0 -1
- package/src/lib/theme/utils/themeHelpers.ts +1 -39
- package/src/lib/theme/utils/themeUtils.ts +1 -170
- package/src/lib/types/components.ts +145 -0
- package/src/lib/utils/dataTableExport.ts +143 -0
- package/src/styles/06-components/_components.data-table.scss +95 -0
- 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
|
-
*
|
|
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
|
|
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
|
-
{
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
:
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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
|
-
</
|
|
104
|
-
|
|
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={
|
|
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={
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
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.
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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={
|
|
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
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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">
|