@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
|
@@ -44,10 +44,11 @@ import { getTextContent } from "./utils/text";
|
|
|
44
44
|
* @returns The rendered select component
|
|
45
45
|
*/
|
|
46
46
|
export const Select = React.forwardRef<HTMLFieldSetElement, SelectProps & UseSelectStateProps>(
|
|
47
|
-
({
|
|
47
|
+
({
|
|
48
48
|
children,
|
|
49
49
|
className,
|
|
50
50
|
direction = 'down',
|
|
51
|
+
showCheckmark = true,
|
|
51
52
|
...selectProps
|
|
52
53
|
}, ref) => {
|
|
53
54
|
const internalRef = React.useRef<HTMLFieldSetElement>(null);
|
|
@@ -108,7 +109,7 @@ export const Select = React.forwardRef<HTMLFieldSetElement, SelectProps & UseSel
|
|
|
108
109
|
// Query the DOM for the SelectItem (li element) with matching value
|
|
109
110
|
// Must be a select-item, not the trigger which also has data-value
|
|
110
111
|
let selectItem = selectElement.querySelector(`[data-testid="select-item"][data-value="${state.value}"]`) as HTMLElement;
|
|
111
|
-
|
|
112
|
+
|
|
112
113
|
// If not found, try querying within content containers
|
|
113
114
|
if (!selectItem) {
|
|
114
115
|
const hiddenContent = selectElement.querySelector('[data-testid="select-content-hidden"]');
|
|
@@ -116,7 +117,7 @@ export const Select = React.forwardRef<HTMLFieldSetElement, SelectProps & UseSel
|
|
|
116
117
|
selectItem = hiddenContent.querySelector(`[data-testid="select-item"][data-value="${state.value}"]`) as HTMLElement;
|
|
117
118
|
}
|
|
118
119
|
}
|
|
119
|
-
|
|
120
|
+
|
|
120
121
|
// Try visible content as fallback
|
|
121
122
|
if (!selectItem) {
|
|
122
123
|
const visibleContent = selectElement.querySelector('[data-testid="select-content"]');
|
|
@@ -130,7 +131,7 @@ export const Select = React.forwardRef<HTMLFieldSetElement, SelectProps & UseSel
|
|
|
130
131
|
let textContent = selectItem.textContent?.trim() || '';
|
|
131
132
|
// Remove any check mark icons or other decorators (basic cleanup)
|
|
132
133
|
textContent = textContent.split('\n').map(line => line.trim()).filter(line => line).join(' ');
|
|
133
|
-
|
|
134
|
+
|
|
134
135
|
if (textContent) {
|
|
135
136
|
actions.setSelectedText(textContent);
|
|
136
137
|
// Also register it for future use
|
|
@@ -138,11 +139,11 @@ export const Select = React.forwardRef<HTMLFieldSetElement, SelectProps & UseSel
|
|
|
138
139
|
return;
|
|
139
140
|
}
|
|
140
141
|
}
|
|
141
|
-
|
|
142
|
+
|
|
142
143
|
// If not found and we have a content container, items might still be rendering
|
|
143
144
|
// Don't clear selectedText in this case - let the MutationObserver or retry effect handle it
|
|
144
|
-
const hasContentContainer = selectElement.querySelector('[data-testid="select-content-hidden"]') ||
|
|
145
|
-
|
|
145
|
+
const hasContentContainer = selectElement.querySelector('[data-testid="select-content-hidden"]') ||
|
|
146
|
+
selectElement.querySelector('[data-testid="select-content"]');
|
|
146
147
|
if (!hasContentContainer) {
|
|
147
148
|
// No content container means no items exist - clear selectedText
|
|
148
149
|
actions.setSelectedText('');
|
|
@@ -198,7 +199,8 @@ export const Select = React.forwardRef<HTMLFieldSetElement, SelectProps & UseSel
|
|
|
198
199
|
registerItem,
|
|
199
200
|
unregisterItem,
|
|
200
201
|
direction,
|
|
201
|
-
|
|
202
|
+
showCheckmark,
|
|
203
|
+
}), [state, actions, registerItem, unregisterItem, direction, showCheckmark]);
|
|
202
204
|
|
|
203
205
|
return (
|
|
204
206
|
<fieldset
|
|
@@ -235,14 +237,14 @@ export const SelectTrigger = React.forwardRef<HTMLButtonElement, SelectTriggerPr
|
|
|
235
237
|
|
|
236
238
|
// Use ref to store the latest handleClick to avoid re-creating the effect
|
|
237
239
|
const handleClickRef = React.useRef<(e: React.MouseEvent) => void>(undefined);
|
|
238
|
-
|
|
240
|
+
|
|
239
241
|
const handleClick = React.useCallback((e: React.MouseEvent) => {
|
|
240
242
|
if (disabled) {
|
|
241
243
|
e.preventDefault();
|
|
242
244
|
e.stopPropagation();
|
|
243
245
|
return;
|
|
244
246
|
}
|
|
245
|
-
|
|
247
|
+
|
|
246
248
|
e.preventDefault();
|
|
247
249
|
e.stopPropagation();
|
|
248
250
|
actions.setOpen(!open);
|
|
@@ -313,14 +315,14 @@ export const SelectTrigger = React.forwardRef<HTMLButtonElement, SelectTriggerPr
|
|
|
313
315
|
|
|
314
316
|
// Early return after all hooks have been called
|
|
315
317
|
if (asChild) {
|
|
316
|
-
const childElement = children as React.ReactElement<{ children?: React.ReactNode;
|
|
318
|
+
const childElement = children as React.ReactElement<{ children?: React.ReactNode;[key: string]: unknown }>;
|
|
317
319
|
const childChildren = React.Children.toArray(childElement.props.children);
|
|
318
320
|
const hasChevron = childChildren.some(child => {
|
|
319
321
|
if (React.isValidElement(child)) {
|
|
320
|
-
const childProps = child.props as { 'data-testid'?: string;
|
|
321
|
-
return child.type === ChevronDown ||
|
|
322
|
-
|
|
323
|
-
|
|
322
|
+
const childProps = child.props as { 'data-testid'?: string;[key: string]: unknown };
|
|
323
|
+
return child.type === ChevronDown ||
|
|
324
|
+
(child.type === 'svg' && childProps['data-testid'] === 'chevron-down') ||
|
|
325
|
+
(typeof child === 'object' && 'type' in child && typeof child.type === 'function' && child.type.name === 'ChevronDown');
|
|
324
326
|
}
|
|
325
327
|
return false;
|
|
326
328
|
});
|
|
@@ -337,12 +339,12 @@ export const SelectTrigger = React.forwardRef<HTMLButtonElement, SelectTriggerPr
|
|
|
337
339
|
className: mergedClassName,
|
|
338
340
|
children: hasChevron ? childChildren : [
|
|
339
341
|
...childChildren,
|
|
340
|
-
<ChevronDown
|
|
342
|
+
<ChevronDown
|
|
341
343
|
key="chevron-down"
|
|
342
344
|
className={cn(
|
|
343
345
|
"size-4 opacity-50 transition-transform pointer-events-none float-right",
|
|
344
346
|
open && "rotate-180"
|
|
345
|
-
)}
|
|
347
|
+
)}
|
|
346
348
|
/>
|
|
347
349
|
]
|
|
348
350
|
});
|
|
@@ -380,11 +382,11 @@ export const SelectTrigger = React.forwardRef<HTMLButtonElement, SelectTriggerPr
|
|
|
380
382
|
{...props}
|
|
381
383
|
>
|
|
382
384
|
{children}
|
|
383
|
-
<ChevronDown
|
|
385
|
+
<ChevronDown
|
|
384
386
|
className={cn(
|
|
385
387
|
"size-4 opacity-50 transition-transform pointer-events-none float-right",
|
|
386
388
|
open && "rotate-180"
|
|
387
|
-
)}
|
|
389
|
+
)}
|
|
388
390
|
/>
|
|
389
391
|
</Button>
|
|
390
392
|
);
|
|
@@ -409,8 +411,8 @@ export const SelectValue = React.forwardRef<HTMLSpanElement, SelectValueProps>(
|
|
|
409
411
|
const { selectedText } = useSelectContext();
|
|
410
412
|
|
|
411
413
|
return (
|
|
412
|
-
<span
|
|
413
|
-
ref={ref}
|
|
414
|
+
<span
|
|
415
|
+
ref={ref}
|
|
414
416
|
data-testid="select-value"
|
|
415
417
|
style={{ pointerEvents: 'none' }}
|
|
416
418
|
className="pointer-events-none"
|
|
@@ -435,9 +437,9 @@ SelectValue.displayName = "SelectValue";
|
|
|
435
437
|
* @returns The rendered select content
|
|
436
438
|
*/
|
|
437
439
|
export const SelectContent = React.forwardRef<HTMLUListElement, SelectContentProps>(
|
|
438
|
-
({
|
|
439
|
-
children,
|
|
440
|
-
className,
|
|
440
|
+
({
|
|
441
|
+
children,
|
|
442
|
+
className,
|
|
441
443
|
searchable = false,
|
|
442
444
|
searchPlaceholder = "Search...",
|
|
443
445
|
maxHeight = "max(20rem, 50vh)",
|
|
@@ -473,19 +475,19 @@ export const SelectContent = React.forwardRef<HTMLUListElement, SelectContentPro
|
|
|
473
475
|
}
|
|
474
476
|
|
|
475
477
|
const opensUpward = direction === 'up';
|
|
476
|
-
|
|
478
|
+
|
|
477
479
|
return (
|
|
478
480
|
<ul
|
|
479
481
|
ref={ref}
|
|
480
482
|
className={cn(
|
|
481
483
|
"absolute z-[99999] w-full overflow-y-auto border border-main-300 bg-main-50 shadow-lg",
|
|
482
484
|
"list-none p-0 m-0",
|
|
483
|
-
opensUpward
|
|
484
|
-
? "rounded-t-md border-b-0"
|
|
485
|
+
opensUpward
|
|
486
|
+
? "rounded-t-md border-b-0"
|
|
485
487
|
: "rounded-b-md border-t-0",
|
|
486
488
|
className
|
|
487
489
|
)}
|
|
488
|
-
style={{
|
|
490
|
+
style={{
|
|
489
491
|
[opensUpward ? 'bottom' : 'top']: '100%',
|
|
490
492
|
left: 0,
|
|
491
493
|
right: 0,
|
|
@@ -498,38 +500,36 @@ export const SelectContent = React.forwardRef<HTMLUListElement, SelectContentPro
|
|
|
498
500
|
role="listbox"
|
|
499
501
|
>
|
|
500
502
|
{searchable && (
|
|
501
|
-
<
|
|
502
|
-
<
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
</div>
|
|
532
|
-
</div>
|
|
503
|
+
<li className="relative p-2 border-b border-main-200 sticky top-0 bg-main-50 z-10" data-testid="select-search-item">
|
|
504
|
+
<Search className="absolute left-2 top-1/2 transform -translate-y-1/2 size-4 text-main-400" />
|
|
505
|
+
<input
|
|
506
|
+
ref={searchInputRef}
|
|
507
|
+
type="text"
|
|
508
|
+
placeholder={searchPlaceholder}
|
|
509
|
+
value={searchTerm}
|
|
510
|
+
onChange={(e) => setSearchTerm(e.target.value)}
|
|
511
|
+
onKeyDown={(e) => {
|
|
512
|
+
if (e.key === 'Escape') {
|
|
513
|
+
e.preventDefault();
|
|
514
|
+
setSearchTerm('');
|
|
515
|
+
}
|
|
516
|
+
}}
|
|
517
|
+
className="w-full pl-8 pr-8 py-1 text-sm border border-main-200 rounded focus:outline-none focus:ring-2 focus:ring-main-500"
|
|
518
|
+
data-testid="select-search-input"
|
|
519
|
+
aria-label="Search options"
|
|
520
|
+
/>
|
|
521
|
+
{searchTerm && (
|
|
522
|
+
<button
|
|
523
|
+
type="button"
|
|
524
|
+
onClick={() => setSearchTerm('')}
|
|
525
|
+
className="absolute right-2 top-1/2 transform -translate-y-1/2 text-main-400 hover:text-main-600"
|
|
526
|
+
data-testid="select-clear-search"
|
|
527
|
+
aria-label="Clear search"
|
|
528
|
+
>
|
|
529
|
+
<X className="size-4" />
|
|
530
|
+
</button>
|
|
531
|
+
)}
|
|
532
|
+
</li>
|
|
533
533
|
)}
|
|
534
534
|
{filteredChildren}
|
|
535
535
|
</ul>
|
|
@@ -551,9 +551,12 @@ SelectContent.displayName = "SelectContent";
|
|
|
551
551
|
* @returns The rendered select item
|
|
552
552
|
*/
|
|
553
553
|
export const SelectItem = React.forwardRef<HTMLLIElement, SelectItemProps>(
|
|
554
|
-
({ value, children, disabled = false, className, onClick }, ref) => {
|
|
555
|
-
const { value: selectedValue, actions, registerItem, unregisterItem } = useSelectContext();
|
|
554
|
+
({ value, children, disabled = false, className, onClick, showCheckmark: propShowCheckmark }, ref) => {
|
|
555
|
+
const { value: selectedValue, actions, registerItem, unregisterItem, showCheckmark: contextShowCheckmark = true } = useSelectContext();
|
|
556
556
|
const isSelected = selectedValue === value;
|
|
557
|
+
|
|
558
|
+
// Use prop if provided, otherwise use context value, default to true
|
|
559
|
+
const showCheckmark = propShowCheckmark ?? contextShowCheckmark;
|
|
557
560
|
|
|
558
561
|
const itemText = getTextContent(children);
|
|
559
562
|
|
|
@@ -569,7 +572,7 @@ export const SelectItem = React.forwardRef<HTMLLIElement, SelectItemProps>(
|
|
|
569
572
|
}
|
|
570
573
|
};
|
|
571
574
|
}, [value, itemText, registerItem, unregisterItem]);
|
|
572
|
-
|
|
575
|
+
|
|
573
576
|
const handleMouseDown = (e: React.MouseEvent) => {
|
|
574
577
|
if (!disabled) {
|
|
575
578
|
// CRITICAL FIX: Prevent event from bubbling to avoid interference with table row interactions
|
|
@@ -636,7 +639,7 @@ export const SelectItem = React.forwardRef<HTMLLIElement, SelectItemProps>(
|
|
|
636
639
|
tabIndex={disabled ? -1 : 0}
|
|
637
640
|
>
|
|
638
641
|
{children}
|
|
639
|
-
{isSelected && (
|
|
642
|
+
{isSelected && showCheckmark && (
|
|
640
643
|
<Check className="absolute right-2 size-4 flex-shrink-0 mt-0.5" />
|
|
641
644
|
)}
|
|
642
645
|
</li>
|
|
@@ -651,18 +654,24 @@ SelectItem.displayName = "SelectItem";
|
|
|
651
654
|
|
|
652
655
|
/**
|
|
653
656
|
* Select group component.
|
|
654
|
-
* Groups related select items together.
|
|
657
|
+
* Groups related select items together using a nested list structure.
|
|
655
658
|
*
|
|
656
659
|
* @param props - Select group configuration
|
|
657
|
-
* @param ref - Forwarded ref to the
|
|
660
|
+
* @param ref - Forwarded ref to the ul element
|
|
658
661
|
* @returns The rendered select group
|
|
659
662
|
*/
|
|
660
|
-
export const SelectGroup = React.forwardRef<
|
|
663
|
+
export const SelectGroup = React.forwardRef<HTMLUListElement, { children: React.ReactNode; className?: string }>(
|
|
661
664
|
({ children, className }, ref) => {
|
|
662
665
|
return (
|
|
663
|
-
<
|
|
664
|
-
|
|
665
|
-
|
|
666
|
+
<li className="list-none" data-testid="select-group-wrapper">
|
|
667
|
+
<ul
|
|
668
|
+
ref={ref}
|
|
669
|
+
className={cn("p-1 list-none m-0", className)}
|
|
670
|
+
data-testid="select-group"
|
|
671
|
+
>
|
|
672
|
+
{children}
|
|
673
|
+
</ul>
|
|
674
|
+
</li>
|
|
666
675
|
);
|
|
667
676
|
}
|
|
668
677
|
);
|
|
@@ -673,15 +682,15 @@ SelectGroup.displayName = "SelectGroup";
|
|
|
673
682
|
* Provides a label for a group of select items.
|
|
674
683
|
*
|
|
675
684
|
* @param props - Select label configuration
|
|
676
|
-
* @param ref - Forwarded ref to the
|
|
685
|
+
* @param ref - Forwarded ref to the li element
|
|
677
686
|
* @returns The rendered select label
|
|
678
687
|
*/
|
|
679
|
-
export const SelectLabel = React.forwardRef<
|
|
688
|
+
export const SelectLabel = React.forwardRef<HTMLLIElement, { children: React.ReactNode; className?: string }>(
|
|
680
689
|
({ children, className }, ref) => {
|
|
681
690
|
return (
|
|
682
|
-
<
|
|
691
|
+
<li ref={ref} className={cn("px-2 py-1.5 text-sm font-semibold", className)} data-testid="select-label">
|
|
683
692
|
{children}
|
|
684
|
-
</
|
|
693
|
+
</li>
|
|
685
694
|
);
|
|
686
695
|
}
|
|
687
696
|
);
|
|
@@ -692,13 +701,13 @@ SelectLabel.displayName = "SelectLabel";
|
|
|
692
701
|
* Provides visual separation between groups of select items.
|
|
693
702
|
*
|
|
694
703
|
* @param props - Select separator configuration
|
|
695
|
-
* @param ref - Forwarded ref to the
|
|
704
|
+
* @param ref - Forwarded ref to the hr element
|
|
696
705
|
* @returns The rendered select separator
|
|
697
706
|
*/
|
|
698
|
-
export const SelectSeparator = React.forwardRef<
|
|
707
|
+
export const SelectSeparator = React.forwardRef<HTMLHRElement, { className?: string }>(
|
|
699
708
|
({ className }, ref) => {
|
|
700
709
|
return (
|
|
701
|
-
<
|
|
710
|
+
<hr
|
|
702
711
|
ref={ref}
|
|
703
712
|
className={cn("my-1 h-px bg-sec-200", className)}
|
|
704
713
|
data-testid="select-separator"
|
|
@@ -67,6 +67,7 @@ export interface SelectContextValue extends SelectState {
|
|
|
67
67
|
registerItem?: (value: string, text: string) => void;
|
|
68
68
|
unregisterItem?: (value: string) => void;
|
|
69
69
|
direction?: SelectDirection;
|
|
70
|
+
showCheckmark?: boolean;
|
|
70
71
|
}
|
|
71
72
|
|
|
72
73
|
/**
|
|
@@ -80,6 +81,7 @@ export interface SelectProps
|
|
|
80
81
|
children: React.ReactNode;
|
|
81
82
|
className?: string;
|
|
82
83
|
direction?: SelectDirection;
|
|
84
|
+
showCheckmark?: boolean;
|
|
83
85
|
}
|
|
84
86
|
|
|
85
87
|
/**
|
|
@@ -120,4 +122,5 @@ export interface SelectItemProps {
|
|
|
120
122
|
disabled?: boolean;
|
|
121
123
|
className?: string;
|
|
122
124
|
onClick?: (e: React.MouseEvent) => void;
|
|
125
|
+
showCheckmark?: boolean;
|
|
123
126
|
}
|
|
@@ -137,7 +137,7 @@ describe('Service Hooks', () => {
|
|
|
137
137
|
it('should return AuthService from context', () => {
|
|
138
138
|
const mockService = createMockService('auth');
|
|
139
139
|
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
140
|
-
<
|
|
140
|
+
<section>{children}</section>
|
|
141
141
|
);
|
|
142
142
|
|
|
143
143
|
// Mock the context with proper structure
|
|
@@ -150,7 +150,7 @@ describe('Service Hooks', () => {
|
|
|
150
150
|
|
|
151
151
|
it('should throw error when used outside provider', () => {
|
|
152
152
|
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
153
|
-
<
|
|
153
|
+
<section>{children}</section>
|
|
154
154
|
);
|
|
155
155
|
|
|
156
156
|
// Mock the context to return null
|
|
@@ -164,7 +164,7 @@ describe('Service Hooks', () => {
|
|
|
164
164
|
it('should subscribe to service changes', () => {
|
|
165
165
|
const mockService = createMockService('auth');
|
|
166
166
|
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
167
|
-
<
|
|
167
|
+
<section>{children}</section>
|
|
168
168
|
);
|
|
169
169
|
|
|
170
170
|
mockUseContext.mockReturnValue({ authService: mockService });
|
|
@@ -179,7 +179,7 @@ describe('Service Hooks', () => {
|
|
|
179
179
|
it('should return OrganisationService from context', () => {
|
|
180
180
|
const mockService = createMockService('organisation');
|
|
181
181
|
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
182
|
-
<
|
|
182
|
+
<section>{children}</section>
|
|
183
183
|
);
|
|
184
184
|
|
|
185
185
|
mockUseContext.mockReturnValue({ organisationService: mockService });
|
|
@@ -191,7 +191,7 @@ describe('Service Hooks', () => {
|
|
|
191
191
|
|
|
192
192
|
it('should throw error when used outside provider', () => {
|
|
193
193
|
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
194
|
-
<
|
|
194
|
+
<section>{children}</section>
|
|
195
195
|
);
|
|
196
196
|
|
|
197
197
|
mockUseContext.mockReturnValue(null);
|
|
@@ -206,7 +206,7 @@ describe('Service Hooks', () => {
|
|
|
206
206
|
it('should return EventService from context', () => {
|
|
207
207
|
const mockService = createMockService('event');
|
|
208
208
|
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
209
|
-
<
|
|
209
|
+
<section>{children}</section>
|
|
210
210
|
);
|
|
211
211
|
|
|
212
212
|
mockUseContext.mockReturnValue({ eventService: mockService });
|
|
@@ -218,7 +218,7 @@ describe('Service Hooks', () => {
|
|
|
218
218
|
|
|
219
219
|
it('should throw error when used outside provider', () => {
|
|
220
220
|
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
221
|
-
<
|
|
221
|
+
<section>{children}</section>
|
|
222
222
|
);
|
|
223
223
|
|
|
224
224
|
mockUseContext.mockReturnValue(null);
|
|
@@ -233,7 +233,7 @@ describe('Service Hooks', () => {
|
|
|
233
233
|
it('should return InactivityService from context', () => {
|
|
234
234
|
const mockService = createMockService('inactivity');
|
|
235
235
|
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
236
|
-
<
|
|
236
|
+
<section>{children}</section>
|
|
237
237
|
);
|
|
238
238
|
|
|
239
239
|
vi.spyOn(React, 'useContext').mockReturnValue({ inactivityService: mockService });
|
|
@@ -245,7 +245,7 @@ describe('Service Hooks', () => {
|
|
|
245
245
|
|
|
246
246
|
it('should throw error when used outside provider', () => {
|
|
247
247
|
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
248
|
-
<
|
|
248
|
+
<section>{children}</section>
|
|
249
249
|
);
|
|
250
250
|
|
|
251
251
|
mockUseContext.mockReturnValue(null);
|
|
@@ -276,7 +276,7 @@ describe('Service Hooks', () => {
|
|
|
276
276
|
};
|
|
277
277
|
|
|
278
278
|
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
279
|
-
<
|
|
279
|
+
<section>{children}</section>
|
|
280
280
|
);
|
|
281
281
|
|
|
282
282
|
mockUseContext.mockReturnValue({ authService: mockService });
|
|
@@ -327,7 +327,7 @@ describe('Service Hooks', () => {
|
|
|
327
327
|
};
|
|
328
328
|
|
|
329
329
|
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
330
|
-
<
|
|
330
|
+
<section>{children}</section>
|
|
331
331
|
);
|
|
332
332
|
|
|
333
333
|
mockUseContext.mockReturnValue({ rbacService: mockService });
|
|
@@ -382,7 +382,7 @@ describe('Service Hooks', () => {
|
|
|
382
382
|
};
|
|
383
383
|
|
|
384
384
|
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
385
|
-
<
|
|
385
|
+
<section>{children}</section>
|
|
386
386
|
);
|
|
387
387
|
|
|
388
388
|
mockUseContext.mockReturnValue({ organisationService: mockService });
|
|
@@ -434,7 +434,7 @@ describe('Service Hooks', () => {
|
|
|
434
434
|
};
|
|
435
435
|
|
|
436
436
|
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
437
|
-
<
|
|
437
|
+
<section>{children}</section>
|
|
438
438
|
);
|
|
439
439
|
|
|
440
440
|
mockUseContext.mockReturnValue({ eventService: mockService });
|
|
@@ -527,7 +527,7 @@ describe('Service Hooks', () => {
|
|
|
527
527
|
};
|
|
528
528
|
|
|
529
529
|
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
530
|
-
<
|
|
530
|
+
<section>{children}</section>
|
|
531
531
|
);
|
|
532
532
|
|
|
533
533
|
// Test individual hooks
|
|
@@ -570,7 +570,7 @@ describe('Service Hooks', () => {
|
|
|
570
570
|
};
|
|
571
571
|
|
|
572
572
|
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
573
|
-
<
|
|
573
|
+
<section>{children}</section>
|
|
574
574
|
);
|
|
575
575
|
|
|
576
576
|
mockUseContext.mockReturnValue({ authService: mockService });
|
|
@@ -600,7 +600,7 @@ describe('Service Hooks', () => {
|
|
|
600
600
|
};
|
|
601
601
|
|
|
602
602
|
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
603
|
-
<
|
|
603
|
+
<section>{children}</section>
|
|
604
604
|
);
|
|
605
605
|
|
|
606
606
|
mockUseContext.mockReturnValue({ authService: mockService });
|