@jmruthers/pace-core 0.6.6 → 0.6.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/{scripts/audit/audit-dependencies.cjs → audit-tool/00-dependencies.cjs} +12 -13
- package/audit-tool/audits/01-pace-core-compliance.cjs +556 -0
- package/audit-tool/audits/02-project-structure.cjs +255 -0
- package/audit-tool/audits/03-architecture.cjs +196 -0
- package/audit-tool/audits/04-code-quality.cjs +149 -0
- package/audit-tool/audits/05-styling.cjs +224 -0
- package/audit-tool/audits/06-security-rbac.cjs +544 -0
- package/audit-tool/audits/07-api-tech-stack.cjs +301 -0
- package/audit-tool/audits/08-testing-documentation.cjs +202 -0
- package/audit-tool/audits/09-operations.cjs +208 -0
- package/audit-tool/index.cjs +291 -0
- package/audit-tool/utils/code-utils.cjs +218 -0
- package/audit-tool/utils/file-utils.cjs +230 -0
- package/audit-tool/utils/report-utils.cjs +241 -0
- package/cursor-rules/00-standards-overview.mdc +156 -0
- package/cursor-rules/{00-pace-core-compliance.mdc → 01-pace-core-compliance.mdc} +187 -34
- package/cursor-rules/02-project-structure.mdc +37 -5
- package/cursor-rules/{03-solid-principles.mdc → 03-architecture.mdc} +125 -11
- package/cursor-rules/04-code-quality.mdc +419 -0
- package/cursor-rules/{08-markup-quality.mdc → 05-styling.mdc} +55 -10
- package/cursor-rules/{09-rbac-compliance.mdc → 06-security-rbac.mdc} +62 -6
- package/cursor-rules/07-api-tech-stack.mdc +377 -0
- package/cursor-rules/08-testing-documentation.mdc +324 -0
- package/cursor-rules/09-operations.mdc +365 -0
- package/dist/DataTable-7PMH7XN7.js +15 -0
- package/dist/{DataTable-2N_tqbfq.d.ts → DataTable-DRUIgtUH.d.ts} +1 -1
- package/dist/{PublicPageProvider-BBH6Vqg7.d.ts → PublicPageProvider-DlsCaR5v.d.ts} +26 -16
- package/dist/{chunk-FENMYN2U.js → chunk-5X4QLXRG.js} +1 -3
- package/dist/{chunk-4T7OBVTU.js → chunk-6F3IILHI.js} +1 -1
- package/dist/{chunk-SD6WQY43.js → chunk-7ILTDCL2.js} +9 -1
- package/dist/{chunk-3QC3KRHK.js → chunk-A3W6LW53.js} +16 -1
- package/dist/{chunk-7TYHROIV.js → chunk-BM4CQ5P3.js} +50 -8
- package/dist/{chunk-2HGJFNAH.js → chunk-FEJLJNWA.js} +1 -15
- package/dist/{chunk-OHIK3MIO.js → chunk-GHYHJTYV.js} +2 -2
- package/dist/{chunk-UIYSCEV7.js → chunk-IUBRCBSY.js} +1 -1
- package/dist/{chunk-LAZMKTTF.js → chunk-JGWDVX64.js} +281 -347
- package/dist/{chunk-MAGBIDNS.js → chunk-L4XMVJKY.js} +2 -2
- package/dist/{chunk-A55DK444.js → chunk-OJ4SKRSV.js} +1 -7
- package/dist/{chunk-ZS5VO5JB.js → chunk-Q7Q7V5NV.js} +406 -451
- package/dist/{chunk-3O3WHILE.js → chunk-VBCS3DUA.js} +236 -60
- package/dist/{chunk-BVP2BCJF.js → chunk-ZKAWKYT4.js} +8 -8
- package/dist/components.d.ts +5 -4
- package/dist/components.js +27 -32
- package/dist/eslint-rules/index.cjs +22 -9
- package/{src/eslint-rules/rules/compliance.cjs → dist/eslint-rules/rules/01-pace-core-compliance.cjs} +184 -23
- package/dist/eslint-rules/rules/04-code-quality.cjs +290 -0
- package/dist/eslint-rules/rules/05-styling.cjs +61 -0
- package/dist/eslint-rules/rules/{rbac.cjs → 06-security-rbac.cjs} +26 -10
- package/dist/eslint-rules/rules/07-api-tech-stack.cjs +263 -0
- package/dist/eslint-rules/rules/08-testing.cjs +94 -0
- package/dist/hooks.d.ts +5 -5
- package/dist/hooks.js +6 -6
- package/dist/index.d.ts +6 -6
- package/dist/index.js +18 -17
- package/dist/rbac/index.js +6 -6
- package/dist/theming/runtime.d.ts +14 -1
- package/dist/theming/runtime.js +1 -1
- package/dist/{types-B-K_5VnO.d.ts → types-DXstZpNI.d.ts} +0 -17
- package/dist/{usePublicRouteParams-COZ28Mvq.d.ts → usePublicRouteParams-MamNgwqe.d.ts} +19 -19
- package/dist/utils.d.ts +2 -2
- package/dist/utils.js +8 -8
- package/docs/README.md +1 -1
- package/docs/api/modules.md +47 -31
- package/docs/api-reference/components.md +18 -20
- package/docs/api-reference/hooks.md +80 -80
- package/docs/api-reference/types.md +1 -1
- package/docs/api-reference/utilities.md +1 -1
- package/docs/architecture/README.md +1 -1
- package/docs/core-concepts/events.md +3 -3
- package/docs/core-concepts/organisations.md +6 -6
- package/docs/core-concepts/permissions.md +6 -6
- package/docs/documentation-index.md +12 -18
- package/docs/getting-started/documentation-index.md +1 -1
- package/docs/getting-started/examples/README.md +4 -4
- package/docs/getting-started/examples/full-featured-app.md +1 -1
- package/docs/getting-started/faq.md +2 -2
- package/docs/getting-started/quick-reference.md +4 -4
- package/docs/implementation-guides/authentication.md +15 -15
- package/docs/implementation-guides/component-styling.md +1 -1
- package/docs/implementation-guides/data-tables.md +126 -33
- package/docs/implementation-guides/datatable-rbac-usage.md +1 -1
- package/docs/implementation-guides/dynamic-colors.md +3 -3
- package/docs/implementation-guides/file-upload-storage.md +2 -2
- package/docs/implementation-guides/hierarchical-datatable.md +40 -60
- package/docs/implementation-guides/inactivity-tracking.md +3 -3
- package/docs/implementation-guides/large-datasets.md +3 -2
- package/docs/implementation-guides/organisation-security.md +2 -2
- package/docs/implementation-guides/performance.md +2 -2
- package/docs/implementation-guides/permission-enforcement.md +1 -1
- package/docs/migration/V0.3.44_organisation-context-timing-fix.md +1 -1
- package/docs/migration/V0.4.0_rbac-migration.md +6 -6
- package/docs/rbac/README.md +5 -5
- package/docs/rbac/advanced-patterns.md +6 -6
- package/docs/rbac/api-reference.md +20 -20
- package/docs/rbac/event-based-apps.md +3 -3
- package/docs/rbac/examples.md +41 -41
- package/docs/rbac/getting-started.md +37 -37
- package/docs/rbac/performance.md +1 -1
- package/docs/rbac/quick-start.md +52 -52
- package/docs/rbac/secure-client-protection.md +1 -1
- package/docs/rbac/troubleshooting.md +1 -1
- package/docs/security/README.md +5 -5
- package/docs/standards/0-standards-overview.md +220 -0
- package/docs/standards/{00-pace-core-compliance.md → 1-pace-core-compliance-standards.md} +204 -185
- package/docs/standards/{02-project-structure.md → 2-project-structure-standards.md} +11 -47
- package/docs/standards/3-architecture-standards.md +606 -0
- package/docs/standards/4-code-quality-standards.md +728 -0
- package/docs/standards/{08-markup-quality.md → 5-styling-standards.md} +12 -9
- package/docs/standards/{09-rbac-compliance.md → 6-security-rbac-standards.md} +126 -18
- package/docs/standards/7-api-tech-stack-standards.md +662 -0
- package/docs/standards/8-testing-documentation-standards.md +401 -0
- package/docs/standards/9-operations-standards.md +1102 -0
- package/docs/standards/README.md +203 -104
- package/docs/troubleshooting/README.md +4 -4
- package/docs/troubleshooting/common-issues.md +2 -2
- package/docs/troubleshooting/debugging.md +9 -9
- package/docs/troubleshooting/migration.md +4 -4
- package/eslint-config-pace-core.cjs +21 -10
- package/package.json +6 -5
- package/scripts/install-cursor-rules.cjs +11 -243
- package/scripts/install-eslint-config.cjs +284 -0
- package/src/__tests__/helpers/__tests__/component-test-utils.test.tsx +2 -2
- package/src/__tests__/helpers/__tests__/test-providers.test.tsx +2 -2
- package/src/__tests__/helpers/__tests__/test-utils.test.tsx +10 -10
- package/src/__tests__/integration/UserProfile.test.tsx +14 -14
- package/src/__tests__/rbac/PagePermissionGuard.test.tsx +6 -6
- package/src/__tests__/templates/accessibility.test.template.tsx +9 -9
- package/src/__tests__/templates/component.test.template.tsx +18 -15
- package/src/components/Calendar/Calendar.tsx +201 -47
- package/src/components/ContextSelector/ContextSelector.tsx +137 -153
- package/src/components/DataTable/AUDIT_REPORT.md +293 -0
- package/src/components/DataTable/__tests__/DataTableCore.test.tsx +10 -2
- package/src/components/DataTable/__tests__/a11y.basic.test.tsx +10 -4
- package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +9 -9
- package/src/components/DataTable/components/ColumnFilter.tsx +63 -74
- package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +43 -41
- package/src/components/DataTable/components/DataTableErrorBoundary.tsx +9 -11
- package/src/components/DataTable/components/DataTableLayout.tsx +5 -16
- package/src/components/DataTable/components/EditableRow.tsx +5 -7
- package/src/components/DataTable/components/EmptyState.tsx +10 -9
- package/src/components/DataTable/components/FilterRow.tsx +2 -4
- package/src/components/DataTable/components/ImportModal.tsx +124 -126
- package/src/components/DataTable/components/LoadingState.tsx +5 -6
- package/src/components/DataTable/components/SortIndicator.tsx +50 -0
- package/src/components/DataTable/components/__tests__/COVERAGE_NOTE.md +4 -4
- package/src/components/DataTable/components/__tests__/ColumnFilter.test.tsx +23 -82
- package/src/components/DataTable/components/__tests__/DataTableErrorBoundary.test.tsx +37 -9
- package/src/components/DataTable/components/__tests__/EmptyState.test.tsx +7 -4
- package/src/components/DataTable/components/__tests__/FilterRow.test.tsx +12 -4
- package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +41 -27
- package/src/components/DataTable/components/index.ts +2 -1
- package/src/components/DataTable/types.ts +0 -18
- package/src/components/DataTable/utils/a11yUtils.ts +17 -0
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +2 -1
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +11 -15
- package/src/components/DateTimeField/DateTimeField.tsx +7 -8
- package/src/components/Dialog/Dialog.test.tsx +1 -0
- package/src/components/Dialog/Dialog.tsx +25 -8
- package/src/components/ErrorBoundary/ErrorBoundary.tsx +77 -79
- package/src/components/FileUpload/FileUpload.test.tsx +52 -14
- package/src/components/FileUpload/FileUpload.tsx +112 -130
- package/src/components/Progress/Progress.tsx +2 -4
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +8 -8
- package/src/components/Select/Select.tsx +86 -77
- package/src/components/Select/types.ts +3 -0
- package/src/hooks/__tests__/ServiceHooks.test.tsx +16 -16
- package/src/hooks/__tests__/hooks.integration.test.tsx +49 -49
- package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +97 -97
- package/src/hooks/public/usePublicEvent.ts +5 -5
- package/src/hooks/public/usePublicEventLogo.ts +5 -5
- package/src/hooks/public/usePublicFileDisplay.ts +2 -2
- package/src/hooks/public/usePublicRouteParams.ts +5 -5
- package/src/hooks/useAppConfig.ts +2 -2
- package/src/hooks/useEventTheme.test.ts +7 -7
- package/src/hooks/useEventTheme.ts +1 -4
- package/src/hooks/useFileDisplay.ts +2 -2
- package/src/providers/UnifiedAuthProvider.smoke.test.tsx +21 -21
- package/src/providers/__tests__/AuthProvider.test.tsx +21 -21
- package/src/providers/__tests__/EventProvider.test.tsx +61 -61
- package/src/providers/__tests__/InactivityProvider.test.tsx +56 -56
- package/src/providers/__tests__/OrganisationProvider.test.tsx +75 -75
- package/src/providers/__tests__/ProviderLifecycle.test.tsx +37 -37
- package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +103 -103
- package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +7 -7
- package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +10 -10
- package/src/styles/core.css +7 -0
- package/src/theming/__tests__/parseEventColours.test.ts +9 -3
- package/src/theming/parseEventColours.ts +22 -10
- package/src/utils/__tests__/lazyLoad.unit.test.tsx +42 -39
- package/src/utils/storage/README.md +1 -1
- package/cursor-rules/01-standards-compliance.mdc +0 -285
- package/cursor-rules/04-testing-standards.mdc +0 -270
- package/cursor-rules/05-bug-reports-and-features.mdc +0 -248
- package/cursor-rules/06-code-quality.mdc +0 -311
- package/cursor-rules/07-tech-stack-compliance.mdc +0 -216
- package/cursor-rules/10-error-handling-patterns.mdc +0 -179
- package/cursor-rules/11-performance-optimization.mdc +0 -169
- package/cursor-rules/12-ci-cd-integration.mdc +0 -150
- package/dist/DataTable-LRJL4IRV.js +0 -15
- package/dist/eslint-rules/rules/compliance.cjs +0 -348
- package/dist/eslint-rules/rules/components.cjs +0 -113
- package/dist/eslint-rules/rules/imports.cjs +0 -102
- package/docs/best-practices/README.md +0 -472
- package/docs/best-practices/accessibility.md +0 -604
- package/docs/best-practices/common-patterns.md +0 -516
- package/docs/best-practices/deployment.md +0 -1103
- package/docs/best-practices/performance.md +0 -1328
- package/docs/best-practices/security.md +0 -940
- package/docs/best-practices/testing.md +0 -1034
- package/docs/rbac/compliance/compliance-guide.md +0 -544
- package/docs/standards/01-standards-compliance.md +0 -188
- package/docs/standards/03-solid-principles.md +0 -39
- package/docs/standards/04-testing-standards.md +0 -36
- package/docs/standards/05-bug-reports-and-features.md +0 -27
- package/docs/standards/06-code-quality.md +0 -34
- package/docs/standards/07-tech-stack-compliance.md +0 -30
- package/docs/standards/10-error-handling-patterns.md +0 -401
- package/docs/standards/11-performance-optimization.md +0 -348
- package/docs/standards/12-ci-cd-integration.md +0 -370
- package/docs/standards/ALIGNMENT_REVIEW_SUMMARY.md +0 -192
- package/scripts/audit/audit-compliance.cjs +0 -1295
- package/scripts/audit/audit-components.cjs +0 -260
- package/scripts/audit/audit-rbac.cjs +0 -954
- package/scripts/audit/audit-standards.cjs +0 -1268
- package/scripts/audit/index.cjs +0 -1927
- package/src/components/DataTable/components/DataTableBody.tsx +0 -478
- package/src/components/DataTable/components/DraggableColumnHeader.tsx +0 -156
- package/src/components/DataTable/components/ExpandButton.tsx +0 -113
- package/src/components/DataTable/components/GroupHeader.tsx +0 -54
- package/src/components/DataTable/components/ViewRowModal.tsx +0 -68
- package/src/components/DataTable/components/VirtualizedDataTable.tsx +0 -525
- package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +0 -462
- package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +0 -393
- package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +0 -476
- package/src/components/DataTable/components/__tests__/VirtualizedDataTable.test.tsx +0 -128
- package/src/components/DataTable/core/DataTableContext.tsx +0 -216
- package/src/components/DataTable/core/__tests__/DataTableContext.test.tsx +0 -136
- package/src/components/DataTable/hooks/__tests__/useColumnReordering.test.ts +0 -570
- package/src/components/DataTable/hooks/useColumnReordering.ts +0 -123
- package/src/components/DataTable/utils/debugTools.ts +0 -514
- package/src/eslint-rules/index.cjs +0 -22
- package/src/eslint-rules/rules/components.cjs +0 -113
- package/src/eslint-rules/rules/imports.cjs +0 -102
- package/src/eslint-rules/rules/rbac.cjs +0 -790
- package/src/eslint-rules/utils/helpers.cjs +0 -42
- package/src/eslint-rules/utils/manifest-loader.cjs +0 -75
|
@@ -354,7 +354,11 @@ describe('DataTableCore Component', () => {
|
|
|
354
354
|
/>
|
|
355
355
|
);
|
|
356
356
|
|
|
357
|
-
|
|
357
|
+
// There are two "Loading..." texts (sr-only and visible), use getAllByText
|
|
358
|
+
const loadingTexts = screen.getAllByText('Loading...');
|
|
359
|
+
expect(loadingTexts.length).toBeGreaterThan(0);
|
|
360
|
+
// Check that the visible text is present
|
|
361
|
+
expect(screen.getByText('Loading...', { selector: 'strong' })).toBeInTheDocument();
|
|
358
362
|
});
|
|
359
363
|
});
|
|
360
364
|
|
|
@@ -1267,7 +1271,11 @@ describe('DataTableCore Component', () => {
|
|
|
1267
1271
|
/>
|
|
1268
1272
|
);
|
|
1269
1273
|
|
|
1270
|
-
|
|
1274
|
+
// There are two "Loading..." texts (sr-only and visible), use getAllByText
|
|
1275
|
+
const loadingTexts = screen.getAllByText('Loading...');
|
|
1276
|
+
expect(loadingTexts.length).toBeGreaterThan(0);
|
|
1277
|
+
// Check that the visible text is present
|
|
1278
|
+
expect(screen.getByText('Loading...', { selector: 'strong' })).toBeInTheDocument();
|
|
1271
1279
|
});
|
|
1272
1280
|
|
|
1273
1281
|
it('handles permission loading state', () => {
|
|
@@ -392,9 +392,11 @@ describe('DataTable Accessibility', () => {
|
|
|
392
392
|
);
|
|
393
393
|
|
|
394
394
|
// When loading, the table might not be rendered, so check for loading state
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
expect(
|
|
395
|
+
// The spinner has role="status" which provides the aria-live region
|
|
396
|
+
const spinner = screen.getByRole('status');
|
|
397
|
+
expect(spinner).toBeInTheDocument();
|
|
398
|
+
// Check that the visible loading text is present
|
|
399
|
+
expect(screen.getByText('Loading...', { selector: 'strong' })).toBeInTheDocument();
|
|
398
400
|
});
|
|
399
401
|
|
|
400
402
|
it('should not have aria-busy when not loading', async () => {
|
|
@@ -682,7 +684,11 @@ describe('DataTable Accessibility', () => {
|
|
|
682
684
|
const results = await axe(container, {
|
|
683
685
|
rules: {
|
|
684
686
|
// Column visibility button has icon-only design - acceptable pattern
|
|
685
|
-
'button-name': { enabled: false }
|
|
687
|
+
'button-name': { enabled: false },
|
|
688
|
+
// EmptyState uses Alert which renders as <aside> with role="status" - acceptable pattern for status messages
|
|
689
|
+
'aria-allowed-role': { enabled: false },
|
|
690
|
+
// EmptyState uses h5 for title - acceptable when used in status messages
|
|
691
|
+
'heading-order': { enabled: false }
|
|
686
692
|
}
|
|
687
693
|
});
|
|
688
694
|
expect(results).toHaveNoViolations();
|
|
@@ -14,20 +14,20 @@ import '@testing-library/jest-dom';
|
|
|
14
14
|
import React from 'react';
|
|
15
15
|
|
|
16
16
|
// Mock icon components
|
|
17
|
-
const MockEditIcon = ({ className }: { className?: string }) => <
|
|
18
|
-
const MockDeleteIcon = ({ className }: { className?: string }) => <
|
|
17
|
+
const MockEditIcon = ({ className }: { className?: string }) => <span className={className} data-testid="edit-icon">Edit</span>;
|
|
18
|
+
const MockDeleteIcon = ({ className }: { className?: string }) => <span className={className} data-testid="delete-icon">Delete</span>;
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* Common mock implementations for DataTable components
|
|
22
22
|
*/
|
|
23
23
|
export const mockComponents = {
|
|
24
|
-
DataTableContext: vi.fn(({ children }: any) => <
|
|
25
|
-
DataTableToolbar: vi.fn(() => <
|
|
26
|
-
|
|
27
|
-
DataTableModals: vi.fn(() => <
|
|
28
|
-
PaginationControls: vi.fn(() => <
|
|
29
|
-
LoadingState: vi.fn(() => <
|
|
30
|
-
DataTableErrorBoundary: vi.fn(({ children }: any) => <
|
|
24
|
+
DataTableContext: vi.fn(({ children }: any) => <section data-testid="data-table-context">{children}</section>),
|
|
25
|
+
DataTableToolbar: vi.fn(() => <nav data-testid="data-table-toolbar">Toolbar</nav>),
|
|
26
|
+
UnifiedTableBody: vi.fn(() => <main data-testid="unified-table-body">Body</main>),
|
|
27
|
+
DataTableModals: vi.fn(() => <section data-testid="data-table-modals">Modals</section>),
|
|
28
|
+
PaginationControls: vi.fn(() => <nav data-testid="pagination-controls">Pagination</nav>),
|
|
29
|
+
LoadingState: vi.fn(() => <section data-testid="loading-state">Loading</section>),
|
|
30
|
+
DataTableErrorBoundary: vi.fn(({ children }: any) => <section data-testid="error-boundary">{children}</section>),
|
|
31
31
|
};
|
|
32
32
|
|
|
33
33
|
/**
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Input } from '../../Input/Input';
|
|
3
3
|
import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from '../../Select/Select';
|
|
4
|
-
import { Button } from '../../Button/Button';
|
|
5
|
-
import { X, Filter } from 'lucide-react';
|
|
6
4
|
import type { Column } from '@tanstack/react-table';
|
|
7
5
|
import { getColumnHeaderText } from '../utils/columnUtils';
|
|
6
|
+
import { Filter } from 'lucide-react';
|
|
8
7
|
|
|
9
8
|
/**
|
|
10
9
|
* Props for the ColumnFilter component.
|
|
@@ -50,80 +49,70 @@ export function ColumnFilter({
|
|
|
50
49
|
|
|
51
50
|
const hasFilter = columnFilterValue !== undefined && columnFilterValue !== '';
|
|
52
51
|
|
|
53
|
-
// Get the default placeholder using column header text
|
|
52
|
+
// Get the default placeholder using column header text (for Input components)
|
|
54
53
|
const defaultPlaceholder = `Filter ${getColumnHeaderText(column)}...`;
|
|
55
|
-
|
|
56
|
-
const
|
|
57
|
-
switch (filterType) {
|
|
58
|
-
case 'select':
|
|
59
|
-
return (
|
|
60
|
-
<Select
|
|
61
|
-
value={columnFilterValue as string || ''}
|
|
62
|
-
onValueChange={handleFilterChange}
|
|
63
|
-
>
|
|
64
|
-
<SelectTrigger className="h-8">
|
|
65
|
-
<SelectValue placeholder={placeholder || defaultPlaceholder} />
|
|
66
|
-
</SelectTrigger>
|
|
67
|
-
<SelectContent>
|
|
68
|
-
<SelectItem value="">All</SelectItem>
|
|
69
|
-
{options.map((option) => (
|
|
70
|
-
<SelectItem key={option.value} value={option.value}>
|
|
71
|
-
{option.label}
|
|
72
|
-
</SelectItem>
|
|
73
|
-
))}
|
|
74
|
-
</SelectContent>
|
|
75
|
-
</Select>
|
|
76
|
-
);
|
|
77
|
-
|
|
78
|
-
case 'number':
|
|
79
|
-
// Always hide spinner arrows for number filter inputs (cleaner UX)
|
|
80
|
-
return (
|
|
81
|
-
<Input
|
|
82
|
-
type="number"
|
|
83
|
-
value={columnFilterValue as string || ''}
|
|
84
|
-
onChange={(e) => handleFilterChange(e.target.value ? Number(e.target.value) : undefined)}
|
|
85
|
-
placeholder={placeholder || defaultPlaceholder}
|
|
86
|
-
className="h-8 datatable-number-no-spinners"
|
|
87
|
-
/>
|
|
88
|
-
);
|
|
89
|
-
|
|
90
|
-
case 'date':
|
|
91
|
-
return (
|
|
92
|
-
<Input
|
|
93
|
-
type="date"
|
|
94
|
-
value={columnFilterValue as string || ''}
|
|
95
|
-
onChange={(e) => handleFilterChange(e.target.value || undefined)}
|
|
96
|
-
placeholder={placeholder || defaultPlaceholder}
|
|
97
|
-
className="h-8"
|
|
98
|
-
/>
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
default: // text
|
|
102
|
-
return (
|
|
103
|
-
<Input
|
|
104
|
-
value={columnFilterValue as string || ''}
|
|
105
|
-
onChange={(e) => handleFilterChange(e.target.value || undefined)}
|
|
106
|
-
placeholder={placeholder || defaultPlaceholder}
|
|
107
|
-
className="h-8"
|
|
108
|
-
/>
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
};
|
|
54
|
+
// For Select components, use just the column name (icon will replace "Filter" text)
|
|
55
|
+
const selectColumnName = `${getColumnHeaderText(column)}...`;
|
|
112
56
|
|
|
113
57
|
return (
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
58
|
+
(() => {
|
|
59
|
+
switch (filterType) {
|
|
60
|
+
case 'select':
|
|
61
|
+
return (
|
|
62
|
+
<Select
|
|
63
|
+
value={columnFilterValue as string || ''}
|
|
64
|
+
onValueChange={handleFilterChange}
|
|
65
|
+
>
|
|
66
|
+
<SelectTrigger className="h-8">
|
|
67
|
+
<SelectValue>
|
|
68
|
+
<Filter className="size-4 inline-block mr-2"/>
|
|
69
|
+
<span className="truncate">{selectColumnName}</span>
|
|
70
|
+
</SelectValue>
|
|
71
|
+
</SelectTrigger>
|
|
72
|
+
<SelectContent>
|
|
73
|
+
<SelectItem value="">All</SelectItem>
|
|
74
|
+
{options.map((option) => (
|
|
75
|
+
<SelectItem key={option.value} value={option.value}>
|
|
76
|
+
{option.label}
|
|
77
|
+
</SelectItem>
|
|
78
|
+
))}
|
|
79
|
+
</SelectContent>
|
|
80
|
+
</Select>
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
case 'number':
|
|
84
|
+
// Always hide spinner arrows for number filter inputs (cleaner UX)
|
|
85
|
+
return (
|
|
86
|
+
<Input
|
|
87
|
+
type="number"
|
|
88
|
+
value={columnFilterValue as string || ''}
|
|
89
|
+
onChange={(e) => handleFilterChange(e.target.value ? Number(e.target.value) : undefined)}
|
|
90
|
+
placeholder={placeholder || defaultPlaceholder}
|
|
91
|
+
className="h-8 datatable-number-no-spinners"
|
|
92
|
+
/>
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
case 'date':
|
|
96
|
+
return (
|
|
97
|
+
<Input
|
|
98
|
+
type="date"
|
|
99
|
+
value={columnFilterValue as string || ''}
|
|
100
|
+
onChange={(e) => handleFilterChange(e.target.value || undefined)}
|
|
101
|
+
placeholder={placeholder || defaultPlaceholder}
|
|
102
|
+
className="h-8"
|
|
103
|
+
/>
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
default: // text
|
|
107
|
+
return (
|
|
108
|
+
<Input
|
|
109
|
+
value={columnFilterValue as string || ''}
|
|
110
|
+
onChange={(e) => handleFilterChange(e.target.value || undefined)}
|
|
111
|
+
placeholder={placeholder || defaultPlaceholder}
|
|
112
|
+
className="h-8"
|
|
113
|
+
/>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
})()
|
|
128
117
|
);
|
|
129
118
|
}
|
|
@@ -5,9 +5,11 @@ import {
|
|
|
5
5
|
Select,
|
|
6
6
|
SelectContent,
|
|
7
7
|
SelectItem,
|
|
8
|
+
SelectGroup,
|
|
8
9
|
SelectSeparator,
|
|
9
10
|
SelectTrigger,
|
|
10
11
|
} from '../../Select/Select';
|
|
12
|
+
import { Label } from '../../Label/Label';
|
|
11
13
|
import { Checkbox } from '../../Checkbox/Checkbox';
|
|
12
14
|
import { Settings2, Eye, EyeOff } from 'lucide-react';
|
|
13
15
|
|
|
@@ -37,7 +39,7 @@ export function ColumnVisibilityDropdown<TData>({
|
|
|
37
39
|
);
|
|
38
40
|
|
|
39
41
|
return (
|
|
40
|
-
<Select className="w-52">
|
|
42
|
+
<Select className="w-52" showCheckmark={false}>
|
|
41
43
|
<SelectTrigger asChild>
|
|
42
44
|
<Button
|
|
43
45
|
variant="outline"
|
|
@@ -47,40 +49,40 @@ export function ColumnVisibilityDropdown<TData>({
|
|
|
47
49
|
</Button>
|
|
48
50
|
</SelectTrigger>
|
|
49
51
|
<SelectContent>
|
|
50
|
-
<
|
|
51
|
-
<
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
52
|
+
<SelectGroup className="flex flex-row gap-1 p-2">
|
|
53
|
+
<Button
|
|
54
|
+
variant="ghost"
|
|
55
|
+
size="sm"
|
|
56
|
+
className="h-7 px-2 text-xs flex-1"
|
|
57
|
+
onClick={() => {
|
|
58
|
+
toggleableColumns.forEach(column => {
|
|
59
|
+
if (!column.getIsVisible()) {
|
|
60
|
+
onColumnVisibilityChange(column.id, true);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}}
|
|
64
|
+
>
|
|
65
|
+
<Eye className="size-3 mr-1" />
|
|
66
|
+
Show All
|
|
67
|
+
</Button>
|
|
68
|
+
<Button
|
|
69
|
+
variant="ghost"
|
|
70
|
+
size="sm"
|
|
71
|
+
className="h-7 px-2 text-xs flex-1"
|
|
72
|
+
onClick={() => {
|
|
73
|
+
toggleableColumns.forEach(column => {
|
|
74
|
+
if (column.getIsVisible()) {
|
|
75
|
+
onColumnVisibilityChange(column.id, false);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
}}
|
|
79
|
+
>
|
|
80
|
+
<EyeOff className="size-3 mr-1" />
|
|
81
|
+
Hide All
|
|
82
|
+
</Button>
|
|
83
|
+
</SelectGroup>
|
|
84
|
+
<SelectSeparator />
|
|
85
|
+
<SelectGroup>
|
|
84
86
|
{toggleableColumns.map((column) => (
|
|
85
87
|
<SelectItem
|
|
86
88
|
key={column.id}
|
|
@@ -88,24 +90,24 @@ export function ColumnVisibilityDropdown<TData>({
|
|
|
88
90
|
className="flex items-center space-x-2 cursor-pointer px-3 py-2 text-sm hover:bg-sec-50"
|
|
89
91
|
onClick={(e) => e.preventDefault()}
|
|
90
92
|
>
|
|
93
|
+
<Label htmlFor={column.id}
|
|
94
|
+
// className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer"
|
|
95
|
+
>
|
|
91
96
|
<Checkbox
|
|
97
|
+
className="mr-2 align-middle"
|
|
92
98
|
id={column.id}
|
|
93
99
|
checked={column.getIsVisible()}
|
|
94
100
|
onCheckedChange={(checked) =>
|
|
95
101
|
onColumnVisibilityChange(column.id, !!checked)
|
|
96
102
|
}
|
|
97
103
|
/>
|
|
98
|
-
<label
|
|
99
|
-
htmlFor={column.id}
|
|
100
|
-
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70 cursor-pointer"
|
|
101
|
-
>
|
|
102
104
|
{typeof column.columnDef?.header === 'string'
|
|
103
105
|
? column.columnDef.header
|
|
104
106
|
: column.id}
|
|
105
|
-
</
|
|
107
|
+
</Label>
|
|
106
108
|
</SelectItem>
|
|
107
109
|
))}
|
|
108
|
-
</
|
|
110
|
+
</SelectGroup>
|
|
109
111
|
</SelectContent>
|
|
110
112
|
</Select>
|
|
111
113
|
);
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import React, { Component, ErrorInfo, ReactNode } from 'react';
|
|
11
11
|
import { Alert, AlertDescription, AlertTitle } from '../../Alert/Alert';
|
|
12
|
-
import { Button } from '../../Button/Button';
|
|
12
|
+
import { Button, ButtonGroup } from '../../Button/Button';
|
|
13
13
|
import { createLogger } from '../../../utils/core/logger';
|
|
14
14
|
// Icons removed to avoid test mocking issues
|
|
15
15
|
|
|
@@ -142,8 +142,7 @@ export class DataTableErrorBoundary extends Component<
|
|
|
142
142
|
|
|
143
143
|
// Default error UI
|
|
144
144
|
return (
|
|
145
|
-
|
|
146
|
-
<Alert variant="destructive" className="max-w-md">
|
|
145
|
+
<Alert variant="destructive">
|
|
147
146
|
<AlertTitle>DataTable Error</AlertTitle>
|
|
148
147
|
<AlertDescription className="mt-2">
|
|
149
148
|
<span>Something went wrong</span>
|
|
@@ -164,17 +163,16 @@ export class DataTableErrorBoundary extends Component<
|
|
|
164
163
|
</pre>
|
|
165
164
|
</details>
|
|
166
165
|
) : (
|
|
167
|
-
<
|
|
168
|
-
<
|
|
169
|
-
</
|
|
166
|
+
<Alert variant="destructive">
|
|
167
|
+
<AlertDescription>An unexpected error occurred</AlertDescription>
|
|
168
|
+
</Alert>
|
|
170
169
|
)}
|
|
171
|
-
<
|
|
170
|
+
<ButtonGroup>
|
|
172
171
|
{showRetryButton && retryCount < maxRetries && (
|
|
173
172
|
<Button
|
|
174
173
|
variant="outline"
|
|
175
174
|
size="sm"
|
|
176
|
-
onClick={this.handleRetry}
|
|
177
|
-
className="flex items-center gap-2"
|
|
175
|
+
onClick={this.handleRetry}
|
|
178
176
|
>
|
|
179
177
|
Retry ({retryCount + 1}/{maxRetries})
|
|
180
178
|
</Button>
|
|
@@ -186,9 +184,9 @@ export class DataTableErrorBoundary extends Component<
|
|
|
186
184
|
>
|
|
187
185
|
Reset
|
|
188
186
|
</Button>
|
|
189
|
-
|
|
187
|
+
</ButtonGroup>
|
|
190
188
|
</Alert>
|
|
191
|
-
|
|
189
|
+
|
|
192
190
|
);
|
|
193
191
|
}
|
|
194
192
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Edit, Trash
|
|
2
|
+
import { Edit, Trash } from 'lucide-react';
|
|
3
|
+
import { SortIndicator } from './SortIndicator';
|
|
3
4
|
import type { Table } from '@tanstack/react-table';
|
|
4
5
|
import { cn } from '../../../utils/core/cn';
|
|
5
6
|
import { Button } from '../../Button/Button';
|
|
@@ -7,7 +8,7 @@ import { DataTableToolbar } from './DataTableToolbar';
|
|
|
7
8
|
import { UnifiedTableBody } from './UnifiedTableBody';
|
|
8
9
|
import { PaginationControls, EnhancedPaginationControls } from './PaginationControls';
|
|
9
10
|
import { DataTableModals } from './DataTableModals';
|
|
10
|
-
import { announceSortChange } from '../utils/a11yUtils';
|
|
11
|
+
import { announceSortChange, getAriaSortState } from '../utils/a11yUtils';
|
|
11
12
|
import { exportToCSVWithTableRows } from '../utils/exportUtils';
|
|
12
13
|
import { getTableClasses } from '../styles';
|
|
13
14
|
import { toast } from '../../../hooks/useToast';
|
|
@@ -334,13 +335,7 @@ export function DataTableLayout<TData extends DataRecord>({
|
|
|
334
335
|
const isFirst = index === 0;
|
|
335
336
|
const isLast = index === visibleHeaders.length - 1;
|
|
336
337
|
const isSortable = header.column.getCanSort();
|
|
337
|
-
const ariaSort =
|
|
338
|
-
? header.column.getIsSorted() === 'asc'
|
|
339
|
-
? 'ascending'
|
|
340
|
-
: header.column.getIsSorted() === 'desc'
|
|
341
|
-
? 'descending'
|
|
342
|
-
: 'none'
|
|
343
|
-
: undefined;
|
|
338
|
+
const ariaSort = getAriaSortState(header.column);
|
|
344
339
|
const isRightAligned = header.column.columnDef.meta?.align === 'right';
|
|
345
340
|
|
|
346
341
|
const handleSortClick = (event: React.MouseEvent) => {
|
|
@@ -406,13 +401,7 @@ export function DataTableLayout<TData extends DataRecord>({
|
|
|
406
401
|
{typeof header.column.columnDef.header === 'function'
|
|
407
402
|
? header.column.columnDef.header(header.getContext())
|
|
408
403
|
: header.column.columnDef.header}
|
|
409
|
-
{header.column.getIsSorted()
|
|
410
|
-
<ChevronUp className="size-4" />
|
|
411
|
-
) : header.column.getIsSorted() === 'desc' ? (
|
|
412
|
-
<ChevronDown className="size-4" />
|
|
413
|
-
) : (
|
|
414
|
-
<ChevronsUpDown className="size-4" />
|
|
415
|
-
)}
|
|
404
|
+
<SortIndicator sortState={header.column.getIsSorted() || false} />
|
|
416
405
|
</Button>
|
|
417
406
|
) : typeof header.column.columnDef.header === 'function' ? (
|
|
418
407
|
header.column.columnDef.header(header.getContext())
|
|
@@ -379,19 +379,18 @@ export function EditableRow<TData extends DataRecord>({
|
|
|
379
379
|
const isSystemColumn = cell.column.id === 'select' || cell.column.id === 'actions';
|
|
380
380
|
|
|
381
381
|
return (
|
|
382
|
-
<td key={cell.id} role="cell">
|
|
383
|
-
|
|
384
|
-
{isSystemColumn ? (
|
|
382
|
+
<td key={cell.id} role="cell" className={cell.column.columnDef.meta?.align === 'right' ? 'text-right' : ''}>
|
|
383
|
+
{isSystemColumn ? (
|
|
385
384
|
// System columns: render their normal cell content (checkbox for select, buttons for actions)
|
|
386
385
|
cell.column.id === 'actions' ? (
|
|
387
|
-
|
|
388
|
-
<Button onClick={onSave} size="sm" variant="default" aria-label="Save changes">
|
|
386
|
+
<>
|
|
387
|
+
<Button onClick={onSave} size="sm" variant="default" aria-label="Save changes" className="mr-1">
|
|
389
388
|
<Check className="size-4" />
|
|
390
389
|
</Button>
|
|
391
390
|
<Button onClick={onCancel} size="sm" variant="outline" aria-label="Cancel editing">
|
|
392
391
|
<X className="size-4" />
|
|
393
392
|
</Button>
|
|
394
|
-
|
|
393
|
+
</>
|
|
395
394
|
) : (
|
|
396
395
|
// Select column: render the checkbox normally
|
|
397
396
|
flexRender(cell.column.columnDef.cell, cell.getContext())
|
|
@@ -455,7 +454,6 @@ export function EditableRow<TData extends DataRecord>({
|
|
|
455
454
|
);
|
|
456
455
|
})()
|
|
457
456
|
)}
|
|
458
|
-
</div>
|
|
459
457
|
</td>
|
|
460
458
|
);
|
|
461
459
|
})}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Database, Search, Plus, User } from 'lucide-react';
|
|
3
3
|
import { Button } from '../../Button/Button';
|
|
4
|
+
import { Alert, AlertTitle, AlertDescription } from '../../Alert/Alert';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Props for the EmptyState component.
|
|
@@ -38,10 +39,10 @@ export function EmptyState({
|
|
|
38
39
|
: "Get started by adding your first entry";
|
|
39
40
|
|
|
40
41
|
return (
|
|
41
|
-
<
|
|
42
|
+
<Alert
|
|
42
43
|
role="status"
|
|
43
44
|
aria-live="polite"
|
|
44
|
-
className="
|
|
45
|
+
className="grid place-items-center text-center max-w-lg mx-auto"
|
|
45
46
|
>
|
|
46
47
|
<Icon
|
|
47
48
|
role="img"
|
|
@@ -49,15 +50,15 @@ export function EmptyState({
|
|
|
49
50
|
className="size-12 text-muted-foreground mb-4"
|
|
50
51
|
data-testid={Icon === Database ? 'lucide-database' : Icon === User ? 'lucide-user' : 'custom-icon'}
|
|
51
52
|
/>
|
|
52
|
-
<
|
|
53
|
+
<AlertTitle className="text-lg font-semibold mb-2">
|
|
53
54
|
{title || defaultTitle}
|
|
54
|
-
</
|
|
55
|
-
<
|
|
55
|
+
</AlertTitle>
|
|
56
|
+
<AlertDescription className="text-sm text-muted-foreground mb-4 max-w-sm">
|
|
56
57
|
{description || defaultDescription}
|
|
57
|
-
</
|
|
58
|
+
</AlertDescription>
|
|
58
59
|
|
|
59
60
|
{(isFiltered && onClearFilters) || action ? (
|
|
60
|
-
|
|
61
|
+
<>
|
|
61
62
|
{isFiltered && onClearFilters && (
|
|
62
63
|
<Button variant="outline" onClick={onClearFilters}>
|
|
63
64
|
<Search className="size-4 mr-2" />
|
|
@@ -71,8 +72,8 @@ export function EmptyState({
|
|
|
71
72
|
{action.label}
|
|
72
73
|
</Button>
|
|
73
74
|
)}
|
|
74
|
-
|
|
75
|
+
</>
|
|
75
76
|
) : null}
|
|
76
|
-
</
|
|
77
|
+
</Alert>
|
|
77
78
|
);
|
|
78
79
|
}
|
|
@@ -121,7 +121,7 @@ export function FilterRow<TData>({ table, visibleColumns }: FilterRowProps<TData
|
|
|
121
121
|
return (
|
|
122
122
|
<td
|
|
123
123
|
key={header.id}
|
|
124
|
-
className="
|
|
124
|
+
className="p-1"
|
|
125
125
|
>
|
|
126
126
|
{canFilter ? (
|
|
127
127
|
<ColumnFilter
|
|
@@ -131,9 +131,7 @@ export function FilterRow<TData>({ table, visibleColumns }: FilterRowProps<TData
|
|
|
131
131
|
placeholder={`Filter ${getColumnHeaderText(column as unknown as Column<DataRecord, unknown>)}...`}
|
|
132
132
|
/>
|
|
133
133
|
) : (
|
|
134
|
-
|
|
135
|
-
No filter
|
|
136
|
-
</div>
|
|
134
|
+
<> </>
|
|
137
135
|
)}
|
|
138
136
|
</td>
|
|
139
137
|
);
|