@jmruthers/pace-core 0.6.7 → 0.6.9
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/CHANGELOG.md +3 -0
- package/audit-tool/00-dependencies.cjs +215 -9
- package/audit-tool/audits/02-project-structure.cjs +41 -53
- package/audit-tool/audits/03-architecture.cjs +34 -6
- package/audit-tool/audits/06-security-rbac.cjs +10 -0
- package/audit-tool/audits/07-api-tech-stack.cjs +55 -1
- package/audit-tool/index.cjs +23 -19
- package/audit-tool/utils/report-utils.cjs +141 -2
- package/dist/{DataTable-7PMH7XN7.js → DataTable-SOAFXIWY.js} +5 -5
- package/dist/{PublicPageProvider-DlsCaR5v.d.ts → PublicPageProvider-CIGSujI2.d.ts} +14 -8
- package/dist/{UnifiedAuthProvider-ZT6TIGM7.js → UnifiedAuthProvider-7SNDOWYD.js} +2 -2
- package/dist/{api-Y4MQWOFW.js → api-7P7DI652.js} +1 -1
- package/dist/{chunk-L4XMVJKY.js → chunk-4DDCYDQ3.js} +8 -7
- package/dist/{chunk-JGWDVX64.js → chunk-5HNSDQWH.js} +125 -55
- package/dist/{chunk-ZKAWKYT4.js → chunk-5W2A3DRC.js} +2 -1
- package/dist/{chunk-IUBRCBSY.js → chunk-C7ZQ5O4C.js} +11 -5
- package/dist/{chunk-VBCS3DUA.js → chunk-EF2UGZWY.js} +3 -3
- package/dist/{chunk-BM4CQ5P3.js → chunk-GS5672WG.js} +6 -6
- package/dist/{chunk-Q7Q7V5NV.js → chunk-J2U36LHD.js} +72 -9
- package/dist/{chunk-ZFYPMX46.js → chunk-LX6U42O3.js} +1 -1
- package/dist/{chunk-5X4QLXRG.js → chunk-MPBLMWVR.js} +5 -3
- package/dist/{chunk-6F3IILHI.js → chunk-S6ZQKDY6.js} +1 -1
- package/dist/{chunk-FTCRZOG2.js → chunk-T5CVK4R3.js} +5 -5
- package/dist/{chunk-GHYHJTYV.js → chunk-Z2FNRKF3.js} +13 -13
- package/dist/components.d.ts +1 -1
- package/dist/components.js +12 -12
- package/dist/{database.generated-CcnC_DRc.d.ts → database.generated-DT8JTZiP.d.ts} +12 -12
- package/dist/eslint-rules/rules/04-code-quality.cjs +66 -10
- package/dist/eslint-rules/rules/06-security-rbac.cjs +8 -3
- package/dist/eslint-rules/rules/07-api-tech-stack.cjs +190 -68
- package/dist/{functions-DHebl8-F.d.ts → functions-lBy5L2ry.d.ts} +1 -1
- package/dist/hooks.d.ts +3 -3
- package/dist/hooks.js +7 -7
- package/dist/index.d.ts +6 -6
- package/dist/index.js +16 -16
- package/dist/providers.js +2 -2
- package/dist/rbac/index.d.ts +2 -2
- package/dist/rbac/index.js +6 -6
- package/dist/theming/runtime.d.ts +48 -1
- package/dist/theming/runtime.js +1 -1
- package/dist/{timezone-BZe_eUxx.d.ts → timezone-0AyangqX.d.ts} +1 -1
- package/dist/types.d.ts +3 -3
- package/dist/{usePublicRouteParams-MamNgwqe.d.ts → usePublicRouteParams-DQLrDqDb.d.ts} +1 -1
- package/dist/utils.d.ts +3 -3
- package/dist/utils.js +3 -3
- package/docs/api/modules.md +64 -15
- package/docs/api-reference/rpc-functions.md +3 -3
- package/docs/getting-started/dependencies.md +23 -0
- package/docs/implementation-guides/app-layout.md +1 -1
- package/docs/implementation-guides/data-tables.md +67 -1
- package/docs/standards/1-pace-core-compliance-standards.md +38 -1
- package/eslint-config-pace-core.cjs +30 -11
- package/package.json +45 -15
- package/scripts/eslint-audit.cjs +123 -0
- package/scripts/install-eslint-config.cjs +67 -2
- package/scripts/validate-dependencies.cjs +248 -0
- package/src/__tests__/helpers/__tests__/test-utils.test.tsx +20 -8
- package/src/__tests__/templates/accessibility.test.template.tsx +1 -0
- package/src/components/AddressField/AddressField.tsx +26 -1
- package/src/components/Alert/Alert.test.tsx +86 -22
- package/src/components/Alert/Alert.tsx +19 -11
- package/src/components/Badge/Badge.tsx +1 -1
- package/src/components/Checkbox/Checkbox.test.tsx +2 -1
- package/src/components/ContextSelector/ContextSelector.tsx +39 -41
- package/src/components/DataTable/DataTable.tsx +1 -19
- package/src/components/DataTable/__tests__/DataTable.select-label-display.test.tsx +483 -0
- package/src/components/DataTable/__tests__/DataTableCore.test.tsx +6 -10
- package/src/components/DataTable/__tests__/a11y.basic.test.tsx +18 -9
- package/src/components/DataTable/__tests__/pagination.modes.test.tsx +3 -2
- package/src/components/DataTable/components/EmptyState.tsx +1 -1
- package/src/components/DataTable/components/__tests__/DataTableErrorBoundary.test.tsx +1 -1
- package/src/components/DataTable/components/__tests__/EmptyState.test.tsx +3 -3
- package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +33 -29
- package/src/components/DataTable/hooks/__tests__/useTableColumns.test.ts +224 -0
- package/src/components/DataTable/hooks/useTableColumns.ts +23 -1
- package/src/components/DataTable/utils/__tests__/selectFieldUtils.test.ts +207 -0
- package/src/components/DataTable/utils/index.ts +1 -0
- package/src/components/DataTable/utils/selectFieldUtils.ts +134 -0
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +1 -2
- package/src/components/FileUpload/FileUpload.test.tsx +22 -31
- package/src/components/FileUpload/FileUpload.tsx +29 -0
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +48 -12
- package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +9 -9
- package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +30 -30
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +4 -4
- package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +7 -1
- package/src/components/UserMenu/UserMenu.tsx +3 -5
- package/src/hooks/__tests__/useDataTablePerformance.unit.test.ts +8 -5
- package/src/hooks/__tests__/useFileUrl.unit.test.ts +4 -0
- package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +3 -3
- package/src/hooks/__tests__/useInactivityTracker.unit.test.ts +45 -8
- package/src/hooks/__tests__/usePerformanceMonitor.unit.test.ts +22 -2
- package/src/hooks/public/usePublicRouteParams.ts +8 -4
- package/src/hooks/useAddressAutocomplete.test.ts +18 -18
- package/src/hooks/useEventTheme.ts +5 -1
- package/src/hooks/useFileUrl.ts +52 -8
- package/src/hooks/useOrganisationSecurity.test.ts +2 -1
- package/src/providers/__tests__/ProviderLifecycle.test.tsx +1 -1
- package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +15 -6
- package/src/rbac/__tests__/rbac-functions.test.ts +3 -3
- package/src/rbac/api.test.ts +104 -0
- package/src/rbac/engine.ts +1 -1
- package/src/rbac/hooks/useCan.test.ts +2 -2
- package/src/rbac/secureClient.ts +1 -1
- package/src/rbac/types/functions.ts +1 -1
- package/src/theming/__tests__/parseEventColours.test.ts +117 -8
- package/src/theming/parseEventColours.ts +56 -2
- package/src/types/database.generated.ts +9 -9
- package/src/types/supabase.ts +2 -3
- package/src/utils/__tests__/bundleAnalysis.unit.test.ts +9 -9
- package/src/utils/file-reference/__tests__/file-reference.test.ts +4 -0
- package/src/utils/formatting/formatDate.test.ts +3 -2
- package/src/utils/formatting/formatDateTime.test.ts +2 -2
- package/src/utils/google-places/googlePlacesUtils.test.ts +36 -24
- package/src/utils/storage/__tests__/helpers.unit.test.ts +19 -12
- package/src/utils/storage/helpers.test.ts +69 -3
- package/src/utils/supabase/createBaseClient.ts +25 -7
|
@@ -11,7 +11,7 @@ import { renderWithProviders } from '../../__tests__/helpers/test-utils';
|
|
|
11
11
|
|
|
12
12
|
describe('Alert Component', () => {
|
|
13
13
|
describe('Rendering', () => {
|
|
14
|
-
it('renders as semantic
|
|
14
|
+
it('renders as semantic p element with role="alert"', () => {
|
|
15
15
|
renderWithProviders(
|
|
16
16
|
<Alert>
|
|
17
17
|
<AlertTitle>Test Title</AlertTitle>
|
|
@@ -20,8 +20,9 @@ describe('Alert Component', () => {
|
|
|
20
20
|
);
|
|
21
21
|
|
|
22
22
|
const alert = screen.getByRole('alert');
|
|
23
|
-
expect(alert.tagName).toBe('
|
|
23
|
+
expect(alert.tagName).toBe('P');
|
|
24
24
|
expect(alert).toBeInTheDocument();
|
|
25
|
+
expect(alert).toHaveAttribute('role', 'alert');
|
|
25
26
|
});
|
|
26
27
|
|
|
27
28
|
it('renders with default variant', () => {
|
|
@@ -34,7 +35,8 @@ describe('Alert Component', () => {
|
|
|
34
35
|
|
|
35
36
|
const alert = screen.getByRole('alert');
|
|
36
37
|
expect(alert).toBeInTheDocument();
|
|
37
|
-
expect(alert.tagName).toBe('
|
|
38
|
+
expect(alert.tagName).toBe('P');
|
|
39
|
+
expect(alert).toHaveAttribute('role', 'alert');
|
|
38
40
|
expect(alert).toHaveClass('relative', 'w-full', 'rounded-lg', 'border', 'p-4');
|
|
39
41
|
});
|
|
40
42
|
|
|
@@ -48,7 +50,8 @@ describe('Alert Component', () => {
|
|
|
48
50
|
|
|
49
51
|
const alert = screen.getByRole('alert');
|
|
50
52
|
expect(alert).toBeInTheDocument();
|
|
51
|
-
expect(alert.tagName).toBe('
|
|
53
|
+
expect(alert.tagName).toBe('P');
|
|
54
|
+
expect(alert).toHaveAttribute('role', 'alert');
|
|
52
55
|
expect(alert).toHaveClass('border-destructive', 'text-destructive');
|
|
53
56
|
});
|
|
54
57
|
|
|
@@ -73,7 +76,8 @@ describe('Alert Component', () => {
|
|
|
73
76
|
);
|
|
74
77
|
|
|
75
78
|
const alert = screen.getByRole('alert');
|
|
76
|
-
expect(alert.tagName).toBe('
|
|
79
|
+
expect(alert.tagName).toBe('P');
|
|
80
|
+
expect(alert).toHaveAttribute('role', 'alert');
|
|
77
81
|
expect(alert).toHaveClass('custom-alert-class');
|
|
78
82
|
});
|
|
79
83
|
|
|
@@ -88,7 +92,7 @@ describe('Alert Component', () => {
|
|
|
88
92
|
});
|
|
89
93
|
|
|
90
94
|
it('forwards ref correctly', () => {
|
|
91
|
-
const ref = React.createRef<
|
|
95
|
+
const ref = React.createRef<HTMLParagraphElement>();
|
|
92
96
|
|
|
93
97
|
renderWithProviders(
|
|
94
98
|
<Alert ref={ref}>
|
|
@@ -96,8 +100,8 @@ describe('Alert Component', () => {
|
|
|
96
100
|
</Alert>
|
|
97
101
|
);
|
|
98
102
|
|
|
99
|
-
expect(ref.current).toBeInstanceOf(
|
|
100
|
-
expect(ref.current?.tagName).toBe('
|
|
103
|
+
expect(ref.current).toBeInstanceOf(HTMLParagraphElement);
|
|
104
|
+
expect(ref.current?.tagName).toBe('P');
|
|
101
105
|
expect(ref.current).toHaveAttribute('role', 'alert');
|
|
102
106
|
});
|
|
103
107
|
});
|
|
@@ -275,7 +279,54 @@ describe('Alert Component', () => {
|
|
|
275
279
|
|
|
276
280
|
const alert = screen.getByRole('alert');
|
|
277
281
|
expect(alert).toBeInTheDocument();
|
|
278
|
-
expect(alert.tagName).toBe('
|
|
282
|
+
expect(alert.tagName).toBe('P');
|
|
283
|
+
expect(alert).toHaveAttribute('role', 'alert');
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it('supports role="status" prop for informational messages', () => {
|
|
287
|
+
renderWithProviders(
|
|
288
|
+
<Alert role="status" aria-live="polite">
|
|
289
|
+
<AlertTitle>Status Message</AlertTitle>
|
|
290
|
+
<AlertDescription>This is a status message</AlertDescription>
|
|
291
|
+
</Alert>
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
const status = screen.getByRole('status');
|
|
295
|
+
expect(status).toBeInTheDocument();
|
|
296
|
+
expect(status.tagName).toBe('P');
|
|
297
|
+
expect(status).toHaveAttribute('role', 'status');
|
|
298
|
+
expect(status).toHaveAttribute('aria-live', 'polite');
|
|
299
|
+
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('defaults to role="alert" when role prop not provided', () => {
|
|
303
|
+
renderWithProviders(
|
|
304
|
+
<Alert>
|
|
305
|
+
<AlertTitle>Default Alert</AlertTitle>
|
|
306
|
+
<AlertDescription>This should default to alert role</AlertDescription>
|
|
307
|
+
</Alert>
|
|
308
|
+
);
|
|
309
|
+
|
|
310
|
+
const alert = screen.getByRole('alert');
|
|
311
|
+
expect(alert).toBeInTheDocument();
|
|
312
|
+
expect(alert).toHaveAttribute('role', 'alert');
|
|
313
|
+
expect(screen.queryByRole('status')).not.toBeInTheDocument();
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it('allows custom role values', () => {
|
|
317
|
+
renderWithProviders(
|
|
318
|
+
<Alert role="region" aria-label="Custom region">
|
|
319
|
+
<AlertTitle>Custom Role</AlertTitle>
|
|
320
|
+
<AlertDescription>This uses a custom role</AlertDescription>
|
|
321
|
+
</Alert>
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
const region = screen.getByRole('region', { name: 'Custom region' });
|
|
325
|
+
expect(region).toBeInTheDocument();
|
|
326
|
+
expect(region.tagName).toBe('P');
|
|
327
|
+
expect(region).toHaveAttribute('role', 'region');
|
|
328
|
+
expect(screen.queryByRole('alert')).not.toBeInTheDocument();
|
|
329
|
+
expect(screen.queryByRole('status')).not.toBeInTheDocument();
|
|
279
330
|
});
|
|
280
331
|
|
|
281
332
|
it('does not have role="alert" for inline variant', () => {
|
|
@@ -301,7 +352,8 @@ describe('Alert Component', () => {
|
|
|
301
352
|
|
|
302
353
|
const alert = screen.getByRole('alert');
|
|
303
354
|
expect(alert).toBeInTheDocument();
|
|
304
|
-
expect(alert.tagName).toBe('
|
|
355
|
+
expect(alert.tagName).toBe('P');
|
|
356
|
+
expect(alert).toHaveAttribute('role', 'alert');
|
|
305
357
|
|
|
306
358
|
// Screen readers will announce the content within the alert
|
|
307
359
|
expect(screen.getByText('Important Notice')).toBeInTheDocument();
|
|
@@ -320,7 +372,8 @@ describe('Alert Component', () => {
|
|
|
320
372
|
const title = screen.getByRole('heading', { level: 5 });
|
|
321
373
|
const description = screen.getByText('Semantic description with proper heading structure');
|
|
322
374
|
|
|
323
|
-
expect(alert.tagName).toBe('
|
|
375
|
+
expect(alert.tagName).toBe('P');
|
|
376
|
+
expect(alert).toHaveAttribute('role', 'alert');
|
|
324
377
|
expect(title).toBeInTheDocument();
|
|
325
378
|
expect(description.tagName).toBe('P');
|
|
326
379
|
});
|
|
@@ -339,7 +392,8 @@ describe('Alert Component', () => {
|
|
|
339
392
|
|
|
340
393
|
const alert = screen.getByRole('alert');
|
|
341
394
|
expect(alert).toBeInTheDocument();
|
|
342
|
-
expect(alert.tagName).toBe('
|
|
395
|
+
expect(alert.tagName).toBe('P');
|
|
396
|
+
expect(alert).toHaveAttribute('role', 'alert');
|
|
343
397
|
expect(screen.getByText('⚠️')).toBeInTheDocument();
|
|
344
398
|
expect(screen.getByRole('button', { name: 'Dismiss' })).toBeInTheDocument();
|
|
345
399
|
});
|
|
@@ -355,7 +409,8 @@ describe('Alert Component', () => {
|
|
|
355
409
|
|
|
356
410
|
const alert = screen.getByRole('alert');
|
|
357
411
|
expect(alert).toBeInTheDocument();
|
|
358
|
-
expect(alert.tagName).toBe('
|
|
412
|
+
expect(alert.tagName).toBe('P');
|
|
413
|
+
expect(alert).toHaveAttribute('role', 'alert');
|
|
359
414
|
expect(screen.getByText('First description')).toBeInTheDocument();
|
|
360
415
|
expect(screen.getByText('Second description')).toBeInTheDocument();
|
|
361
416
|
});
|
|
@@ -369,7 +424,8 @@ describe('Alert Component', () => {
|
|
|
369
424
|
|
|
370
425
|
const alert = screen.getByRole('alert');
|
|
371
426
|
expect(alert).toBeInTheDocument();
|
|
372
|
-
expect(alert.tagName).toBe('
|
|
427
|
+
expect(alert.tagName).toBe('P');
|
|
428
|
+
expect(alert).toHaveAttribute('role', 'alert');
|
|
373
429
|
expect(screen.getByText('Description without title')).toBeInTheDocument();
|
|
374
430
|
});
|
|
375
431
|
|
|
@@ -382,7 +438,8 @@ describe('Alert Component', () => {
|
|
|
382
438
|
|
|
383
439
|
const alert = screen.getByRole('alert');
|
|
384
440
|
expect(alert).toBeInTheDocument();
|
|
385
|
-
expect(alert.tagName).toBe('
|
|
441
|
+
expect(alert.tagName).toBe('P');
|
|
442
|
+
expect(alert).toHaveAttribute('role', 'alert');
|
|
386
443
|
expect(screen.getByRole('heading', { level: 5 })).toHaveTextContent('Title without description');
|
|
387
444
|
});
|
|
388
445
|
});
|
|
@@ -393,7 +450,8 @@ describe('Alert Component', () => {
|
|
|
393
450
|
|
|
394
451
|
const alert = screen.getByRole('alert');
|
|
395
452
|
expect(alert).toBeInTheDocument();
|
|
396
|
-
expect(alert.tagName).toBe('
|
|
453
|
+
expect(alert.tagName).toBe('P');
|
|
454
|
+
expect(alert).toHaveAttribute('role', 'alert');
|
|
397
455
|
expect(alert).toBeEmptyDOMElement();
|
|
398
456
|
});
|
|
399
457
|
|
|
@@ -423,7 +481,8 @@ describe('Alert Component', () => {
|
|
|
423
481
|
// Should fallback to default behavior
|
|
424
482
|
const alert = screen.getByRole('alert');
|
|
425
483
|
expect(alert).toBeInTheDocument();
|
|
426
|
-
expect(alert.tagName).toBe('
|
|
484
|
+
expect(alert.tagName).toBe('P');
|
|
485
|
+
expect(alert).toHaveAttribute('role', 'alert');
|
|
427
486
|
});
|
|
428
487
|
|
|
429
488
|
it('handles rapid variant changes', () => {
|
|
@@ -454,7 +513,8 @@ describe('Alert Component', () => {
|
|
|
454
513
|
|
|
455
514
|
const alert = screen.getByRole('alert');
|
|
456
515
|
expect(alert).toBeInTheDocument();
|
|
457
|
-
expect(alert.tagName).toBe('
|
|
516
|
+
expect(alert.tagName).toBe('P');
|
|
517
|
+
expect(alert).toHaveAttribute('role', 'alert');
|
|
458
518
|
});
|
|
459
519
|
});
|
|
460
520
|
|
|
@@ -473,7 +533,8 @@ describe('Alert Component', () => {
|
|
|
473
533
|
|
|
474
534
|
const alert = screen.getByRole('alert');
|
|
475
535
|
expect(alert).toBeInTheDocument();
|
|
476
|
-
expect(alert.tagName).toBe('
|
|
536
|
+
expect(alert.tagName).toBe('P');
|
|
537
|
+
expect(alert).toHaveAttribute('role', 'alert');
|
|
477
538
|
expect(screen.getByRole('textbox')).toBeInTheDocument();
|
|
478
539
|
expect(screen.getByRole('button', { name: 'Submit' })).toBeInTheDocument();
|
|
479
540
|
});
|
|
@@ -494,8 +555,10 @@ describe('Alert Component', () => {
|
|
|
494
555
|
|
|
495
556
|
const alerts = screen.getAllByRole('alert');
|
|
496
557
|
expect(alerts).toHaveLength(2);
|
|
497
|
-
expect(alerts[0].tagName).toBe('
|
|
498
|
-
expect(alerts[1].tagName).toBe('
|
|
558
|
+
expect(alerts[0].tagName).toBe('P');
|
|
559
|
+
expect(alerts[1].tagName).toBe('P');
|
|
560
|
+
expect(alerts[0]).toHaveAttribute('role', 'alert');
|
|
561
|
+
expect(alerts[1]).toHaveAttribute('role', 'alert');
|
|
499
562
|
expect(alerts[0]).toHaveClass('bg-background', 'text-foreground');
|
|
500
563
|
expect(alerts[1]).toHaveClass('border-destructive', 'text-destructive');
|
|
501
564
|
});
|
|
@@ -516,7 +579,8 @@ describe('Alert Component', () => {
|
|
|
516
579
|
|
|
517
580
|
const alert = screen.getByRole('alert');
|
|
518
581
|
expect(alert).toBeInTheDocument();
|
|
519
|
-
expect(alert.tagName).toBe('
|
|
582
|
+
expect(alert.tagName).toBe('P');
|
|
583
|
+
expect(alert).toHaveAttribute('role', 'alert');
|
|
520
584
|
expect(screen.getByText('Complex Alert')).toBeInTheDocument();
|
|
521
585
|
expect(screen.getByText('This is a complex description with')).toBeInTheDocument();
|
|
522
586
|
expect(screen.getByText('Multiple elements')).toBeInTheDocument();
|
|
@@ -10,27 +10,32 @@
|
|
|
10
10
|
* Features:
|
|
11
11
|
* - Multiple visual variants (default, destructive, inline)
|
|
12
12
|
* - Title and description support
|
|
13
|
-
* - Semantic HTML: renders as `<
|
|
14
|
-
* - ARIA role="alert" for accessibility
|
|
13
|
+
* - Semantic HTML: renders as `<p>` element with `role="alert"` (default) or custom role
|
|
15
14
|
* - Keyboard and screen reader accessible
|
|
16
15
|
* - Composable with icons and actions
|
|
17
16
|
* - Inline variant for lightweight text formatting
|
|
18
17
|
*
|
|
19
18
|
* @example
|
|
20
19
|
* ```tsx
|
|
21
|
-
* // Basic alert (renders as <
|
|
20
|
+
* // Basic alert (renders as <p role="alert"> with <h5> title and <p> description)
|
|
22
21
|
* <Alert>
|
|
23
22
|
* <AlertTitle>Success</AlertTitle>
|
|
24
23
|
* <AlertDescription>Your changes have been saved.</AlertDescription>
|
|
25
24
|
* </Alert>
|
|
26
25
|
*
|
|
27
|
-
* // Destructive alert with icon (renders as <
|
|
26
|
+
* // Destructive alert with icon (renders as <p role="alert"> with <h5> title and <p> description)
|
|
28
27
|
* <Alert variant="destructive">
|
|
29
28
|
* <ErrorIcon />
|
|
30
29
|
* <AlertTitle>Error</AlertTitle>
|
|
31
30
|
* <AlertDescription>Something went wrong.</AlertDescription>
|
|
32
31
|
* </Alert>
|
|
33
32
|
*
|
|
33
|
+
* // Status message (renders as <p role="status"> for informational messages)
|
|
34
|
+
* <Alert role="status" aria-live="polite">
|
|
35
|
+
* <AlertTitle>No data available</AlertTitle>
|
|
36
|
+
* <AlertDescription>Get started by adding your first entry.</AlertDescription>
|
|
37
|
+
* </Alert>
|
|
38
|
+
*
|
|
34
39
|
* // Inline alert (renders as React.Fragment with <strong> title and <span> description)
|
|
35
40
|
* <Alert variant="inline">
|
|
36
41
|
* <AlertTitle>Note:</AlertTitle>
|
|
@@ -39,8 +44,8 @@
|
|
|
39
44
|
* ```
|
|
40
45
|
*
|
|
41
46
|
* @accessibility
|
|
42
|
-
* - Uses semantic HTML: `<
|
|
43
|
-
* -
|
|
47
|
+
* - Uses semantic HTML: `<p>` element with `role="alert"` (default) for screen reader announcements
|
|
48
|
+
* - Can be customized with `role="status"` for informational messages
|
|
44
49
|
* - Title and description are semantically structured
|
|
45
50
|
* - Supports keyboard navigation and focus
|
|
46
51
|
*/
|
|
@@ -65,9 +70,12 @@ const getAlertClasses = (variant: "default" | "destructive" | "inline" = "defaul
|
|
|
65
70
|
};
|
|
66
71
|
|
|
67
72
|
const Alert = React.forwardRef<
|
|
68
|
-
|
|
69
|
-
React.HTMLAttributes<
|
|
70
|
-
|
|
73
|
+
HTMLParagraphElement,
|
|
74
|
+
React.HTMLAttributes<HTMLParagraphElement> & {
|
|
75
|
+
variant?: "default" | "destructive" | "inline";
|
|
76
|
+
role?: string;
|
|
77
|
+
}
|
|
78
|
+
>(({ className, variant = "default", role = "alert", ...props }, ref) => {
|
|
71
79
|
const contextValue = React.useMemo(() => ({ variant }), [variant])
|
|
72
80
|
|
|
73
81
|
if (variant === "inline") {
|
|
@@ -80,10 +88,10 @@ const Alert = React.forwardRef<
|
|
|
80
88
|
|
|
81
89
|
return (
|
|
82
90
|
<AlertContext.Provider value={contextValue}>
|
|
83
|
-
<
|
|
91
|
+
<p
|
|
84
92
|
ref={ref}
|
|
85
93
|
className={cn(getAlertClasses(variant), className)}
|
|
86
|
-
role=
|
|
94
|
+
role={role}
|
|
87
95
|
{...props}
|
|
88
96
|
/>
|
|
89
97
|
</AlertContext.Provider>
|
|
@@ -163,7 +163,7 @@ function buildVariantClasses(style: Style, color: Color, shade: Shade): string {
|
|
|
163
163
|
* Classes used: shadow-badge-soft shadow-main-200 shadow-main-500 shadow-main-700
|
|
164
164
|
* shadow-sec-200 shadow-sec-500 shadow-sec-700 shadow-acc-200 shadow-acc-500 shadow-acc-700
|
|
165
165
|
*/
|
|
166
|
-
const
|
|
166
|
+
const _tailwindClassScan = [
|
|
167
167
|
// Solid background classes
|
|
168
168
|
'bg-main-100', 'bg-main-600', 'bg-main-900',
|
|
169
169
|
'bg-sec-100', 'bg-sec-600', 'bg-sec-900',
|
|
@@ -474,8 +474,9 @@ describe('Checkbox Component', () => {
|
|
|
474
474
|
const endTime = performance.now();
|
|
475
475
|
|
|
476
476
|
// Performance test: verify rendering completes in reasonable time
|
|
477
|
+
// Note: Performance can vary based on system load, so we use a more lenient threshold
|
|
477
478
|
expect(screen.getAllByRole('checkbox')).toHaveLength(100);
|
|
478
|
-
expect(endTime - startTime).toBeLessThan(
|
|
479
|
+
expect(endTime - startTime).toBeLessThan(3000);
|
|
479
480
|
});
|
|
480
481
|
});
|
|
481
482
|
});
|
|
@@ -55,7 +55,6 @@ import { LoadingSpinner } from '../LoadingSpinner/LoadingSpinner';
|
|
|
55
55
|
import { RefreshCw, AlertCircle, Building2, Calendar } from 'lucide-react';
|
|
56
56
|
import { useOrganisations } from '../../hooks/useOrganisations';
|
|
57
57
|
import { useEvents } from '../../hooks/useEvents';
|
|
58
|
-
import { useRBAC } from '../../rbac/hooks/useRBAC';
|
|
59
58
|
import type { Organisation } from '../../types/organisation';
|
|
60
59
|
import type { Event } from '../../types/event';
|
|
61
60
|
import { logger } from '../../utils/core/logger';
|
|
@@ -133,7 +132,6 @@ export function ContextSelector({
|
|
|
133
132
|
refreshEvents
|
|
134
133
|
} = useEvents();
|
|
135
134
|
|
|
136
|
-
const { isSuperAdmin } = useRBAC();
|
|
137
135
|
|
|
138
136
|
const isLoading = (showOrganisations && orgLoading) || (showEvents && eventLoading);
|
|
139
137
|
const hasError = (showOrganisations && orgError) || (showEvents && eventError);
|
|
@@ -155,6 +153,45 @@ export function ContextSelector({
|
|
|
155
153
|
return '';
|
|
156
154
|
}, [showOrganisations, showEvents, selectedOrganisation?.id, selectedEvent]);
|
|
157
155
|
|
|
156
|
+
// Format display value
|
|
157
|
+
// Priority: Event selection takes precedence over organisation selection (matches currentValue)
|
|
158
|
+
const displayValue = useMemo(() => {
|
|
159
|
+
if (showEvents && selectedEvent) {
|
|
160
|
+
return (
|
|
161
|
+
<>
|
|
162
|
+
<Calendar className="inline-block size-4 mr-2" />
|
|
163
|
+
<span className="truncate">{selectedEvent.event_name}</span>
|
|
164
|
+
</>
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
if (showOrganisations && selectedOrganisation) {
|
|
168
|
+
return (
|
|
169
|
+
<>
|
|
170
|
+
<Building2 className="inline-block size-4 mr-2" />
|
|
171
|
+
<span className="truncate">{selectedOrganisation.display_name}</span>
|
|
172
|
+
</>
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
return null;
|
|
176
|
+
}, [showOrganisations, showEvents, selectedOrganisation, selectedEvent]);
|
|
177
|
+
|
|
178
|
+
// Determine placeholder text based on what's shown
|
|
179
|
+
const effectivePlaceholder = useMemo(() => {
|
|
180
|
+
if (placeholder !== "Select organisation or event") {
|
|
181
|
+
return placeholder;
|
|
182
|
+
}
|
|
183
|
+
if (showOrganisations && showEvents) {
|
|
184
|
+
return "Select organisation or event";
|
|
185
|
+
}
|
|
186
|
+
if (showOrganisations) {
|
|
187
|
+
return "Select organisation";
|
|
188
|
+
}
|
|
189
|
+
if (showEvents) {
|
|
190
|
+
return "Select event";
|
|
191
|
+
}
|
|
192
|
+
return placeholder;
|
|
193
|
+
}, [placeholder, showOrganisations, showEvents]);
|
|
194
|
+
|
|
158
195
|
const handleValueChange = (value: string) => {
|
|
159
196
|
if (disabled || isLoading) return;
|
|
160
197
|
|
|
@@ -265,45 +302,6 @@ export function ContextSelector({
|
|
|
265
302
|
return null;
|
|
266
303
|
}
|
|
267
304
|
|
|
268
|
-
// Format display value
|
|
269
|
-
// Priority: Event selection takes precedence over organisation selection (matches currentValue)
|
|
270
|
-
const displayValue = useMemo(() => {
|
|
271
|
-
if (showEvents && selectedEvent) {
|
|
272
|
-
return (
|
|
273
|
-
<>
|
|
274
|
-
<Calendar className="inline-block size-4 mr-2" />
|
|
275
|
-
<span className="truncate">{selectedEvent.event_name}</span>
|
|
276
|
-
</>
|
|
277
|
-
);
|
|
278
|
-
}
|
|
279
|
-
if (showOrganisations && selectedOrganisation) {
|
|
280
|
-
return (
|
|
281
|
-
<>
|
|
282
|
-
<Building2 className="inline-block size-4 mr-2" />
|
|
283
|
-
<span className="truncate">{selectedOrganisation.display_name}</span>
|
|
284
|
-
</>
|
|
285
|
-
);
|
|
286
|
-
}
|
|
287
|
-
return null;
|
|
288
|
-
}, [showOrganisations, showEvents, selectedOrganisation, selectedEvent]);
|
|
289
|
-
|
|
290
|
-
// Determine placeholder text based on what's shown
|
|
291
|
-
const effectivePlaceholder = useMemo(() => {
|
|
292
|
-
if (placeholder !== "Select organisation or event") {
|
|
293
|
-
return placeholder;
|
|
294
|
-
}
|
|
295
|
-
if (showOrganisations && showEvents) {
|
|
296
|
-
return "Select organisation or event";
|
|
297
|
-
}
|
|
298
|
-
if (showOrganisations) {
|
|
299
|
-
return "Select organisation";
|
|
300
|
-
}
|
|
301
|
-
if (showEvents) {
|
|
302
|
-
return "Select event";
|
|
303
|
-
}
|
|
304
|
-
return placeholder;
|
|
305
|
-
}, [placeholder, showOrganisations, showEvents]);
|
|
306
|
-
|
|
307
305
|
return (
|
|
308
306
|
<Select
|
|
309
307
|
value={currentValue}
|
|
@@ -283,25 +283,7 @@
|
|
|
283
283
|
import React from 'react';
|
|
284
284
|
import { DataTableCore } from './components/DataTableCore';
|
|
285
285
|
import { createLogger } from '../../utils/core/logger';
|
|
286
|
-
import { normalizeDataTableFeatures } from './types';
|
|
287
|
-
import type {
|
|
288
|
-
DataRecord,
|
|
289
|
-
GetRowId,
|
|
290
|
-
ServerSideParams,
|
|
291
|
-
PerformanceConfig,
|
|
292
|
-
ServerSideConfig,
|
|
293
|
-
ChunkingConfig,
|
|
294
|
-
SearchIndexConfig,
|
|
295
|
-
PaginationMode,
|
|
296
|
-
EmptyStateConfig,
|
|
297
|
-
DataTableFeatureConfig,
|
|
298
|
-
DataTableColumn,
|
|
299
|
-
SimpleColumn,
|
|
300
|
-
AggregateConfig,
|
|
301
|
-
DataTableAction,
|
|
302
|
-
HierarchicalConfig,
|
|
303
|
-
DataTableRBACConfig
|
|
304
|
-
} from './types';
|
|
286
|
+
import { normalizeDataTableFeatures, type DataRecord, type GetRowId, type ServerSideParams, type PerformanceConfig, type ServerSideConfig, type ChunkingConfig, type SearchIndexConfig, type PaginationMode, type EmptyStateConfig, type DataTableFeatureConfig, type DataTableColumn, type SimpleColumn, type AggregateConfig, type DataTableAction, type HierarchicalConfig, type DataTableRBACConfig } from './types';
|
|
305
287
|
import type { ImportModalConfig } from './components/ImportModal';
|
|
306
288
|
|
|
307
289
|
// ============================================================================
|