@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.
Files changed (167) hide show
  1. package/dist/{DataTable-BHlzyKZP.d.ts → DataTable-C1AEm9Cx.d.ts} +1 -1
  2. package/dist/{DataTable-GEY5U7OI.js → DataTable-EEUDXPE5.js} +2 -8
  3. package/dist/{api-T6CBS7IO.js → api-ETQ6YJ3C.js} +2 -3
  4. package/dist/{chunk-DY5E3AT7.js → chunk-BEZRLNK3.js} +13 -3
  5. package/dist/chunk-BEZRLNK3.js.map +1 -0
  6. package/dist/{chunk-ANE4PDC2.js → chunk-C5G2A4PO.js} +159 -6
  7. package/dist/chunk-C5G2A4PO.js.map +1 -0
  8. package/dist/{chunk-WYB6MBZA.js → chunk-EWKPTNPO.js} +579 -973
  9. package/dist/chunk-EWKPTNPO.js.map +1 -0
  10. package/dist/{chunk-TMRLB2LA.js → chunk-HEMJ4SUJ.js} +2 -2
  11. package/dist/{chunk-O4T53L7X.js → chunk-HNDFPXUU.js} +5 -5
  12. package/dist/{chunk-UY7AM4QG.js → chunk-RRUYHORU.js} +161 -74
  13. package/dist/chunk-RRUYHORU.js.map +1 -0
  14. package/dist/{chunk-PFRRIDYA.js → chunk-TIVL4UQ7.js} +2 -2
  15. package/dist/{chunk-2MKP6IYD.js → chunk-VYG4AXYW.js} +2 -2
  16. package/dist/components.d.ts +2 -2
  17. package/dist/components.js +15 -16
  18. package/dist/components.js.map +1 -1
  19. package/dist/hooks.d.ts +1 -1
  20. package/dist/hooks.js +4 -4
  21. package/dist/index.d.ts +2 -2
  22. package/dist/index.js +16 -17
  23. package/dist/index.js.map +1 -1
  24. package/dist/providers.js +2 -2
  25. package/dist/rbac/index.js +25 -20
  26. package/dist/rbac/index.js.map +1 -1
  27. package/dist/styles/core.css +83 -62
  28. package/dist/{types-CInEi-ng.d.ts → types-DiRQsGJs.d.ts} +0 -2
  29. package/dist/utils.d.ts +2 -2
  30. package/dist/utils.js +1 -1
  31. package/docs/api/classes/ErrorBoundary.md +1 -1
  32. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  33. package/docs/api/interfaces/AggregateConfig.md +1 -1
  34. package/docs/api/interfaces/ButtonProps.md +1 -1
  35. package/docs/api/interfaces/CardProps.md +1 -1
  36. package/docs/api/interfaces/ColorPalette.md +1 -1
  37. package/docs/api/interfaces/ColorShade.md +1 -1
  38. package/docs/api/interfaces/DataTableAction.md +1 -1
  39. package/docs/api/interfaces/DataTableColumn.md +1 -1
  40. package/docs/api/interfaces/DataTableProps.md +33 -33
  41. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  42. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  43. package/docs/api/interfaces/EventContextType.md +1 -1
  44. package/docs/api/interfaces/EventLogoProps.md +1 -1
  45. package/docs/api/interfaces/EventProviderProps.md +1 -1
  46. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  47. package/docs/api/interfaces/FileUploadProps.md +1 -1
  48. package/docs/api/interfaces/FooterProps.md +1 -1
  49. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  50. package/docs/api/interfaces/InputProps.md +1 -1
  51. package/docs/api/interfaces/LabelProps.md +1 -1
  52. package/docs/api/interfaces/LoginFormProps.md +1 -1
  53. package/docs/api/interfaces/NavigationItem.md +1 -1
  54. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  55. package/docs/api/interfaces/Organisation.md +1 -1
  56. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  57. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  58. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  59. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  60. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  61. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  62. package/docs/api/interfaces/PaletteData.md +1 -1
  63. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  64. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  65. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  66. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  67. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  68. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  69. package/docs/api/interfaces/StorageConfig.md +1 -1
  70. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  71. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  72. package/docs/api/interfaces/StorageListOptions.md +1 -1
  73. package/docs/api/interfaces/StorageListResult.md +1 -1
  74. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  75. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  76. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  77. package/docs/api/interfaces/StyleImport.md +1 -1
  78. package/docs/api/interfaces/ToastActionElement.md +1 -1
  79. package/docs/api/interfaces/ToastProps.md +1 -1
  80. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  81. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  82. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  83. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  84. package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
  85. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  86. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  87. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  88. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  89. package/docs/api/interfaces/UserEventAccess.md +1 -1
  90. package/docs/api/interfaces/UserMenuProps.md +1 -1
  91. package/docs/api/interfaces/UserProfile.md +1 -1
  92. package/docs/api/modules.md +10 -10
  93. package/docs/architecture/README.md +1 -1
  94. package/package.json +1 -1
  95. package/src/__tests__/shared/testUtils.optimized.tsx +65 -7
  96. package/src/components/DataTable/DataTable.tsx +1 -3
  97. package/src/components/DataTable/__tests__/DataTable.errorHandling.test.tsx +0 -8
  98. package/src/components/DataTable/__tests__/DataTable.hierarchical.test.tsx +17 -12
  99. package/src/components/DataTable/__tests__/DataTable.infinite-loop.test.tsx +0 -1
  100. package/src/components/DataTable/__tests__/DataTable.integration.test.tsx +4 -12
  101. package/src/components/DataTable/__tests__/DataTable.performance.test.tsx +0 -8
  102. package/src/components/DataTable/__tests__/DataTable.permissions.test.tsx +21 -11
  103. package/src/components/DataTable/__tests__/DataTable.sorting.test.tsx +321 -0
  104. package/src/components/DataTable/__tests__/DataTable.userWorkflows.test.tsx +21 -11
  105. package/src/components/DataTable/__tests__/DataTable.workflowValidation.test.tsx +94 -0
  106. package/src/components/DataTable/__tests__/DataTable.workflows.test.tsx +25 -15
  107. package/src/components/DataTable/__tests__/README.md +11 -2
  108. package/src/components/DataTable/__tests__/performance-regression.test.tsx +0 -11
  109. package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +0 -1
  110. package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +2 -2
  111. package/src/components/DataTable/components/DataTableBody.tsx +34 -35
  112. package/src/components/DataTable/components/DataTableCore.tsx +205 -133
  113. package/src/components/DataTable/components/DataTableToolbar.tsx +9 -10
  114. package/src/components/DataTable/components/DraggableColumnHeader.tsx +3 -7
  115. package/src/components/DataTable/components/EditableRow.tsx +6 -7
  116. package/src/components/DataTable/components/FilterRow.tsx +0 -1
  117. package/src/components/DataTable/components/GroupingDropdown.tsx +2 -2
  118. package/src/components/DataTable/components/UnifiedTableBody.tsx +83 -281
  119. package/src/components/DataTable/components/VirtualizedDataTable.tsx +9 -89
  120. package/src/components/DataTable/components/__tests__/DataTable.accessibility.test.tsx +111 -5
  121. package/src/components/DataTable/components/__tests__/DataTable.integration.test.tsx +82 -13
  122. package/src/components/DataTable/components/__tests__/DataTable.performance.test.tsx +0 -1
  123. package/src/components/DataTable/components/__tests__/DataTable.real.test.tsx +2 -2
  124. package/src/components/DataTable/components/__tests__/DataTable.security.test.tsx +0 -1
  125. package/src/components/DataTable/components/__tests__/DataTable.unit.test.tsx +2 -2
  126. package/src/components/DataTable/components/__tests__/FilteringToggle.unit.test.tsx +3 -0
  127. package/src/components/DataTable/components/index.ts +0 -1
  128. package/src/components/DataTable/core/DataTableContext.tsx +0 -1
  129. package/src/components/DataTable/index.ts +0 -2
  130. package/src/components/DataTable/types.ts +0 -2
  131. package/src/components/Input/Input.tsx +2 -2
  132. package/src/components/Input/__tests__/Input.unit.test.tsx +4 -4
  133. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.integration.test.tsx +6 -2
  134. package/src/components/RBAC/PagePermissionGuard.tsx +13 -0
  135. package/src/components/RBAC/__tests__/PagePermissionGuard.unit.test.tsx +10 -1
  136. package/src/components/Select/Select.tsx +7 -1
  137. package/src/components/__tests__/EdgeCaseTesting.enhanced.test.tsx +2 -1
  138. package/src/hooks/__tests__/useRBAC.unit.test.ts +32 -24
  139. package/src/providers/RBACProvider.tsx +14 -2
  140. package/src/providers/__tests__/UnifiedAuthProvider.unit.test.tsx +11 -3
  141. package/src/rbac/__tests__/cache-invalidation.test.ts +2 -2
  142. package/src/rbac/__tests__/cache.test.ts +3 -3
  143. package/src/rbac/hooks.ts +15 -7
  144. package/src/styles/core.css +83 -62
  145. package/src/utils/__tests__/lazyLoad.unit.test.tsx +13 -18
  146. package/src/utils/storage/__tests__/helpers.unit.test.ts +9 -7
  147. package/dist/cache-I72HKDOA.js +0 -12
  148. package/dist/cache-I72HKDOA.js.map +0 -1
  149. package/dist/chunk-ANE4PDC2.js.map +0 -1
  150. package/dist/chunk-DY5E3AT7.js.map +0 -1
  151. package/dist/chunk-MRRFJ6SA.js +0 -161
  152. package/dist/chunk-MRRFJ6SA.js.map +0 -1
  153. package/dist/chunk-UY7AM4QG.js.map +0 -1
  154. package/dist/chunk-WYB6MBZA.js.map +0 -1
  155. package/src/components/DataTable/__tests__/DataTable.autoSizing.test.tsx +0 -526
  156. package/src/components/DataTable/components/DataTableHeader.tsx +0 -31
  157. package/src/components/DataTable/components/__tests__/DataTableHeader.unit.test.tsx +0 -143
  158. package/src/components/DataTable/examples/AutoSizingExample.tsx +0 -180
  159. package/src/components/DataTable/examples/ColumnSizingComparison.tsx +0 -235
  160. package/src/components/DataTable/utils/__tests__/columnSizing.test.ts +0 -237
  161. package/src/components/DataTable/utils/columnSizing.ts +0 -125
  162. /package/dist/{DataTable-GEY5U7OI.js.map → DataTable-EEUDXPE5.js.map} +0 -0
  163. /package/dist/{api-T6CBS7IO.js.map → api-ETQ6YJ3C.js.map} +0 -0
  164. /package/dist/{chunk-TMRLB2LA.js.map → chunk-HEMJ4SUJ.js.map} +0 -0
  165. /package/dist/{chunk-O4T53L7X.js.map → chunk-HNDFPXUU.js.map} +0 -0
  166. /package/dist/{chunk-PFRRIDYA.js.map → chunk-TIVL4UQ7.js.map} +0 -0
  167. /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, columnSizes }: {
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.label || col.header}
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
- getVisibleFlatColumns: () => [],
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: { columnDef: { header: 'Name' } },
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: { columnDef: { header: 'Email' } },
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: { columnDef: { header: 'Role' } },
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.getByTestId('header')).toBeInTheDocument();
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.getByTestId('header')).toBeInTheDocument();
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';
@@ -66,5 +66,3 @@ export * from './components';
66
66
  // Utility functions
67
67
  export * from './utils';
68
68
 
69
- // Column sizing utilities
70
- export { calculateColumnWidths, getColumnSizingConfig } from './utils/columnSizing';
@@ -389,8 +389,6 @@ export interface DataTableFeatureConfig {
389
389
  // Virtualization is now handled automatically based on data size
390
390
 
391
391
  // Layout
392
- /** Enable automatic column width adjustment based on content */
393
- autoColumnSizing?: boolean;
394
392
  }
395
393
 
396
394
 
@@ -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-10 px-3 py-2 text-sm': size === 'md',
123
- 'h-12 px-4 py-3 text-base': size === 'lg',
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-10', 'px-3', 'py-2', 'text-sm');
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-12', 'px-4', 'py-3', 'text-base');
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-10', 'px-3', 'py-2', 'text-sm');
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-10',
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