@jmruthers/pace-core 0.2.5 → 0.2.7
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/{DataTable-BHlzyKZP.d.ts → DataTable-C1AEm9Cx.d.ts} +1 -1
- package/dist/{DataTable-GEY5U7OI.js → DataTable-EEUDXPE5.js} +2 -8
- package/dist/{api-T6CBS7IO.js → api-ETQ6YJ3C.js} +2 -3
- package/dist/{chunk-DY5E3AT7.js → chunk-BEZRLNK3.js} +13 -3
- package/dist/chunk-BEZRLNK3.js.map +1 -0
- package/dist/{chunk-ANE4PDC2.js → chunk-C5G2A4PO.js} +159 -6
- package/dist/chunk-C5G2A4PO.js.map +1 -0
- package/dist/{chunk-WYB6MBZA.js → chunk-EWKPTNPO.js} +579 -973
- package/dist/chunk-EWKPTNPO.js.map +1 -0
- package/dist/{chunk-TMRLB2LA.js → chunk-HEMJ4SUJ.js} +2 -2
- package/dist/{chunk-O4T53L7X.js → chunk-HNDFPXUU.js} +5 -5
- package/dist/{chunk-UY7AM4QG.js → chunk-RRUYHORU.js} +161 -74
- package/dist/chunk-RRUYHORU.js.map +1 -0
- package/dist/{chunk-PFRRIDYA.js → chunk-TIVL4UQ7.js} +2 -2
- package/dist/{chunk-2MKP6IYD.js → chunk-VYG4AXYW.js} +2 -2
- package/dist/components.d.ts +2 -2
- package/dist/components.js +15 -16
- package/dist/components.js.map +1 -1
- package/dist/hooks.d.ts +1 -1
- package/dist/hooks.js +4 -4
- package/dist/index.d.ts +2 -2
- package/dist/index.js +16 -17
- package/dist/index.js.map +1 -1
- package/dist/providers.js +2 -2
- package/dist/rbac/index.js +25 -20
- package/dist/rbac/index.js.map +1 -1
- package/dist/styles/core.css +83 -62
- package/dist/{types-CInEi-ng.d.ts → types-DiRQsGJs.d.ts} +0 -2
- package/dist/utils.d.ts +2 -2
- package/dist/utils.js +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/PublicErrorBoundary.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CardProps.md +1 -1
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +1 -1
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +33 -33
- package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EventContextType.md +1 -1
- package/docs/api/interfaces/EventLogoProps.md +1 -1
- package/docs/api/interfaces/EventProviderProps.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadProps.md +1 -1
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +1 -1
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
- package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
- package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +1 -1
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +10 -10
- package/docs/architecture/README.md +1 -1
- package/package.json +1 -1
- package/src/__tests__/shared/testUtils.optimized.tsx +65 -7
- package/src/components/DataTable/DataTable.tsx +1 -3
- package/src/components/DataTable/__tests__/DataTable.errorHandling.test.tsx +0 -8
- package/src/components/DataTable/__tests__/DataTable.hierarchical.test.tsx +17 -12
- package/src/components/DataTable/__tests__/DataTable.infinite-loop.test.tsx +0 -1
- package/src/components/DataTable/__tests__/DataTable.integration.test.tsx +4 -12
- package/src/components/DataTable/__tests__/DataTable.performance.test.tsx +0 -8
- package/src/components/DataTable/__tests__/DataTable.permissions.test.tsx +21 -11
- package/src/components/DataTable/__tests__/DataTable.sorting.test.tsx +321 -0
- package/src/components/DataTable/__tests__/DataTable.userWorkflows.test.tsx +21 -11
- package/src/components/DataTable/__tests__/DataTable.workflowValidation.test.tsx +94 -0
- package/src/components/DataTable/__tests__/DataTable.workflows.test.tsx +25 -15
- package/src/components/DataTable/__tests__/README.md +11 -2
- package/src/components/DataTable/__tests__/performance-regression.test.tsx +0 -11
- package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +0 -1
- package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +2 -2
- package/src/components/DataTable/components/DataTableBody.tsx +34 -35
- package/src/components/DataTable/components/DataTableCore.tsx +205 -133
- package/src/components/DataTable/components/DataTableToolbar.tsx +9 -10
- package/src/components/DataTable/components/DraggableColumnHeader.tsx +3 -7
- package/src/components/DataTable/components/EditableRow.tsx +6 -7
- package/src/components/DataTable/components/FilterRow.tsx +0 -1
- package/src/components/DataTable/components/GroupingDropdown.tsx +2 -2
- package/src/components/DataTable/components/UnifiedTableBody.tsx +83 -281
- package/src/components/DataTable/components/VirtualizedDataTable.tsx +9 -89
- package/src/components/DataTable/components/__tests__/DataTable.accessibility.test.tsx +111 -5
- package/src/components/DataTable/components/__tests__/DataTable.integration.test.tsx +82 -13
- package/src/components/DataTable/components/__tests__/DataTable.performance.test.tsx +0 -1
- package/src/components/DataTable/components/__tests__/DataTable.real.test.tsx +2 -2
- package/src/components/DataTable/components/__tests__/DataTable.security.test.tsx +0 -1
- package/src/components/DataTable/components/__tests__/DataTable.unit.test.tsx +2 -2
- package/src/components/DataTable/components/__tests__/FilteringToggle.unit.test.tsx +3 -0
- package/src/components/DataTable/components/index.ts +0 -1
- package/src/components/DataTable/core/DataTableContext.tsx +0 -1
- package/src/components/DataTable/index.ts +0 -2
- package/src/components/DataTable/types.ts +0 -2
- package/src/components/Input/Input.tsx +2 -2
- package/src/components/Input/__tests__/Input.unit.test.tsx +4 -4
- package/src/components/PaceAppLayout/__tests__/PaceAppLayout.integration.test.tsx +6 -2
- package/src/components/RBAC/PagePermissionGuard.tsx +13 -0
- package/src/components/RBAC/__tests__/PagePermissionGuard.unit.test.tsx +10 -1
- package/src/components/Select/Select.tsx +7 -1
- package/src/components/__tests__/EdgeCaseTesting.enhanced.test.tsx +2 -1
- package/src/hooks/__tests__/useRBAC.unit.test.ts +32 -24
- package/src/providers/RBACProvider.tsx +14 -2
- package/src/providers/__tests__/UnifiedAuthProvider.unit.test.tsx +11 -3
- package/src/rbac/__tests__/cache-invalidation.test.ts +2 -2
- package/src/rbac/__tests__/cache.test.ts +3 -3
- package/src/rbac/hooks.ts +15 -7
- package/src/styles/core.css +83 -62
- package/src/utils/__tests__/lazyLoad.unit.test.tsx +13 -18
- package/src/utils/storage/__tests__/helpers.unit.test.ts +9 -7
- package/dist/cache-I72HKDOA.js +0 -12
- package/dist/cache-I72HKDOA.js.map +0 -1
- package/dist/chunk-ANE4PDC2.js.map +0 -1
- package/dist/chunk-DY5E3AT7.js.map +0 -1
- package/dist/chunk-MRRFJ6SA.js +0 -161
- package/dist/chunk-MRRFJ6SA.js.map +0 -1
- package/dist/chunk-UY7AM4QG.js.map +0 -1
- package/dist/chunk-WYB6MBZA.js.map +0 -1
- package/src/components/DataTable/__tests__/DataTable.autoSizing.test.tsx +0 -526
- package/src/components/DataTable/components/DataTableHeader.tsx +0 -31
- package/src/components/DataTable/components/__tests__/DataTableHeader.unit.test.tsx +0 -143
- package/src/components/DataTable/examples/AutoSizingExample.tsx +0 -180
- package/src/components/DataTable/examples/ColumnSizingComparison.tsx +0 -235
- package/src/components/DataTable/utils/__tests__/columnSizing.test.ts +0 -237
- package/src/components/DataTable/utils/columnSizing.ts +0 -125
- /package/dist/{DataTable-GEY5U7OI.js.map → DataTable-EEUDXPE5.js.map} +0 -0
- /package/dist/{api-T6CBS7IO.js.map → api-ETQ6YJ3C.js.map} +0 -0
- /package/dist/{chunk-TMRLB2LA.js.map → chunk-HEMJ4SUJ.js.map} +0 -0
- /package/dist/{chunk-O4T53L7X.js.map → chunk-HNDFPXUU.js.map} +0 -0
- /package/dist/{chunk-PFRRIDYA.js.map → chunk-TIVL4UQ7.js.map} +0 -0
- /package/dist/{chunk-2MKP6IYD.js.map → chunk-VYG4AXYW.js.map} +0 -0
|
@@ -30,12 +30,7 @@ const MemoizedCell = memo(({ cell, style }: { cell: any; style?: React.CSSProper
|
|
|
30
30
|
"px-4 py-2 text-sm border-b border-gray-200 overflow-hidden",
|
|
31
31
|
cell.column?.getCanSort && cell.column.getCanSort() && "cursor-pointer select-none"
|
|
32
32
|
)}
|
|
33
|
-
style={
|
|
34
|
-
...style,
|
|
35
|
-
width: cell.column?.getSize ? cell.column.getSize() : 150,
|
|
36
|
-
minWidth: cell.column?.columnDef?.minSize,
|
|
37
|
-
maxWidth: cell.column?.columnDef?.maxSize,
|
|
38
|
-
}}
|
|
33
|
+
style={style}
|
|
39
34
|
>
|
|
40
35
|
{flexRender(cell.column?.columnDef?.cell, cell.getContext?.() || {})}
|
|
41
36
|
</td>
|
|
@@ -47,10 +42,9 @@ MemoizedCell.displayName = 'MemoizedCell';
|
|
|
47
42
|
/**
|
|
48
43
|
* Memoized row component for performance
|
|
49
44
|
*/
|
|
50
|
-
const MemoizedRow = memo(({ row, style
|
|
45
|
+
const MemoizedRow = memo(({ row, style }: {
|
|
51
46
|
row: any;
|
|
52
47
|
style: React.CSSProperties;
|
|
53
|
-
columnSizes: Record<string, number>;
|
|
54
48
|
}) => {
|
|
55
49
|
return (
|
|
56
50
|
<tr
|
|
@@ -63,13 +57,10 @@ const MemoizedRow = memo(({ row, style, columnSizes }: {
|
|
|
63
57
|
data-testid={`data-table-row-${row.id}`}
|
|
64
58
|
>
|
|
65
59
|
{row.getVisibleCells && row.getVisibleCells().map((cell: any) => (
|
|
66
|
-
<MemoizedCell
|
|
60
|
+
<MemoizedCell
|
|
67
61
|
key={cell.id}
|
|
68
62
|
cell={cell}
|
|
69
|
-
style={{
|
|
70
|
-
width: columnSizes[cell.column?.id] || (cell.column?.getSize ? cell.column.getSize() : 150),
|
|
71
|
-
minWidth: columnSizes[cell.column?.id] || (cell.column?.getSize ? cell.column.getSize() : 150),
|
|
72
|
-
}}
|
|
63
|
+
style={{}}
|
|
73
64
|
/>
|
|
74
65
|
))}
|
|
75
66
|
</tr>
|
|
@@ -92,7 +83,6 @@ export function VirtualizedDataTable<TData extends DataRecord>({
|
|
|
92
83
|
const headerRef = useRef<HTMLTableElement>(null);
|
|
93
84
|
const bodyRef = useRef<HTMLTableElement>(null);
|
|
94
85
|
const rows = table.getRowModel().rows;
|
|
95
|
-
const [columnSizes, setColumnSizes] = useState<Record<string, number>>({});
|
|
96
86
|
|
|
97
87
|
// Virtual scrolling setup
|
|
98
88
|
const virtualizer = useVirtualizer({
|
|
@@ -107,31 +97,6 @@ export function VirtualizedDataTable<TData extends DataRecord>({
|
|
|
107
97
|
// Get table headers
|
|
108
98
|
const headerGroups = table.getHeaderGroups();
|
|
109
99
|
|
|
110
|
-
// Calculate column sizes and sync between header and body
|
|
111
|
-
useLayoutEffect(() => {
|
|
112
|
-
if (headerRef.current && bodyRef.current) {
|
|
113
|
-
const headerCells = headerRef.current.querySelectorAll('th');
|
|
114
|
-
const newColumnSizes: Record<string, number> = {};
|
|
115
|
-
|
|
116
|
-
headerGroups[0]?.headers.forEach((header, index) => {
|
|
117
|
-
const headerCell = headerCells[index];
|
|
118
|
-
if (headerCell) {
|
|
119
|
-
const computedWidth = headerCell.getBoundingClientRect().width;
|
|
120
|
-
const columnSize = header.column?.getSize ? header.column.getSize() : 150;
|
|
121
|
-
newColumnSizes[header.id] = Math.max(computedWidth, columnSize);
|
|
122
|
-
}
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
// Only update if sizes have actually changed
|
|
126
|
-
const hasChanged = Object.keys(newColumnSizes).some(
|
|
127
|
-
key => newColumnSizes[key] !== columnSizes[key]
|
|
128
|
-
) || Object.keys(columnSizes).length !== Object.keys(newColumnSizes).length;
|
|
129
|
-
|
|
130
|
-
if (hasChanged) {
|
|
131
|
-
setColumnSizes(newColumnSizes);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}, [headerGroups, rows.length, columnSizes]);
|
|
135
100
|
|
|
136
101
|
// Notify parent of visibility changes
|
|
137
102
|
const handleVisibilityChange = useCallback(() => {
|
|
@@ -167,11 +132,7 @@ export function VirtualizedDataTable<TData extends DataRecord>({
|
|
|
167
132
|
"px-4 py-3 text-left text-xs font-medium text-app-sec-500 uppercase tracking-wider",
|
|
168
133
|
header.column?.getCanSort && header.column.getCanSort() && "cursor-pointer select-none hover:bg-app-sec-100"
|
|
169
134
|
)}
|
|
170
|
-
style={{
|
|
171
|
-
width: header.getSize ? header.getSize() : 150,
|
|
172
|
-
minWidth: header.column?.columnDef?.minSize,
|
|
173
|
-
maxWidth: header.column?.columnDef?.maxSize,
|
|
174
|
-
}}
|
|
135
|
+
style={{}}
|
|
175
136
|
onClick={header.column?.getToggleSortingHandler ? header.column.getToggleSortingHandler() : undefined}
|
|
176
137
|
>
|
|
177
138
|
<div className="flex items-center space-x-1">
|
|
@@ -221,10 +182,7 @@ export function VirtualizedDataTable<TData extends DataRecord>({
|
|
|
221
182
|
"px-4 py-3 text-left text-xs font-medium text-app-sec-500 uppercase tracking-wider",
|
|
222
183
|
header.column?.getCanSort && header.column.getCanSort() && "cursor-pointer select-none hover:bg-app-sec-100"
|
|
223
184
|
)}
|
|
224
|
-
style={{
|
|
225
|
-
width: columnSizes[header.id] || (header.getSize ? header.getSize() : 150),
|
|
226
|
-
minWidth: columnSizes[header.id] || (header.getSize ? header.getSize() : 150),
|
|
227
|
-
}}
|
|
185
|
+
style={{}}
|
|
228
186
|
onClick={header.column?.getToggleSortingHandler ? header.column.getToggleSortingHandler() : undefined}
|
|
229
187
|
>
|
|
230
188
|
<div className="flex items-center space-x-1">
|
|
@@ -271,7 +229,6 @@ export function VirtualizedDataTable<TData extends DataRecord>({
|
|
|
271
229
|
<MemoizedRow
|
|
272
230
|
key={row.id}
|
|
273
231
|
row={row}
|
|
274
|
-
columnSizes={columnSizes}
|
|
275
232
|
style={{
|
|
276
233
|
position: 'absolute',
|
|
277
234
|
top: 0,
|
|
@@ -353,7 +310,6 @@ export function EnhancedVirtualizedDataTable<TData extends DataRecord>({
|
|
|
353
310
|
const headerRef = useRef<HTMLTableElement>(null);
|
|
354
311
|
const bodyRef = useRef<HTMLTableElement>(null);
|
|
355
312
|
const rows = table.getRowModel().rows;
|
|
356
|
-
const [columnSizes, setColumnSizes] = useState<Record<string, number>>({});
|
|
357
313
|
|
|
358
314
|
// Show loading skeleton if loading
|
|
359
315
|
const displayRows = useMemo(() => {
|
|
@@ -377,31 +333,6 @@ export function EnhancedVirtualizedDataTable<TData extends DataRecord>({
|
|
|
377
333
|
const headerGroups = table.getHeaderGroups();
|
|
378
334
|
const totalSize = virtualizer.getTotalSize();
|
|
379
335
|
|
|
380
|
-
// Calculate column sizes and sync between header and body
|
|
381
|
-
useLayoutEffect(() => {
|
|
382
|
-
if (headerRef.current && bodyRef.current && !isLoading) {
|
|
383
|
-
const headerCells = headerRef.current.querySelectorAll('th');
|
|
384
|
-
const newColumnSizes: Record<string, number> = {};
|
|
385
|
-
|
|
386
|
-
headerGroups[0]?.headers.forEach((header, index) => {
|
|
387
|
-
const headerCell = headerCells[index];
|
|
388
|
-
if (headerCell) {
|
|
389
|
-
const computedWidth = headerCell.getBoundingClientRect().width;
|
|
390
|
-
const columnSize = header.column?.getSize ? header.column.getSize() : 150;
|
|
391
|
-
newColumnSizes[header.id] = Math.max(computedWidth, columnSize);
|
|
392
|
-
}
|
|
393
|
-
});
|
|
394
|
-
|
|
395
|
-
// Only update if sizes have actually changed
|
|
396
|
-
const hasChanged = Object.keys(newColumnSizes).some(
|
|
397
|
-
key => newColumnSizes[key] !== columnSizes[key]
|
|
398
|
-
) || Object.keys(columnSizes).length !== Object.keys(newColumnSizes).length;
|
|
399
|
-
|
|
400
|
-
if (hasChanged) {
|
|
401
|
-
setColumnSizes(newColumnSizes);
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
}, [headerGroups, displayRows.length, isLoading, columnSizes]);
|
|
405
336
|
|
|
406
337
|
// Handle visibility changes
|
|
407
338
|
React.useEffect(() => {
|
|
@@ -419,10 +350,7 @@ export function EnhancedVirtualizedDataTable<TData extends DataRecord>({
|
|
|
419
350
|
<td
|
|
420
351
|
key={header.id}
|
|
421
352
|
className="px-4 py-2 border-b border-app-sec-200"
|
|
422
|
-
style={{
|
|
423
|
-
width: columnSizes[header.id] || (header.getSize ? header.getSize() : 150),
|
|
424
|
-
minWidth: columnSizes[header.id] || (header.getSize ? header.getSize() : 150),
|
|
425
|
-
}}
|
|
353
|
+
style={{}}
|
|
426
354
|
>
|
|
427
355
|
<div className="h-4 bg-app-sec-200 rounded"></div>
|
|
428
356
|
</td>
|
|
@@ -445,11 +373,7 @@ export function EnhancedVirtualizedDataTable<TData extends DataRecord>({
|
|
|
445
373
|
<th
|
|
446
374
|
key={header.id}
|
|
447
375
|
className="px-4 py-3 text-left text-xs font-medium text-app-sec-500 uppercase tracking-wider"
|
|
448
|
-
style={{
|
|
449
|
-
width: header.getSize ? header.getSize() : 150,
|
|
450
|
-
minWidth: header.column?.columnDef?.minSize,
|
|
451
|
-
maxWidth: header.column?.columnDef?.maxSize,
|
|
452
|
-
}}
|
|
376
|
+
style={{}}
|
|
453
377
|
>
|
|
454
378
|
{header.isPlaceholder
|
|
455
379
|
? null
|
|
@@ -486,10 +410,7 @@ export function EnhancedVirtualizedDataTable<TData extends DataRecord>({
|
|
|
486
410
|
"px-4 py-3 text-left text-xs font-medium text-app-sec-500 uppercase tracking-wider",
|
|
487
411
|
header.column?.getCanSort && header.column.getCanSort() && "cursor-pointer select-none hover:bg-app-sec-100"
|
|
488
412
|
)}
|
|
489
|
-
style={{
|
|
490
|
-
width: columnSizes[header.id] || (header.getSize ? header.getSize() : 150),
|
|
491
|
-
minWidth: columnSizes[header.id] || (header.getSize ? header.getSize() : 150),
|
|
492
|
-
}}
|
|
413
|
+
style={{}}
|
|
493
414
|
onClick={header.column?.getToggleSortingHandler ? header.column.getToggleSortingHandler() : undefined}
|
|
494
415
|
>
|
|
495
416
|
<div className="flex items-center space-x-1">
|
|
@@ -552,7 +473,6 @@ export function EnhancedVirtualizedDataTable<TData extends DataRecord>({
|
|
|
552
473
|
<MemoizedRow
|
|
553
474
|
key={row.id}
|
|
554
475
|
row={row}
|
|
555
|
-
columnSizes={columnSizes}
|
|
556
476
|
style={{
|
|
557
477
|
position: 'absolute',
|
|
558
478
|
top: 0,
|
|
@@ -29,7 +29,8 @@ vi.mock('../../DataTable', () => ({
|
|
|
29
29
|
actions = [],
|
|
30
30
|
toolbarButtons = [],
|
|
31
31
|
isLoading = false,
|
|
32
|
-
variant = 'default'
|
|
32
|
+
variant = 'default',
|
|
33
|
+
sortable = false
|
|
33
34
|
}: any) => {
|
|
34
35
|
if (isLoading) {
|
|
35
36
|
return (
|
|
@@ -107,7 +108,16 @@ vi.mock('../../DataTable', () => ({
|
|
|
107
108
|
scope="col"
|
|
108
109
|
aria-sort="none"
|
|
109
110
|
>
|
|
110
|
-
{col.
|
|
111
|
+
{sortable && col.sortable ? (
|
|
112
|
+
<button
|
|
113
|
+
aria-label={`Sort by ${col.label || col.header}`}
|
|
114
|
+
tabIndex={0}
|
|
115
|
+
>
|
|
116
|
+
{col.label || col.header}
|
|
117
|
+
</button>
|
|
118
|
+
) : (
|
|
119
|
+
col.label || col.header
|
|
120
|
+
)}
|
|
111
121
|
</th>
|
|
112
122
|
))}
|
|
113
123
|
{(enableEditing || enableDeletion || actions.length > 0) && (
|
|
@@ -212,9 +222,9 @@ const createTestData = (count: number) => {
|
|
|
212
222
|
};
|
|
213
223
|
|
|
214
224
|
const createTestColumns = () => [
|
|
215
|
-
{ key: 'name', label: 'Name' },
|
|
216
|
-
{ key: 'email', label: 'Email' },
|
|
217
|
-
{ key: 'role', label: 'Role' }
|
|
225
|
+
{ key: 'name', label: 'Name', sortable: true },
|
|
226
|
+
{ key: 'email', label: 'Email', sortable: true },
|
|
227
|
+
{ key: 'role', label: 'Role', sortable: false }
|
|
218
228
|
];
|
|
219
229
|
|
|
220
230
|
const createTestActions = () => [];
|
|
@@ -520,4 +530,100 @@ describe('DataTable Accessibility Tests', () => {
|
|
|
520
530
|
expect(table).toBeInTheDocument();
|
|
521
531
|
});
|
|
522
532
|
});
|
|
533
|
+
|
|
534
|
+
// ============================================================================
|
|
535
|
+
// SORTING ACCESSIBILITY TESTS
|
|
536
|
+
// ============================================================================
|
|
537
|
+
|
|
538
|
+
describe('Sorting Accessibility', () => {
|
|
539
|
+
it('provides proper ARIA labels for sortable columns', () => {
|
|
540
|
+
const sortableColumns = [
|
|
541
|
+
{ accessorKey: 'name', header: 'Name', sortable: true },
|
|
542
|
+
{ accessorKey: 'email', header: 'Email', sortable: true }
|
|
543
|
+
];
|
|
544
|
+
|
|
545
|
+
renderWithProviders(
|
|
546
|
+
<DataTable
|
|
547
|
+
columns={sortableColumns}
|
|
548
|
+
data={createTestData(2)}
|
|
549
|
+
sortable={true}
|
|
550
|
+
/>
|
|
551
|
+
);
|
|
552
|
+
|
|
553
|
+
// Check for proper ARIA labels on sort buttons
|
|
554
|
+
const nameSortButton = screen.getByRole('button', { name: /sort by name/i });
|
|
555
|
+
const emailSortButton = screen.getByRole('button', { name: /sort by email/i });
|
|
556
|
+
|
|
557
|
+
expect(nameSortButton).toHaveAttribute('aria-label', 'Sort by Name');
|
|
558
|
+
expect(emailSortButton).toHaveAttribute('aria-label', 'Sort by Email');
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
it('maintains keyboard navigation for sorting', () => {
|
|
562
|
+
const sortableColumns = [
|
|
563
|
+
{ accessorKey: 'name', header: 'Name', sortable: true }
|
|
564
|
+
];
|
|
565
|
+
|
|
566
|
+
renderWithProviders(
|
|
567
|
+
<DataTable
|
|
568
|
+
columns={sortableColumns}
|
|
569
|
+
data={createTestData(2)}
|
|
570
|
+
sortable={true}
|
|
571
|
+
/>
|
|
572
|
+
);
|
|
573
|
+
|
|
574
|
+
const sortButton = screen.getByRole('button', { name: /sort by name/i });
|
|
575
|
+
|
|
576
|
+
// Sort button should be keyboard accessible
|
|
577
|
+
expect(sortButton).toHaveAttribute('tabIndex', '0');
|
|
578
|
+
expect(sortButton).not.toHaveAttribute('aria-disabled', 'true');
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
it('provides proper ARIA sort attributes', () => {
|
|
582
|
+
const sortableColumns = [
|
|
583
|
+
{ accessorKey: 'name', header: 'Name', sortable: true }
|
|
584
|
+
];
|
|
585
|
+
|
|
586
|
+
renderWithProviders(
|
|
587
|
+
<DataTable
|
|
588
|
+
columns={sortableColumns}
|
|
589
|
+
data={createTestData(2)}
|
|
590
|
+
sortable={true}
|
|
591
|
+
/>
|
|
592
|
+
);
|
|
593
|
+
|
|
594
|
+
// Table headers should have proper ARIA sort attributes
|
|
595
|
+
const table = screen.getByRole('table');
|
|
596
|
+
const headers = table.querySelectorAll('th');
|
|
597
|
+
|
|
598
|
+
// At least one header should have aria-sort attribute
|
|
599
|
+
const hasAriaSort = Array.from(headers).some(header =>
|
|
600
|
+
header.hasAttribute('aria-sort')
|
|
601
|
+
);
|
|
602
|
+
expect(hasAriaSort).toBe(true);
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
it('distinguishes between sortable and non-sortable columns', () => {
|
|
606
|
+
const mixedColumns = [
|
|
607
|
+
{ accessorKey: 'name', header: 'Name', sortable: true },
|
|
608
|
+
{ accessorKey: 'role', header: 'Role', sortable: false }
|
|
609
|
+
];
|
|
610
|
+
|
|
611
|
+
renderWithProviders(
|
|
612
|
+
<DataTable
|
|
613
|
+
columns={mixedColumns}
|
|
614
|
+
data={createTestData(2)}
|
|
615
|
+
sortable={true}
|
|
616
|
+
/>
|
|
617
|
+
);
|
|
618
|
+
|
|
619
|
+
// Sortable column should have button
|
|
620
|
+
const nameHeader = screen.getByRole('button', { name: /sort by name/i });
|
|
621
|
+
expect(nameHeader).toBeInTheDocument();
|
|
622
|
+
|
|
623
|
+
// Non-sortable column should not have button
|
|
624
|
+
const roleHeader = screen.getByText('Role');
|
|
625
|
+
expect(roleHeader).toBeInTheDocument();
|
|
626
|
+
expect(roleHeader.closest('button')).toBeNull();
|
|
627
|
+
});
|
|
628
|
+
});
|
|
523
629
|
});
|
|
@@ -16,27 +16,104 @@ vi.mock('@tanstack/react-table', () => ({
|
|
|
16
16
|
getPaginationRowModel: () => (rows: any[]) => rows,
|
|
17
17
|
getGroupedRowModel: () => (rows: any[]) => rows,
|
|
18
18
|
getExpandedRowModel: () => (rows: any[]) => rows,
|
|
19
|
-
getAllColumns: () => [
|
|
20
|
-
|
|
19
|
+
getAllColumns: () => [
|
|
20
|
+
{
|
|
21
|
+
id: 'name',
|
|
22
|
+
columnDef: { header: 'Name' },
|
|
23
|
+
getCanSort: () => true,
|
|
24
|
+
getIsSorted: () => false,
|
|
25
|
+
getToggleSortingHandler: () => () => {},
|
|
26
|
+
getIsVisible: () => true,
|
|
27
|
+
getCanFilter: () => true
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: 'email',
|
|
31
|
+
columnDef: { header: 'Email' },
|
|
32
|
+
getCanSort: () => true,
|
|
33
|
+
getIsSorted: () => false,
|
|
34
|
+
getToggleSortingHandler: () => () => {},
|
|
35
|
+
getIsVisible: () => true,
|
|
36
|
+
getCanFilter: () => true
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: 'role',
|
|
40
|
+
columnDef: { header: 'Role' },
|
|
41
|
+
getCanSort: () => false,
|
|
42
|
+
getIsSorted: () => false,
|
|
43
|
+
getToggleSortingHandler: () => () => {},
|
|
44
|
+
getIsVisible: () => true,
|
|
45
|
+
getCanFilter: () => true
|
|
46
|
+
}
|
|
47
|
+
],
|
|
48
|
+
getVisibleFlatColumns: () => [
|
|
49
|
+
{
|
|
50
|
+
id: 'name',
|
|
51
|
+
columnDef: { header: 'Name' },
|
|
52
|
+
getCanSort: () => true,
|
|
53
|
+
getIsSorted: () => false,
|
|
54
|
+
getToggleSortingHandler: () => () => {},
|
|
55
|
+
getIsVisible: () => true,
|
|
56
|
+
getCanFilter: () => true
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: 'email',
|
|
60
|
+
columnDef: { header: 'Email' },
|
|
61
|
+
getCanSort: () => true,
|
|
62
|
+
getIsSorted: () => false,
|
|
63
|
+
getToggleSortingHandler: () => () => {},
|
|
64
|
+
getIsVisible: () => true,
|
|
65
|
+
getCanFilter: () => true
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
id: 'role',
|
|
69
|
+
columnDef: { header: 'Role' },
|
|
70
|
+
getCanSort: () => false,
|
|
71
|
+
getIsSorted: () => false,
|
|
72
|
+
getToggleSortingHandler: () => () => {},
|
|
73
|
+
getIsVisible: () => true,
|
|
74
|
+
getCanFilter: () => true
|
|
75
|
+
}
|
|
76
|
+
],
|
|
21
77
|
getHeaderGroups: () => [{
|
|
22
78
|
id: 'headerGroup',
|
|
23
79
|
headers: [
|
|
24
80
|
{
|
|
25
81
|
id: 'name',
|
|
26
82
|
isPlaceholder: false,
|
|
27
|
-
column: {
|
|
83
|
+
column: {
|
|
84
|
+
columnDef: { header: 'Name' },
|
|
85
|
+
getCanSort: () => true,
|
|
86
|
+
getIsSorted: () => false,
|
|
87
|
+
getToggleSortingHandler: () => () => {},
|
|
88
|
+
getIsVisible: () => true,
|
|
89
|
+
getCanFilter: () => true
|
|
90
|
+
},
|
|
28
91
|
getContext: () => ({}),
|
|
29
92
|
},
|
|
30
93
|
{
|
|
31
94
|
id: 'email',
|
|
32
95
|
isPlaceholder: false,
|
|
33
|
-
column: {
|
|
96
|
+
column: {
|
|
97
|
+
columnDef: { header: 'Email' },
|
|
98
|
+
getCanSort: () => true,
|
|
99
|
+
getIsSorted: () => false,
|
|
100
|
+
getToggleSortingHandler: () => () => {},
|
|
101
|
+
getIsVisible: () => true,
|
|
102
|
+
getCanFilter: () => true
|
|
103
|
+
},
|
|
34
104
|
getContext: () => ({}),
|
|
35
105
|
},
|
|
36
106
|
{
|
|
37
107
|
id: 'role',
|
|
38
108
|
isPlaceholder: false,
|
|
39
|
-
column: {
|
|
109
|
+
column: {
|
|
110
|
+
columnDef: { header: 'Role' },
|
|
111
|
+
getCanSort: () => false,
|
|
112
|
+
getIsSorted: () => false,
|
|
113
|
+
getToggleSortingHandler: () => () => {},
|
|
114
|
+
getIsVisible: () => true,
|
|
115
|
+
getCanFilter: () => true
|
|
116
|
+
},
|
|
40
117
|
getContext: () => ({}),
|
|
41
118
|
},
|
|
42
119
|
],
|
|
@@ -155,14 +232,6 @@ vi.mock('../../core/DataTableContext', () => ({
|
|
|
155
232
|
}));
|
|
156
233
|
|
|
157
234
|
// Mock individual components
|
|
158
|
-
vi.mock('../DataTableHeader', () => ({
|
|
159
|
-
DataTableHeader: ({ title, description }: any) => (
|
|
160
|
-
<div data-testid="data-table-header">
|
|
161
|
-
{title && <h2 data-testid="table-title">{title}</h2>}
|
|
162
|
-
{description && <p data-testid="table-description">{description}</p>}
|
|
163
|
-
</div>
|
|
164
|
-
),
|
|
165
|
-
}));
|
|
166
235
|
|
|
167
236
|
vi.mock('../DataTableToolbar', () => ({
|
|
168
237
|
DataTableToolbar: () => <div data-testid="data-table-toolbar">Toolbar</div>,
|
|
@@ -102,7 +102,6 @@ vi.mock('../../core/DataTableContext', () => ({
|
|
|
102
102
|
}));
|
|
103
103
|
|
|
104
104
|
// Mock components
|
|
105
|
-
vi.mock('../DataTableHeader', () => ({ DataTableHeader: () => <div data-testid="header">Header</div> }));
|
|
106
105
|
vi.mock('../DataTableToolbar', () => ({ DataTableToolbar: () => <div data-testid="toolbar">Toolbar</div> }));
|
|
107
106
|
vi.mock('../DataTableBody', () => ({ DataTableBody: () => <div data-testid="body">Body</div> }));
|
|
108
107
|
vi.mock('../DataTableModals', () => ({ DataTableModals: () => <div data-testid="modals">Modals</div> }));
|
|
@@ -102,7 +102,6 @@ vi.mock('../../core/DataTableContext', () => ({
|
|
|
102
102
|
}));
|
|
103
103
|
|
|
104
104
|
// Mock components
|
|
105
|
-
vi.mock('../DataTableHeader', () => ({ DataTableHeader: () => <div data-testid="header">Header</div> }));
|
|
106
105
|
vi.mock('../DataTableToolbar', () => ({ DataTableToolbar: () => <div data-testid="toolbar">Toolbar</div> }));
|
|
107
106
|
vi.mock('../DataTableBody', () => ({
|
|
108
107
|
DataTableBody: ({ table }: any) => {
|
|
@@ -173,7 +172,8 @@ describe('DataTable Real Component Tests', () => {
|
|
|
173
172
|
features={createDefaultFeatures()}
|
|
174
173
|
/>
|
|
175
174
|
);
|
|
176
|
-
expect(screen.
|
|
175
|
+
expect(screen.getByText('Test Table')).toBeInTheDocument();
|
|
176
|
+
expect(screen.getByText('Test Description')).toBeInTheDocument();
|
|
177
177
|
});
|
|
178
178
|
|
|
179
179
|
it('renders with search enabled', () => {
|
|
@@ -102,7 +102,6 @@ vi.mock('../../core/DataTableContext', () => ({
|
|
|
102
102
|
}));
|
|
103
103
|
|
|
104
104
|
// Mock components
|
|
105
|
-
vi.mock('../DataTableHeader', () => ({ DataTableHeader: () => <div data-testid="header">Header</div> }));
|
|
106
105
|
vi.mock('../DataTableToolbar', () => ({ DataTableToolbar: () => <div data-testid="toolbar">Toolbar</div> }));
|
|
107
106
|
vi.mock('../DataTableBody', () => ({ DataTableBody: () => <div data-testid="body">Body</div> }));
|
|
108
107
|
vi.mock('../DataTableModals', () => ({ DataTableModals: () => <div data-testid="modals">Modals</div> }));
|
|
@@ -102,7 +102,6 @@ vi.mock('../../core/DataTableContext', () => ({
|
|
|
102
102
|
}));
|
|
103
103
|
|
|
104
104
|
// Mock components
|
|
105
|
-
vi.mock('../DataTableHeader', () => ({ DataTableHeader: () => <div data-testid="header">Header</div> }));
|
|
106
105
|
vi.mock('../DataTableToolbar', () => ({ DataTableToolbar: () => <div data-testid="toolbar">Toolbar</div> }));
|
|
107
106
|
vi.mock('../DataTableBody', () => ({
|
|
108
107
|
DataTableBody: ({ table }: any) => {
|
|
@@ -173,7 +172,8 @@ describe('DataTable Component', () => {
|
|
|
173
172
|
features={createDefaultFeatures()}
|
|
174
173
|
/>
|
|
175
174
|
);
|
|
176
|
-
expect(screen.
|
|
175
|
+
expect(screen.getByText('Test Table')).toBeInTheDocument();
|
|
176
|
+
expect(screen.getByText('This is a test table')).toBeInTheDocument();
|
|
177
177
|
});
|
|
178
178
|
|
|
179
179
|
it('renders with search enabled', () => {
|
|
@@ -43,16 +43,19 @@ const columns = [
|
|
|
43
43
|
accessorKey: 'name',
|
|
44
44
|
header: 'Name',
|
|
45
45
|
enableColumnFilter: true,
|
|
46
|
+
enableSorting: true,
|
|
46
47
|
},
|
|
47
48
|
{
|
|
48
49
|
accessorKey: 'age',
|
|
49
50
|
header: 'Age',
|
|
50
51
|
enableColumnFilter: true,
|
|
52
|
+
enableSorting: true,
|
|
51
53
|
},
|
|
52
54
|
{
|
|
53
55
|
accessorKey: 'status',
|
|
54
56
|
header: 'Status',
|
|
55
57
|
enableColumnFilter: true,
|
|
58
|
+
enableSorting: true,
|
|
56
59
|
},
|
|
57
60
|
];
|
|
58
61
|
|
|
@@ -11,7 +11,6 @@ export { GroupHeader } from './GroupHeader';
|
|
|
11
11
|
export { GroupingDropdown } from './GroupingDropdown';
|
|
12
12
|
export { DataTableErrorBoundary } from './DataTableErrorBoundary';
|
|
13
13
|
export { EditableRow } from './EditableRow';
|
|
14
|
-
export { DataTableHeader } from './DataTableHeader';
|
|
15
14
|
export { PaginationControls } from './PaginationControls';
|
|
16
15
|
export { LoadingState } from './LoadingState';
|
|
17
16
|
export { EmptyState } from './EmptyState';
|
|
@@ -11,7 +11,6 @@ import { ChevronDown, ChevronUp, ChevronsUpDown, Filter, MoreHorizontal, Plus, S
|
|
|
11
11
|
import { Button } from '../../Button/Button';
|
|
12
12
|
import { Input } from '../../Input/Input';
|
|
13
13
|
import { Checkbox } from '../../Checkbox/Checkbox';
|
|
14
|
-
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '../../Table/Table';
|
|
15
14
|
// DropdownMenu components have been merged into Select components
|
|
16
15
|
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from '../../Dialog/Dialog';
|
|
17
16
|
import type { DataTableContext as IDataTableContext, DataTableConfig, DataTableUtils } from './interfaces';
|
|
@@ -119,8 +119,8 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
|
119
119
|
// Size styles
|
|
120
120
|
{
|
|
121
121
|
'h-8 px-2 py-1 text-xs': size === 'sm',
|
|
122
|
-
'h-
|
|
123
|
-
'h-
|
|
122
|
+
'h-9 px-3 py-2 text-sm': size === 'md',
|
|
123
|
+
'h-10 px-4 py-3 text-base': size === 'lg',
|
|
124
124
|
},
|
|
125
125
|
|
|
126
126
|
className
|
|
@@ -91,19 +91,19 @@ describe('Input Component', () => {
|
|
|
91
91
|
it('applies medium size styling (default)', () => {
|
|
92
92
|
renderWithProviders(<Input size="md" />);
|
|
93
93
|
const input = screen.getByRole('textbox');
|
|
94
|
-
expect(input).toHaveClass('h-
|
|
94
|
+
expect(input).toHaveClass('h-9', 'px-3', 'py-2', 'text-sm');
|
|
95
95
|
});
|
|
96
96
|
|
|
97
97
|
it('applies large size styling', () => {
|
|
98
98
|
renderWithProviders(<Input size="lg" />);
|
|
99
99
|
const input = screen.getByRole('textbox');
|
|
100
|
-
expect(input).toHaveClass('h-
|
|
100
|
+
expect(input).toHaveClass('h-10', 'px-4', 'py-3', 'text-base');
|
|
101
101
|
});
|
|
102
102
|
|
|
103
103
|
it('defaults to medium size when no size is specified', () => {
|
|
104
104
|
renderWithProviders(<Input />);
|
|
105
105
|
const input = screen.getByRole('textbox');
|
|
106
|
-
expect(input).toHaveClass('h-
|
|
106
|
+
expect(input).toHaveClass('h-9', 'px-3', 'py-2', 'text-sm');
|
|
107
107
|
});
|
|
108
108
|
});
|
|
109
109
|
|
|
@@ -113,7 +113,7 @@ describe('Input Component', () => {
|
|
|
113
113
|
const input = screen.getByRole('textbox');
|
|
114
114
|
expect(input).toHaveClass(
|
|
115
115
|
'flex',
|
|
116
|
-
'h-
|
|
116
|
+
'h-9',
|
|
117
117
|
'w-full',
|
|
118
118
|
'rounded-md',
|
|
119
119
|
'border',
|
|
@@ -100,7 +100,7 @@ vi.mock('../../../rbac/api', () => ({
|
|
|
100
100
|
getPermissionMap: vi.fn().mockResolvedValue({}),
|
|
101
101
|
getAccessLevel: vi.fn().mockResolvedValue('viewer'),
|
|
102
102
|
isSuperAdmin: vi.fn().mockResolvedValue(false),
|
|
103
|
-
setupRBAC: vi.fn()
|
|
103
|
+
setupRBAC: vi.fn().mockResolvedValue(undefined)
|
|
104
104
|
}));
|
|
105
105
|
|
|
106
106
|
// Mock child components with more realistic behavior
|
|
@@ -224,10 +224,14 @@ describe('PaceAppLayout Integration', () => {
|
|
|
224
224
|
mockUpdatePassword.mockResolvedValue({ error: null });
|
|
225
225
|
|
|
226
226
|
// Get the mocked functions
|
|
227
|
-
const { isPermitted, isSuperAdmin } = await import('../../../rbac/api');
|
|
227
|
+
const { isPermitted, isSuperAdmin, setupRBAC } = await import('../../../rbac/api');
|
|
228
228
|
mockIsPermitted = vi.mocked(isPermitted);
|
|
229
229
|
const mockIsSuperAdmin = vi.mocked(isSuperAdmin);
|
|
230
230
|
|
|
231
|
+
// Setup RBAC system
|
|
232
|
+
vi.mocked(setupRBAC).mockResolvedValue(undefined);
|
|
233
|
+
await setupRBAC({} as any); // Call setupRBAC to initialize the system
|
|
234
|
+
|
|
231
235
|
// Set up isSuperAdmin mock
|
|
232
236
|
mockIsSuperAdmin.mockResolvedValue(false);
|
|
233
237
|
|