@jmruthers/pace-core 0.5.193 → 0.6.2
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 +62 -0
- package/README.md +7 -1
- package/cursor-rules/00-pace-core-compliance.mdc +299 -0
- package/cursor-rules/01-standards-compliance.mdc +244 -0
- package/cursor-rules/02-project-structure.mdc +200 -0
- package/cursor-rules/03-solid-principles.mdc +222 -0
- package/cursor-rules/04-testing-standards.mdc +268 -0
- package/cursor-rules/05-bug-reports-and-features.mdc +246 -0
- package/cursor-rules/06-code-quality.mdc +309 -0
- package/cursor-rules/07-tech-stack-compliance.mdc +214 -0
- package/cursor-rules/08-markup-quality.mdc +452 -0
- package/cursor-rules/CHANGELOG.md +119 -0
- package/cursor-rules/README.md +192 -0
- package/dist/{AuthService-DjnJHDtC.d.ts → AuthService-BPvc3Ka0.d.ts} +54 -0
- package/dist/{DataTable-Be6dH_dR.d.ts → DataTable-BMRU8a1j.d.ts} +34 -2
- package/dist/{DataTable-5FU7IESH.js → DataTable-TPTKCX4D.js} +10 -9
- package/dist/{PublicPageProvider-C0Sm_e5k.d.ts → PublicPageProvider-DC6kCaqf.d.ts} +385 -261
- package/dist/{UnifiedAuthProvider-RGJTDE2C.js → UnifiedAuthProvider-CH6Z342H.js} +3 -3
- package/dist/{UnifiedAuthProvider-185Ih4dj.d.ts → UnifiedAuthProvider-CVcTjx-d.d.ts} +29 -0
- package/dist/{api-N774RPUA.js → api-MVVQZLJI.js} +2 -2
- package/dist/{chunk-KNC55RTG.js → chunk-24UVZUZG.js} +90 -54
- package/dist/chunk-24UVZUZG.js.map +1 -0
- package/dist/{chunk-HWIIPPNI.js → chunk-2UOI2FG5.js} +20 -20
- package/dist/chunk-2UOI2FG5.js.map +1 -0
- package/dist/{chunk-E3SPN4VZ 5.js → chunk-3XC4CPTD.js} +4345 -3986
- package/dist/chunk-3XC4CPTD.js.map +1 -0
- package/dist/{chunk-7EQTDTTJ.js → chunk-6J4GEEJR.js} +172 -45
- package/dist/chunk-6J4GEEJR.js.map +1 -0
- package/dist/{chunk-6C4YBBJM 5.js → chunk-6SOIHG6Z.js} +1 -1
- package/dist/chunk-6SOIHG6Z.js.map +1 -0
- package/dist/{chunk-7FLMSG37.js → chunk-EHMR7VYL.js} +25 -25
- package/dist/chunk-EHMR7VYL.js.map +1 -0
- package/dist/{chunk-I7PSE6JW.js → chunk-F2IMUDXZ.js} +2 -75
- package/dist/chunk-F2IMUDXZ.js.map +1 -0
- package/dist/{chunk-QWWZ5CAQ.js → chunk-FFQEQTNW.js} +7 -9
- package/dist/chunk-FFQEQTNW.js.map +1 -0
- package/dist/chunk-FMUCXFII.js +76 -0
- package/dist/chunk-FMUCXFII.js.map +1 -0
- package/dist/{chunk-HW3OVDUF.js → chunk-J36DSWQK.js} +1 -1
- package/dist/{chunk-HW3OVDUF.js.map → chunk-J36DSWQK.js.map} +1 -1
- package/dist/{chunk-SQGMNID3.js → chunk-L4OXEN46.js} +4 -5
- package/dist/chunk-L4OXEN46.js.map +1 -0
- package/dist/{chunk-R77UEZ4E 3.js → chunk-M43Y4SSO.js} +1 -1
- package/dist/chunk-M43Y4SSO.js.map +1 -0
- package/dist/{chunk-IIELH4DL.js → chunk-MMZ7JXPU.js} +60 -223
- package/dist/chunk-MMZ7JXPU.js.map +1 -0
- package/dist/{chunk-NOAYCWCX 5.js → chunk-NECFR5MM.js} +394 -312
- package/dist/chunk-NECFR5MM.js.map +1 -0
- package/dist/{chunk-BC4IJKSL.js → chunk-SFZUDBL5.js} +40 -4
- package/dist/chunk-SFZUDBL5.js.map +1 -0
- package/dist/{chunk-XNXXZ43G.js → chunk-XWQCNGTQ.js} +748 -364
- package/dist/chunk-XWQCNGTQ.js.map +1 -0
- package/dist/components.d.ts +6 -6
- package/dist/components.js +15 -12
- package/dist/components.js.map +1 -1
- package/dist/{functions-D_kgHktt.d.ts → functions-DHebl8-F.d.ts} +1 -1
- package/dist/hooks.d.ts +59 -126
- package/dist/hooks.js +19 -28
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +63 -16
- package/dist/index.js +23 -24
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +21 -3
- package/dist/providers.js +2 -2
- package/dist/rbac/index.d.ts +146 -115
- package/dist/rbac/index.js +8 -11
- package/dist/theming/runtime.d.ts +1 -13
- package/dist/theming/runtime.js +1 -1
- package/dist/{timezone-_pgH8qrY.d.ts → timezone-CHhWg6b4.d.ts} +3 -10
- package/dist/{types-UU913iLA.d.ts → types-BeoeWV5I.d.ts} +8 -0
- package/dist/{types-CEpcvwwF.d.ts → types-CkbwOr4Y.d.ts} +6 -0
- package/dist/types.d.ts +2 -2
- package/dist/{usePublicRouteParams-TZe0gy-4.d.ts → usePublicRouteParams-1oMokgLF.d.ts} +34 -4
- package/dist/{useToast-C8gR5ir4.d.ts → useToast-AyaT-x7p.d.ts} +2 -2
- package/dist/utils.d.ts +4 -5
- package/dist/utils.js +15 -15
- package/dist/utils.js.map +1 -1
- package/docs/api/README.md +7 -1
- package/docs/api/classes/ColumnFactory.md +8 -8
- package/docs/api/classes/InvalidScopeError.md +4 -4
- package/docs/api/classes/Logger.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +4 -4
- package/docs/api/classes/OrganisationContextRequiredError.md +4 -4
- package/docs/api/classes/PermissionDeniedError.md +4 -4
- package/docs/api/classes/RBACAuditManager.md +1 -1
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +1 -1
- package/docs/api/classes/RBACError.md +4 -4
- package/docs/api/classes/RBACNotInitializedError.md +4 -4
- package/docs/api/classes/SecureSupabaseClient.md +18 -15
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/enums/LogLevel.md +1 -1
- package/docs/api/enums/RBACErrorCode.md +1 -1
- package/docs/api/enums/RPCFunction.md +1 -1
- package/docs/api/interfaces/AddressFieldProps.md +1 -1
- package/docs/api/interfaces/AddressFieldRef.md +1 -1
- package/docs/api/interfaces/AggregateConfig.md +4 -4
- package/docs/api/interfaces/AutocompleteOptions.md +1 -1
- package/docs/api/interfaces/AvatarProps.md +1 -1
- package/docs/api/interfaces/BadgeProps.md +9 -2
- package/docs/api/interfaces/ButtonProps.md +7 -4
- package/docs/api/interfaces/CalendarProps.md +8 -5
- package/docs/api/interfaces/CardProps.md +8 -5
- package/docs/api/interfaces/ColorPalette.md +1 -1
- package/docs/api/interfaces/ColorShade.md +1 -1
- package/docs/api/interfaces/ComplianceResult.md +1 -1
- package/docs/api/interfaces/DataAccessRecord.md +9 -9
- package/docs/api/interfaces/DataRecord.md +1 -1
- package/docs/api/interfaces/DataTableAction.md +24 -21
- package/docs/api/interfaces/DataTableColumn.md +31 -31
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +7 -7
- package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
- package/docs/api/interfaces/DatabaseIssue.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +5 -5
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/ErrorBoundaryProps.md +147 -0
- package/docs/api/interfaces/ErrorBoundaryProviderProps.md +36 -0
- package/docs/api/interfaces/ErrorBoundaryState.md +75 -0
- package/docs/api/interfaces/EventAppRoleData.md +1 -1
- package/docs/api/interfaces/ExportColumn.md +1 -1
- package/docs/api/interfaces/ExportOptions.md +8 -8
- package/docs/api/interfaces/FileDisplayProps.md +1 -1
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +1 -1
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +1 -1
- package/docs/api/interfaces/FileUploadProps.md +26 -23
- package/docs/api/interfaces/FooterProps.md +10 -8
- package/docs/api/interfaces/FormFieldProps.md +10 -10
- package/docs/api/interfaces/FormProps.md +1 -1
- package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
- package/docs/api/interfaces/InputProps.md +7 -4
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoggerConfig.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +14 -11
- package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
- package/docs/api/interfaces/NavigationContextType.md +1 -1
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +11 -11
- package/docs/api/interfaces/NavigationMenuProps.md +15 -15
- package/docs/api/interfaces/NavigationProviderProps.md +1 -1
- package/docs/api/interfaces/Organisation.md +1 -1
- package/docs/api/interfaces/OrganisationContextType.md +1 -1
- package/docs/api/interfaces/OrganisationMembership.md +1 -1
- package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
- package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
- package/docs/api/interfaces/PaceAppLayoutProps.md +30 -27
- package/docs/api/interfaces/PaceLoginPageProps.md +6 -4
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/ParsedAddress.md +1 -1
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProgressProps.md +1 -1
- package/docs/api/interfaces/ProtectedRouteProps.md +7 -26
- package/docs/api/interfaces/PublicPageFooterProps.md +9 -9
- package/docs/api/interfaces/PublicPageHeaderProps.md +10 -10
- package/docs/api/interfaces/PublicPageLayoutProps.md +7 -20
- package/docs/api/interfaces/QuickFix.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
- package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
- package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
- package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
- package/docs/api/interfaces/RBACConfig.md +1 -1
- package/docs/api/interfaces/RBACContext.md +1 -1
- package/docs/api/interfaces/RBACLogger.md +1 -1
- package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPerformanceMetrics.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
- package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
- package/docs/api/interfaces/RBACResult.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
- package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
- package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
- package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
- package/docs/api/interfaces/RBACRolesListParams.md +1 -1
- package/docs/api/interfaces/RBACRolesListResult.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
- package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
- package/docs/api/interfaces/ResourcePermissions.md +1 -1
- package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/RuntimeComplianceResult.md +1 -1
- package/docs/api/interfaces/SecureDataContextType.md +9 -9
- package/docs/api/interfaces/SecureDataProviderProps.md +8 -8
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +3 -3
- package/docs/api/interfaces/SetupIssue.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +1 -1
- package/docs/api/interfaces/StorageFileInfo.md +1 -1
- package/docs/api/interfaces/StorageFileMetadata.md +1 -1
- package/docs/api/interfaces/StorageListOptions.md +1 -1
- package/docs/api/interfaces/StorageListResult.md +1 -1
- package/docs/api/interfaces/StorageUploadOptions.md +1 -1
- package/docs/api/interfaces/StorageUploadResult.md +1 -1
- package/docs/api/interfaces/StorageUrlOptions.md +1 -1
- package/docs/api/interfaces/StyleImport.md +1 -1
- package/docs/api/interfaces/SwitchProps.md +1 -1
- package/docs/api/interfaces/TabsContentProps.md +1 -1
- package/docs/api/interfaces/TabsListProps.md +1 -1
- package/docs/api/interfaces/TabsProps.md +1 -1
- package/docs/api/interfaces/TabsTriggerProps.md +3 -3
- package/docs/api/interfaces/TextareaProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +4 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +58 -55
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +15 -13
- package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
- package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +11 -9
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +8 -8
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +6 -6
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +9 -6
- package/docs/api/interfaces/UsePublicEventOptions.md +3 -3
- package/docs/api/interfaces/UsePublicEventReturn.md +8 -5
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +4 -4
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +12 -9
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +10 -7
- package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +14 -11
- package/docs/api/interfaces/UserMenuProps.md +8 -6
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +575 -634
- package/docs/architecture/database-schema-requirements.md +161 -0
- package/docs/core-concepts/rbac-system.md +3 -3
- package/docs/documentation-index.md +2 -4
- package/docs/getting-started/cursor-rules.md +263 -0
- package/docs/getting-started/installation-guide.md +6 -1
- package/docs/getting-started/quick-start.md +6 -1
- package/docs/migration/DOCUMENTATION_STRUCTURE.md +441 -0
- package/docs/migration/MIGRATION_GUIDE.md +6 -28
- package/docs/migration/README.md +52 -6
- package/docs/migration/V0.5.190_TO_V0.6.1_MIGRATION.md +1153 -0
- package/docs/migration/V0.6.0_REACT_19_MIGRATION.md +227 -0
- package/docs/migration/database-changes-december-2025.md +3 -3
- package/docs/rbac/event-based-apps.md +1 -1
- package/docs/rbac/getting-started.md +1 -1
- package/docs/rbac/quick-start.md +1 -1
- package/docs/standards/README.md +40 -0
- package/docs/troubleshooting/migration.md +4 -4
- package/examples/PublicPages/PublicEventPage.tsx +1 -1
- package/package.json +12 -6
- package/scripts/audit/core/checks/accessibility.cjs +197 -0
- package/scripts/audit/core/checks/api-usage.cjs +191 -0
- package/scripts/audit/core/checks/bundle.cjs +142 -0
- package/scripts/{check-pace-core-compliance.cjs → audit/core/checks/compliance.cjs} +737 -691
- package/scripts/audit/core/checks/config.cjs +54 -0
- package/scripts/audit/core/checks/coverage.cjs +84 -0
- package/scripts/audit/core/checks/dependencies.cjs +454 -0
- package/scripts/audit/core/checks/documentation.cjs +203 -0
- package/scripts/audit/core/checks/environment.cjs +128 -0
- package/scripts/audit/core/checks/error-handling.cjs +299 -0
- package/scripts/audit/core/checks/forms.cjs +172 -0
- package/scripts/audit/core/checks/heuristics.cjs +68 -0
- package/scripts/audit/core/checks/hooks.cjs +334 -0
- package/scripts/audit/core/checks/imports.cjs +244 -0
- package/scripts/audit/core/checks/performance.cjs +325 -0
- package/scripts/audit/core/checks/routes.cjs +117 -0
- package/scripts/audit/core/checks/state.cjs +130 -0
- package/scripts/audit/core/checks/structure.cjs +65 -0
- package/scripts/audit/core/checks/style.cjs +584 -0
- package/scripts/audit/core/checks/testing.cjs +122 -0
- package/scripts/audit/core/checks/typescript.cjs +61 -0
- package/scripts/audit/core/scanner.cjs +199 -0
- package/scripts/audit/core/utils.cjs +137 -0
- package/scripts/audit/index.cjs +223 -0
- package/scripts/audit/reporters/console.cjs +151 -0
- package/scripts/audit/reporters/json.cjs +54 -0
- package/scripts/audit/reporters/markdown.cjs +124 -0
- package/scripts/audit-consuming-app.cjs +86 -0
- package/scripts/build-docs/build-decision.js +240 -0
- package/scripts/build-docs/cache-utils.js +105 -0
- package/scripts/build-docs/content-normalization.js +150 -0
- package/scripts/build-docs/file-utils.js +105 -0
- package/scripts/build-docs/git-utils.js +86 -0
- package/scripts/build-docs/hash-utils.js +116 -0
- package/scripts/build-docs/typedoc-runner.js +220 -0
- package/scripts/build-docs-incremental.js +77 -913
- package/scripts/install-cursor-rules.cjs +236 -0
- package/scripts/utils/command-runner.js +16 -11
- package/scripts/validate-formats.js +61 -56
- package/scripts/validate-master.js +74 -69
- package/scripts/validate-pre-publish.js +70 -65
- package/src/__tests__/helpers/test-providers.tsx +1 -1
- package/src/__tests__/helpers/test-utils.tsx +1 -1
- package/src/__tests__/hooks/usePermissions.test.ts +2 -2
- package/src/components/Alert/Alert.test.tsx +12 -18
- package/src/components/Alert/Alert.tsx +5 -7
- package/src/components/Avatar/Avatar.test.tsx +4 -4
- package/src/components/Badge/Badge.tsx +16 -4
- package/src/components/Button/Button.tsx +27 -4
- package/src/components/Calendar/Calendar.tsx +9 -3
- package/src/components/Card/Card.tsx +4 -0
- package/src/components/Checkbox/Checkbox.test.tsx +12 -12
- package/src/components/Checkbox/Checkbox.tsx +2 -2
- package/src/components/DataTable/DataTable.test.tsx +57 -93
- package/src/components/DataTable/DataTable.tsx +40 -6
- package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +5 -6
- package/src/components/DataTable/__tests__/pagination.modes.test.tsx +29 -7
- package/src/components/DataTable/__tests__/ssr.strict-mode.test.tsx +12 -12
- package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +2 -3
- package/src/components/DataTable/components/AccessDeniedPage.tsx +17 -26
- package/src/components/DataTable/components/ActionButtons.tsx +10 -7
- package/src/components/DataTable/components/BulkOperationsDropdown.tsx +2 -2
- package/src/components/DataTable/components/ColumnFilter.tsx +10 -0
- package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +12 -0
- package/src/components/DataTable/components/DataTableBody.tsx +8 -0
- package/src/components/DataTable/components/DataTableCore.tsx +200 -561
- package/src/components/DataTable/components/DataTableErrorBoundary.tsx +11 -0
- package/src/components/DataTable/components/DataTableLayout.tsx +559 -0
- package/src/components/DataTable/components/DataTableModals.tsx +9 -1
- package/src/components/DataTable/components/DataTableToolbar.tsx +8 -0
- package/src/components/DataTable/components/DraggableColumnHeader.tsx +12 -0
- package/src/components/DataTable/components/EditFields.tsx +307 -0
- package/src/components/DataTable/components/EditableRow.tsx +9 -1
- package/src/components/DataTable/components/EmptyState.tsx +10 -0
- package/src/components/DataTable/components/FilterRow.tsx +12 -0
- package/src/components/DataTable/components/GroupHeader.tsx +12 -0
- package/src/components/DataTable/components/GroupingDropdown.tsx +12 -0
- package/src/components/DataTable/components/ImportModal.tsx +7 -0
- package/src/components/DataTable/components/LoadingState.tsx +6 -0
- package/src/components/DataTable/components/PaginationControls.tsx +16 -1
- package/src/components/DataTable/components/RowComponent.tsx +391 -0
- package/src/components/DataTable/components/UnifiedTableBody.tsx +62 -852
- package/src/components/DataTable/components/VirtualizedDataTable.tsx +16 -4
- package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +4 -2
- package/src/components/DataTable/components/__tests__/DataTableModals.test.tsx +23 -23
- package/src/components/DataTable/components/__tests__/EditableRow.test.tsx +11 -11
- package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +36 -36
- package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +27 -27
- package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +39 -39
- package/src/components/DataTable/components/__tests__/UnifiedTableBody.test.tsx +33 -33
- package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +29 -29
- package/src/components/DataTable/components/cellValueUtils.ts +40 -0
- package/src/components/DataTable/components/hooks/useImportModalFocus.ts +53 -0
- package/src/components/DataTable/components/hooks/usePermissionTracking.ts +126 -0
- package/src/components/DataTable/context/DataTableContext.tsx +50 -0
- package/src/components/DataTable/core/ColumnFactory.ts +31 -0
- package/src/components/DataTable/core/DataTableContext.tsx +32 -1
- package/src/components/DataTable/hooks/useColumnOrderPersistence.ts +10 -0
- package/src/components/DataTable/hooks/useColumnReordering.ts +14 -2
- package/src/components/DataTable/hooks/useColumnVisibilityPersistence.ts +10 -0
- package/src/components/DataTable/hooks/useDataTableDataPipeline.ts +16 -0
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +124 -32
- package/src/components/DataTable/hooks/useDataTableState.ts +35 -1
- package/src/components/DataTable/hooks/useEffectiveColumnOrder.ts +12 -0
- package/src/components/DataTable/hooks/useKeyboardNavigation.ts +2 -2
- package/src/components/DataTable/hooks/useServerSideDataEffect.ts +11 -0
- package/src/components/DataTable/hooks/useTableColumns.ts +8 -0
- package/src/components/DataTable/hooks/useTableHandlers.ts +14 -0
- package/src/components/DataTable/styles.ts +6 -6
- package/src/components/DataTable/types.ts +6 -10
- package/src/components/DataTable/utils/a11yUtils.ts +7 -0
- package/src/components/DataTable/utils/debugTools.ts +18 -113
- package/src/components/DataTable/utils/errorHandling.ts +12 -0
- package/src/components/DataTable/utils/exportUtils.ts +9 -0
- package/src/components/DataTable/utils/flexibleImport.ts +12 -48
- package/src/components/DataTable/utils/paginationUtils.ts +8 -0
- package/src/components/DataTable/utils/performanceUtils.ts +5 -1
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +8 -14
- package/src/components/Dialog/Dialog.tsx +8 -7
- package/src/components/ErrorBoundary/ErrorBoundary.test.tsx +180 -1
- package/src/components/ErrorBoundary/ErrorBoundary.tsx +46 -6
- package/src/components/ErrorBoundary/ErrorBoundaryContext.tsx +129 -0
- package/src/components/ErrorBoundary/index.ts +27 -2
- package/src/components/EventSelector/EventSelector.tsx +4 -1
- package/src/components/FileDisplay/FileDisplay.test.tsx +2 -2
- package/src/components/FileDisplay/FileDisplay.tsx +32 -18
- package/src/components/FileUpload/FileUpload.tsx +22 -2
- package/src/components/Footer/Footer.test.tsx +16 -16
- package/src/components/Footer/Footer.tsx +15 -12
- package/src/components/Form/Form.test.tsx +36 -15
- package/src/components/Form/Form.tsx +31 -26
- package/src/components/Header/Header.tsx +22 -11
- package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +40 -40
- package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +1 -1
- package/src/components/Input/Input.test.tsx +2 -2
- package/src/components/Input/Input.tsx +36 -34
- package/src/components/Label/Label.tsx +1 -1
- package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +4 -4
- package/src/components/LoadingSpinner/LoadingSpinner.tsx +1 -1
- package/src/components/LoginForm/LoginForm.test.tsx +42 -42
- package/src/components/LoginForm/LoginForm.tsx +12 -8
- package/src/components/NavigationMenu/NavigationMenu.tsx +15 -514
- package/src/components/NavigationMenu/types.ts +56 -0
- package/src/components/NavigationMenu/useNavigationFiltering.ts +390 -0
- package/src/components/OrganisationSelector/OrganisationSelector.tsx +3 -0
- package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +1 -1
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +54 -52
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +33 -12
- package/src/components/PaceAppLayout/README.md +1 -1
- package/src/components/PaceAppLayout/test-setup.tsx +1 -2
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +4 -1
- package/src/components/PasswordChange/PasswordChangeForm.test.tsx +33 -33
- package/src/components/PasswordChange/PasswordChangeForm.tsx +10 -1
- package/src/components/Progress/Progress.tsx +1 -1
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +3 -9
- package/src/components/PublicLayout/PublicPageLayout.tsx +3 -6
- package/src/components/PublicLayout/PublicPageProvider.tsx +4 -0
- package/src/components/Select/Select.tsx +95 -438
- package/src/components/Select/context.ts +23 -0
- package/src/components/Select/hooks/useSelectEvents.ts +87 -0
- package/src/components/Select/hooks/useSelectSearch.ts +91 -0
- package/src/components/Select/hooks/useSelectState.ts +104 -0
- package/src/components/Select/index.ts +9 -1
- package/src/components/Select/types.ts +123 -0
- package/src/components/Select/utils/text.ts +26 -0
- package/src/components/SessionRestorationLoader/SessionRestorationLoader.tsx +5 -6
- package/src/components/Switch/Switch.tsx +4 -4
- package/src/components/Table/Table.tsx +1 -1
- package/src/components/Tabs/Tabs.tsx +1 -1
- package/src/components/Textarea/Textarea.tsx +27 -29
- package/src/components/Toast/Toast.tsx +5 -1
- package/src/components/Tooltip/Tooltip.tsx +3 -3
- package/src/components/UserMenu/UserMenu.test.tsx +24 -11
- package/src/components/UserMenu/UserMenu.tsx +22 -19
- package/src/components/index.ts +2 -2
- package/src/hooks/__tests__/hooks.integration.test.tsx +80 -55
- package/src/hooks/__tests__/index.unit.test.ts +2 -5
- package/src/hooks/__tests__/useStorage.unit.test.ts +36 -36
- package/src/hooks/index.ts +1 -2
- package/src/hooks/public/usePublicEvent.ts +5 -1
- package/src/hooks/public/usePublicEventLogo.ts +5 -1
- package/src/hooks/public/usePublicFileDisplay.ts +4 -0
- package/src/hooks/public/usePublicRouteParams.ts +5 -1
- package/src/hooks/services/useAuth.ts +32 -0
- package/src/hooks/services/useCurrentEvent.ts +6 -0
- package/src/hooks/services/useCurrentOrganisation.ts +6 -0
- package/src/hooks/useDataTableState.ts +8 -18
- package/src/hooks/useDebounce.ts +9 -0
- package/src/hooks/useEventTheme.ts +6 -0
- package/src/hooks/useFileDisplay.ts +4 -0
- package/src/hooks/useFileReference.ts +25 -7
- package/src/hooks/useFileUrl.ts +11 -1
- package/src/hooks/useFocusManagement.ts +16 -2
- package/src/hooks/useFocusTrap.ts +7 -4
- package/src/hooks/useFormDialog.ts +8 -7
- package/src/hooks/useInactivityTracker.ts +4 -1
- package/src/hooks/useKeyboardShortcuts.ts +4 -0
- package/src/hooks/useOrganisationPermissions.ts +4 -0
- package/src/hooks/useOrganisationSecurity.ts +4 -0
- package/src/hooks/usePerformanceMonitor.ts +4 -0
- package/src/hooks/usePermissionCache.ts +8 -1
- package/src/hooks/useQueryCache.ts +12 -1
- package/src/hooks/useSessionRestoration.ts +4 -0
- package/src/hooks/useStorage.ts +4 -0
- package/src/hooks/useToast.ts +3 -3
- package/src/index.ts +2 -1
- package/src/providers/__tests__/OrganisationProvider.test.tsx +115 -49
- package/src/providers/__tests__/ProviderLifecycle.test.tsx +21 -6
- package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +10 -10
- package/src/providers/services/AuthServiceProvider.tsx +18 -0
- package/src/providers/services/EventServiceProvider.tsx +18 -0
- package/src/providers/services/InactivityServiceProvider.tsx +18 -0
- package/src/providers/services/OrganisationServiceProvider.tsx +18 -0
- package/src/providers/services/UnifiedAuthProvider.tsx +58 -22
- package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +33 -7
- package/src/rbac/README.md +1 -1
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +26 -26
- package/src/rbac/__tests__/scenarios.user-role.test.tsx +4 -5
- package/src/rbac/adapters.tsx +14 -5
- package/src/rbac/api.ts +100 -67
- package/src/rbac/components/EnhancedNavigationMenu.tsx +1 -1
- package/src/rbac/components/NavigationGuard.tsx +1 -1
- package/src/rbac/components/NavigationProvider.tsx +5 -2
- package/src/rbac/components/PagePermissionGuard.tsx +158 -18
- package/src/rbac/components/PagePermissionProvider.tsx +1 -1
- package/src/rbac/components/PermissionEnforcer.tsx +1 -1
- package/src/rbac/components/RoleBasedRouter.tsx +6 -2
- package/src/rbac/components/SecureDataProvider.test.tsx +84 -49
- package/src/rbac/components/SecureDataProvider.tsx +21 -6
- package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +24 -14
- package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +7 -0
- package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +14 -6
- package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +15 -4
- package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +148 -24
- package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +81 -15
- package/src/rbac/engine.ts +38 -14
- package/src/rbac/hooks/permissions/index.ts +7 -0
- package/src/rbac/hooks/permissions/useAccessLevel.ts +105 -0
- package/src/rbac/hooks/permissions/useCachedPermissions.ts +79 -0
- package/src/rbac/hooks/permissions/useCan.ts +347 -0
- package/src/rbac/hooks/permissions/useHasAllPermissions.ts +90 -0
- package/src/rbac/hooks/permissions/useHasAnyPermission.ts +90 -0
- package/src/rbac/hooks/permissions/useMultiplePermissions.ts +93 -0
- package/src/rbac/hooks/permissions/usePermissions.ts +253 -0
- package/src/rbac/hooks/useCan.test.ts +71 -64
- package/src/rbac/hooks/usePermissions.ts +14 -995
- package/src/rbac/hooks/useResourcePermissions.test.ts +54 -18
- package/src/rbac/hooks/useResourcePermissions.ts +14 -4
- package/src/rbac/hooks/useSecureSupabase.ts +33 -13
- package/src/rbac/permissions.ts +0 -30
- package/src/rbac/secureClient.ts +212 -61
- package/src/rbac/types.ts +8 -0
- package/src/theming/__tests__/parseEventColours.test.ts +6 -9
- package/src/theming/parseEventColours.ts +5 -19
- package/src/types/vitest-globals.d.ts +51 -26
- package/src/utils/__mocks__/supabaseMock.ts +1 -3
- package/src/utils/__tests__/formatting.unit.test.ts +4 -4
- package/src/utils/__tests__/index.unit.test.ts +2 -2
- package/src/utils/audit/audit.ts +0 -3
- package/src/utils/core/cn.ts +1 -1
- package/src/utils/file-reference/index.ts +53 -1
- package/src/utils/formatting/formatting.ts +8 -18
- package/src/utils/index.ts +0 -1
- package/src/utils/security/secureDataAccess.test.ts +31 -20
- package/src/utils/security/secureDataAccess.ts +4 -3
- package/dist/chunk-6C4YBBJM.js +0 -628
- package/dist/chunk-6C4YBBJM.js.map +0 -1
- package/dist/chunk-7D4SUZUM.js 2.map +0 -1
- package/dist/chunk-7EQTDTTJ.js 2.map +0 -1
- package/dist/chunk-7EQTDTTJ.js.map +0 -1
- package/dist/chunk-7FLMSG37.js 2.map +0 -1
- package/dist/chunk-7FLMSG37.js.map +0 -1
- package/dist/chunk-BC4IJKSL.js.map +0 -1
- package/dist/chunk-E3SPN4VZ.js +0 -12917
- package/dist/chunk-E3SPN4VZ.js.map +0 -1
- package/dist/chunk-E66EQZE6 5.js +0 -37
- package/dist/chunk-E66EQZE6.js 2.map +0 -1
- package/dist/chunk-HWIIPPNI.js.map +0 -1
- package/dist/chunk-I7PSE6JW 5.js +0 -191
- package/dist/chunk-I7PSE6JW.js 2.map +0 -1
- package/dist/chunk-I7PSE6JW.js.map +0 -1
- package/dist/chunk-IIELH4DL.js.map +0 -1
- package/dist/chunk-KNC55RTG.js 5.map +0 -1
- package/dist/chunk-KNC55RTG.js.map +0 -1
- package/dist/chunk-KQCRWDSA.js 5.map +0 -1
- package/dist/chunk-LFNCN2SP.js +0 -412
- package/dist/chunk-LFNCN2SP.js 2.map +0 -1
- package/dist/chunk-LFNCN2SP.js.map +0 -1
- package/dist/chunk-LMC26NLJ 2.js +0 -84
- package/dist/chunk-NOAYCWCX.js +0 -4993
- package/dist/chunk-NOAYCWCX.js.map +0 -1
- package/dist/chunk-QWWZ5CAQ.js 3.map +0 -1
- package/dist/chunk-QWWZ5CAQ.js.map +0 -1
- package/dist/chunk-QXHPKYJV 3.js +0 -113
- package/dist/chunk-R77UEZ4E.js +0 -68
- package/dist/chunk-R77UEZ4E.js.map +0 -1
- package/dist/chunk-SQGMNID3.js.map +0 -1
- package/dist/chunk-VBXEHIUJ.js 6.map +0 -1
- package/dist/chunk-XNXXZ43G.js.map +0 -1
- package/dist/chunk-ZSAAAMVR 6.js +0 -25
- package/dist/components.js 5.map +0 -1
- package/dist/styles/index 2.js +0 -12
- package/dist/styles/index.js 5.map +0 -1
- package/dist/theming/runtime 5.js +0 -19
- package/dist/theming/runtime.js 5.map +0 -1
- package/docs/api/classes/ErrorBoundary.md +0 -144
- package/docs/migration/quick-migration-guide.md +0 -356
- package/docs/migration/service-architecture.md +0 -281
- package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +0 -680
- package/src/hooks/useSecureDataAccess.test.ts +0 -559
- package/src/hooks/useSecureDataAccess.ts +0 -666
- /package/dist/{DataTable-5FU7IESH.js.map → DataTable-TPTKCX4D.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-RGJTDE2C.js.map → UnifiedAuthProvider-CH6Z342H.js.map} +0 -0
- /package/dist/{api-N774RPUA.js.map → api-MVVQZLJI.js.map} +0 -0
- /package/docs/migration/{organisation-context-timing-fix.md → V0.3.44_organisation-context-timing-fix.md} +0 -0
- /package/docs/migration/{rbac-migration.md → V0.4.0_rbac-migration.md} +0 -0
- /package/docs/migration/{person-scoped-profiles-migration-guide.md → V0.5.190_person-scoped-profiles-migration-guide.md} +0 -0
- /package/examples/{rbac → RBAC}/CompleteRBACExample.tsx +0 -0
- /package/examples/{rbac → RBAC}/EventBasedApp.tsx +0 -0
- /package/examples/{rbac → RBAC}/PermissionExample.tsx +0 -0
- /package/examples/{rbac → RBAC}/index.ts +0 -0
|
@@ -14,425 +14,35 @@
|
|
|
14
14
|
|
|
15
15
|
import * as React from "react";
|
|
16
16
|
import { Search, X, ChevronDown, Check } from "lucide-react";
|
|
17
|
-
import { Button
|
|
17
|
+
import { Button } from "../Button/Button";
|
|
18
18
|
import { cn } from "../../utils/core/cn";
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
setOpen: (open: boolean) => void;
|
|
34
|
-
setSelectedText: (text: string) => void;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface SelectEventHandlers {
|
|
38
|
-
onValueChange?: (value: string) => void;
|
|
39
|
-
onOpenChange?: (open: boolean) => void;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export interface UseSelectStateProps {
|
|
43
|
-
value?: string;
|
|
44
|
-
defaultValue?: string;
|
|
45
|
-
selectedText?: string;
|
|
46
|
-
open?: boolean;
|
|
47
|
-
defaultOpen?: boolean;
|
|
48
|
-
disabled?: boolean;
|
|
49
|
-
onValueChange?: (value: string) => void;
|
|
50
|
-
onOpenChange?: (open: boolean) => void;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export interface UseSelectEventsProps {
|
|
54
|
-
state: SelectState;
|
|
55
|
-
actions: SelectActions;
|
|
56
|
-
selectRef: React.RefObject<HTMLElement>;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export interface UseSelectSearchProps {
|
|
60
|
-
children: React.ReactNode;
|
|
61
|
-
searchable?: boolean;
|
|
62
|
-
searchTerm?: string;
|
|
63
|
-
onSearchChange?: (term: string) => void;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export interface SelectContextValue extends SelectState {
|
|
67
|
-
actions: SelectActions;
|
|
68
|
-
registerItem?: (value: string, text: string) => void;
|
|
69
|
-
unregisterItem?: (value: string) => void;
|
|
70
|
-
direction?: 'up' | 'down';
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export interface SelectProps extends Omit<React.HTMLAttributes<HTMLFormElement>, 'onChange' | 'onKeyDown' | 'onFocus' | 'onBlur'> {
|
|
74
|
-
children: React.ReactNode;
|
|
75
|
-
className?: string;
|
|
76
|
-
direction?: 'up' | 'down';
|
|
77
|
-
// State props are in UseSelectStateProps (via & intersection)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
export interface SelectTriggerProps extends Omit<ButtonProps, 'onClick' | 'onKeyDown'> {
|
|
81
|
-
children: React.ReactNode;
|
|
82
|
-
asChild?: boolean;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
export interface SelectValueProps {
|
|
86
|
-
placeholder?: string;
|
|
87
|
-
children?: React.ReactNode;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export interface SelectContentProps {
|
|
91
|
-
children: React.ReactNode;
|
|
92
|
-
className?: string;
|
|
93
|
-
searchable?: boolean;
|
|
94
|
-
searchPlaceholder?: string;
|
|
95
|
-
maxHeight?: string;
|
|
96
|
-
style?: React.CSSProperties;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export interface SelectItemProps {
|
|
100
|
-
value: string;
|
|
101
|
-
children: React.ReactNode;
|
|
102
|
-
disabled?: boolean;
|
|
103
|
-
className?: string;
|
|
104
|
-
onClick?: (e: React.MouseEvent) => void;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// ============================================================================
|
|
108
|
-
// HOOKS
|
|
109
|
-
// ============================================================================
|
|
110
|
-
|
|
111
|
-
// State Management Hook
|
|
112
|
-
export const useSelectState = ({
|
|
113
|
-
value: controlledValue,
|
|
114
|
-
defaultValue = '',
|
|
115
|
-
selectedText: controlledSelectedText,
|
|
116
|
-
open: controlledOpen,
|
|
117
|
-
defaultOpen = false,
|
|
118
|
-
disabled = false,
|
|
119
|
-
onValueChange,
|
|
120
|
-
onOpenChange,
|
|
121
|
-
}: UseSelectStateProps) => {
|
|
122
|
-
// Internal state for uncontrolled mode
|
|
123
|
-
const [internalValue, setInternalValue] = React.useState(defaultValue);
|
|
124
|
-
const [internalSelectedText, setInternalSelectedText] = React.useState('');
|
|
125
|
-
const [internalOpen, setInternalOpen] = React.useState(defaultOpen);
|
|
126
|
-
|
|
127
|
-
// Computed values (controlled vs uncontrolled)
|
|
128
|
-
const value = controlledValue !== undefined ? controlledValue : internalValue;
|
|
129
|
-
const selectedText = controlledSelectedText !== undefined ? controlledSelectedText : internalSelectedText;
|
|
130
|
-
const open = controlledOpen !== undefined ? controlledOpen : internalOpen;
|
|
131
|
-
|
|
132
|
-
// Actions
|
|
133
|
-
const setValue = React.useCallback((newValue: string, newText: string) => {
|
|
134
|
-
if (controlledValue === undefined) {
|
|
135
|
-
setInternalValue(newValue);
|
|
136
|
-
setInternalSelectedText(newText);
|
|
137
|
-
}
|
|
138
|
-
onValueChange?.(newValue);
|
|
139
|
-
|
|
140
|
-
// Close dropdown after selection (for uncontrolled mode)
|
|
141
|
-
if (controlledOpen === undefined) {
|
|
142
|
-
setInternalOpen(false);
|
|
143
|
-
}
|
|
144
|
-
onOpenChange?.(false);
|
|
145
|
-
}, [controlledValue, onValueChange, controlledOpen, onOpenChange]);
|
|
146
|
-
|
|
147
|
-
const setOpen = React.useCallback((newOpen: boolean) => {
|
|
148
|
-
if (disabled) {
|
|
149
|
-
return;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// Close all other select dropdowns when opening this one
|
|
153
|
-
if (newOpen) {
|
|
154
|
-
const allTriggers = document.querySelectorAll('[data-testid="select-trigger"]');
|
|
155
|
-
allTriggers.forEach(trigger => {
|
|
156
|
-
if (trigger.getAttribute('aria-expanded') === 'true') {
|
|
157
|
-
const selectRoot = trigger.closest('[data-testid="select-root"]');
|
|
158
|
-
if (selectRoot) {
|
|
159
|
-
const closeEvent = new CustomEvent('closeSelect');
|
|
160
|
-
selectRoot.dispatchEvent(closeEvent);
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
if (controlledOpen === undefined) {
|
|
167
|
-
setInternalOpen(newOpen);
|
|
168
|
-
}
|
|
169
|
-
onOpenChange?.(newOpen);
|
|
170
|
-
}, [controlledOpen, onOpenChange, disabled]);
|
|
171
|
-
|
|
172
|
-
const setSelectedText = React.useCallback((newText: string) => {
|
|
173
|
-
if (controlledSelectedText === undefined) {
|
|
174
|
-
setInternalSelectedText(newText);
|
|
175
|
-
}
|
|
176
|
-
}, [controlledSelectedText]);
|
|
177
|
-
|
|
178
|
-
const state: SelectState = {
|
|
179
|
-
value,
|
|
180
|
-
selectedText,
|
|
181
|
-
open,
|
|
182
|
-
disabled,
|
|
183
|
-
};
|
|
184
|
-
|
|
185
|
-
const actions: SelectActions = {
|
|
186
|
-
setValue,
|
|
187
|
-
setOpen,
|
|
188
|
-
setSelectedText,
|
|
189
|
-
};
|
|
190
|
-
|
|
191
|
-
return { state, actions };
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
// Event Handling Hook
|
|
195
|
-
export const useSelectEvents = ({ state, actions, selectRef }: UseSelectEventsProps) => {
|
|
196
|
-
const [isSelecting, setIsSelecting] = React.useState(false);
|
|
197
|
-
|
|
198
|
-
// Listen for close events from other selects
|
|
199
|
-
React.useEffect(() => {
|
|
200
|
-
const handleCloseSelect = () => {
|
|
201
|
-
actions.setOpen(false);
|
|
202
|
-
};
|
|
203
|
-
|
|
204
|
-
const selectElement = selectRef.current as HTMLElement;
|
|
205
|
-
if (selectElement) {
|
|
206
|
-
selectElement.addEventListener('closeSelect', handleCloseSelect);
|
|
207
|
-
return () => {
|
|
208
|
-
selectElement.removeEventListener('closeSelect', handleCloseSelect);
|
|
209
|
-
};
|
|
210
|
-
}
|
|
211
|
-
}, [actions, selectRef]);
|
|
212
|
-
|
|
213
|
-
// Handle click outside to close dropdown
|
|
214
|
-
React.useEffect(() => {
|
|
215
|
-
if (!state.open) return;
|
|
216
|
-
|
|
217
|
-
const handleClickOutside = (event: MouseEvent) => {
|
|
218
|
-
const selectElement = selectRef.current;
|
|
219
|
-
if (!selectElement) return;
|
|
220
|
-
|
|
221
|
-
const target = event.target as Node;
|
|
222
|
-
|
|
223
|
-
// Close if clicking outside the select element
|
|
224
|
-
if (!selectElement.contains(target) && !isSelecting) {
|
|
225
|
-
actions.setOpen(false);
|
|
226
|
-
}
|
|
227
|
-
};
|
|
228
|
-
|
|
229
|
-
// Add a small delay to avoid closing immediately when opening
|
|
230
|
-
// The delay ensures the click event that opened the dropdown has fully processed
|
|
231
|
-
const timeoutId = setTimeout(() => {
|
|
232
|
-
document.addEventListener('click', handleClickOutside, true); // Use capture phase
|
|
233
|
-
}, 100); // Small delay to let the opening click complete
|
|
234
|
-
|
|
235
|
-
return () => {
|
|
236
|
-
clearTimeout(timeoutId);
|
|
237
|
-
document.removeEventListener('click', handleClickOutside, true);
|
|
238
|
-
};
|
|
239
|
-
}, [state.open, actions, selectRef, isSelecting]);
|
|
240
|
-
|
|
241
|
-
// Handle SelectItem mousedown events
|
|
242
|
-
React.useEffect(() => {
|
|
243
|
-
let timeoutId: NodeJS.Timeout | null = null;
|
|
244
|
-
let isMounted = true;
|
|
245
|
-
|
|
246
|
-
const handleSelectItemMouseDown = () => {
|
|
247
|
-
if (!isMounted) return;
|
|
248
|
-
|
|
249
|
-
setIsSelecting(true);
|
|
250
|
-
timeoutId = setTimeout(() => {
|
|
251
|
-
if (isMounted) {
|
|
252
|
-
setIsSelecting(false);
|
|
253
|
-
}
|
|
254
|
-
}, 150);
|
|
255
|
-
};
|
|
256
|
-
|
|
257
|
-
document.addEventListener('selectItemMouseDown', handleSelectItemMouseDown as EventListener);
|
|
258
|
-
return () => {
|
|
259
|
-
isMounted = false;
|
|
260
|
-
document.removeEventListener('selectItemMouseDown', handleSelectItemMouseDown as EventListener);
|
|
261
|
-
if (timeoutId) {
|
|
262
|
-
clearTimeout(timeoutId);
|
|
263
|
-
}
|
|
264
|
-
};
|
|
265
|
-
}, []);
|
|
266
|
-
|
|
267
|
-
return { isSelecting };
|
|
268
|
-
};
|
|
269
|
-
|
|
270
|
-
// Search Functionality Hook
|
|
271
|
-
export const useSelectSearch = ({
|
|
272
|
-
children,
|
|
273
|
-
searchable = false,
|
|
274
|
-
searchTerm: controlledSearchTerm,
|
|
275
|
-
onSearchChange
|
|
276
|
-
}: UseSelectSearchProps) => {
|
|
277
|
-
const [internalSearchTerm, setInternalSearchTerm] = React.useState('');
|
|
278
|
-
const [filteredChildren, setFilteredChildren] = React.useState<React.ReactNode>(children);
|
|
279
|
-
const searchInputRef = React.useRef<HTMLInputElement>(null);
|
|
280
|
-
|
|
281
|
-
const searchTerm = controlledSearchTerm !== undefined ? controlledSearchTerm : internalSearchTerm;
|
|
282
|
-
|
|
283
|
-
const setSearchTerm = React.useCallback((term: string) => {
|
|
284
|
-
if (controlledSearchTerm === undefined) {
|
|
285
|
-
setInternalSearchTerm(term);
|
|
286
|
-
}
|
|
287
|
-
onSearchChange?.(term);
|
|
288
|
-
}, [controlledSearchTerm, onSearchChange]);
|
|
289
|
-
|
|
290
|
-
// Filter children based on search term
|
|
291
|
-
React.useEffect(() => {
|
|
292
|
-
if (!searchable || !searchTerm) {
|
|
293
|
-
setFilteredChildren(children);
|
|
294
|
-
return;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
const filterChildren = (nodes: React.ReactNode): React.ReactNode => {
|
|
298
|
-
return React.Children.map(nodes, (child) => {
|
|
299
|
-
if (!React.isValidElement(child)) return child;
|
|
300
|
-
|
|
301
|
-
// Check if this is a SelectItem by looking for the data-testid or value prop
|
|
302
|
-
const isSelectItem = child.props &&
|
|
303
|
-
(child.props['data-testid'] === 'select-item' ||
|
|
304
|
-
child.props.value !== undefined);
|
|
305
|
-
|
|
306
|
-
if (isSelectItem) {
|
|
307
|
-
const childText = React.Children.toArray(child.props.children).join(' ').toLowerCase();
|
|
308
|
-
const searchLower = searchTerm.toLowerCase();
|
|
309
|
-
|
|
310
|
-
if (childText.includes(searchLower)) {
|
|
311
|
-
return child;
|
|
312
|
-
}
|
|
313
|
-
return null;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
if (child.props.children) {
|
|
317
|
-
const filteredChildChildren = filterChildren(child.props.children);
|
|
318
|
-
if (React.Children.count(filteredChildChildren) > 0) {
|
|
319
|
-
return React.cloneElement(child, {}, filteredChildChildren);
|
|
320
|
-
}
|
|
321
|
-
return null;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
return child;
|
|
325
|
-
});
|
|
326
|
-
};
|
|
327
|
-
|
|
328
|
-
const filtered = filterChildren(children);
|
|
329
|
-
setFilteredChildren(filtered);
|
|
330
|
-
}, [children, searchTerm, searchable]);
|
|
331
|
-
|
|
332
|
-
return {
|
|
333
|
-
searchTerm,
|
|
334
|
-
setSearchTerm,
|
|
335
|
-
filteredChildren,
|
|
336
|
-
searchInputRef,
|
|
337
|
-
};
|
|
338
|
-
};
|
|
339
|
-
|
|
340
|
-
// ============================================================================
|
|
341
|
-
// CONTEXT
|
|
342
|
-
// ============================================================================
|
|
343
|
-
|
|
344
|
-
const SelectContext = React.createContext<SelectContextValue | null>(null);
|
|
345
|
-
|
|
346
|
-
const useSelectContext = () => {
|
|
347
|
-
const context = React.useContext(SelectContext);
|
|
348
|
-
if (!context) {
|
|
349
|
-
throw new Error('Select components must be used within a Select');
|
|
350
|
-
}
|
|
351
|
-
return context;
|
|
352
|
-
};
|
|
19
|
+
import { SelectContext, useSelectContext } from "./context";
|
|
20
|
+
import { useSelectEvents } from "./hooks/useSelectEvents";
|
|
21
|
+
import { useSelectSearch } from "./hooks/useSelectSearch";
|
|
22
|
+
import { useSelectState } from "./hooks/useSelectState";
|
|
23
|
+
import type {
|
|
24
|
+
SelectContentProps,
|
|
25
|
+
SelectContextValue,
|
|
26
|
+
SelectItemProps,
|
|
27
|
+
SelectProps,
|
|
28
|
+
SelectTriggerProps,
|
|
29
|
+
SelectValueProps,
|
|
30
|
+
UseSelectStateProps,
|
|
31
|
+
} from "./types";
|
|
32
|
+
import { getTextContent } from "./utils/text";
|
|
353
33
|
|
|
354
34
|
// ============================================================================
|
|
355
35
|
// ROOT COMPONENT
|
|
356
36
|
// ============================================================================
|
|
357
37
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
return children.map(getTextContent).join('');
|
|
367
|
-
}
|
|
368
|
-
return '';
|
|
369
|
-
};
|
|
370
|
-
|
|
371
|
-
// Helper function to find SelectItem by value in React children tree
|
|
372
|
-
const findSelectItemText = (children: React.ReactNode, targetValue: string): string | null => {
|
|
373
|
-
if (!children) return null;
|
|
374
|
-
|
|
375
|
-
// Handle arrays directly (common when using .map())
|
|
376
|
-
if (Array.isArray(children)) {
|
|
377
|
-
for (const child of children) {
|
|
378
|
-
if (!child) continue;
|
|
379
|
-
const found = findSelectItemText(child, targetValue);
|
|
380
|
-
if (found) return found;
|
|
381
|
-
}
|
|
382
|
-
return null;
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
// Handle React elements directly if it's a single element
|
|
386
|
-
if (React.isValidElement(children)) {
|
|
387
|
-
const props = (children as React.ReactElement<{ value?: string; children?: React.ReactNode }>).props;
|
|
388
|
-
|
|
389
|
-
// Check if this is a SelectItem
|
|
390
|
-
if (props && props.value !== undefined && typeof props.value === 'string' && props.value === targetValue) {
|
|
391
|
-
const text = getTextContent(props.children);
|
|
392
|
-
if (text && text.trim()) {
|
|
393
|
-
return text.trim();
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
// Recurse into children
|
|
398
|
-
if (props && props.children !== undefined && props.children !== null) {
|
|
399
|
-
const found = findSelectItemText(props.children, targetValue);
|
|
400
|
-
if (found) return found;
|
|
401
|
-
}
|
|
402
|
-
return null;
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
// Handle multiple children using React.Children utilities
|
|
406
|
-
const childrenArray = React.Children.toArray(children);
|
|
407
|
-
|
|
408
|
-
for (const child of childrenArray) {
|
|
409
|
-
if (!React.isValidElement(child)) continue;
|
|
410
|
-
|
|
411
|
-
const props = (child as React.ReactElement<{ value?: string; children?: React.ReactNode }>).props;
|
|
412
|
-
|
|
413
|
-
// Check if this is a SelectItem by checking for value prop
|
|
414
|
-
// SelectItem components always have a value prop
|
|
415
|
-
if (props && props.value !== undefined && typeof props.value === 'string' && props.value === targetValue) {
|
|
416
|
-
const text = getTextContent(props.children);
|
|
417
|
-
if (text && text.trim()) {
|
|
418
|
-
return text.trim();
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
// Recursively search in children
|
|
423
|
-
// Need to check both props.children and handle null/undefined
|
|
424
|
-
if (props) {
|
|
425
|
-
const childChildren = props.children;
|
|
426
|
-
if (childChildren !== undefined && childChildren !== null) {
|
|
427
|
-
const found = findSelectItemText(childChildren, targetValue);
|
|
428
|
-
if (found) return found;
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
return null;
|
|
434
|
-
};
|
|
435
|
-
|
|
38
|
+
/**
|
|
39
|
+
* Select component root.
|
|
40
|
+
* Provides select dropdown functionality with search, keyboard navigation, and accessibility.
|
|
41
|
+
*
|
|
42
|
+
* @param props - Select configuration
|
|
43
|
+
* @param ref - Forwarded ref to the form element
|
|
44
|
+
* @returns The rendered select component
|
|
45
|
+
*/
|
|
436
46
|
export const Select = React.forwardRef<HTMLFormElement, SelectProps & UseSelectStateProps>(
|
|
437
47
|
({
|
|
438
48
|
children,
|
|
@@ -443,7 +53,7 @@ export const Select = React.forwardRef<HTMLFormElement, SelectProps & UseSelectS
|
|
|
443
53
|
const internalRef = React.useRef<HTMLFormElement>(null);
|
|
444
54
|
const selectRef = React.useMemo(() => {
|
|
445
55
|
if (ref && typeof ref === 'object' && 'current' in ref) {
|
|
446
|
-
return ref as React.RefObject<HTMLFormElement>;
|
|
56
|
+
return ref as React.RefObject<HTMLFormElement | null>;
|
|
447
57
|
}
|
|
448
58
|
return internalRef;
|
|
449
59
|
}, [ref]);
|
|
@@ -614,13 +224,21 @@ Select.displayName = "Select";
|
|
|
614
224
|
// TRIGGER COMPONENT
|
|
615
225
|
// ============================================================================
|
|
616
226
|
|
|
227
|
+
/**
|
|
228
|
+
* Select trigger button component.
|
|
229
|
+
* Opens/closes the select dropdown and displays the selected value.
|
|
230
|
+
*
|
|
231
|
+
* @param props - Select trigger configuration
|
|
232
|
+
* @param ref - Forwarded ref to the button element
|
|
233
|
+
* @returns The rendered select trigger
|
|
234
|
+
*/
|
|
617
235
|
export const SelectTrigger = React.forwardRef<HTMLButtonElement, SelectTriggerProps>(
|
|
618
236
|
({ children, className, variant = "outline", size = "default", asChild = false, ...props }, ref) => {
|
|
619
237
|
const { open, disabled, value, actions, direction = 'down' } = useSelectContext();
|
|
620
238
|
const opensUpward = direction === 'up';
|
|
621
239
|
|
|
622
240
|
// Use ref to store the latest handleClick to avoid re-creating the effect
|
|
623
|
-
const handleClickRef = React.useRef<(e: React.MouseEvent) => void>();
|
|
241
|
+
const handleClickRef = React.useRef<(e: React.MouseEvent) => void>(undefined);
|
|
624
242
|
|
|
625
243
|
const handleClick = React.useCallback((e: React.MouseEvent) => {
|
|
626
244
|
if (disabled) {
|
|
@@ -687,22 +305,26 @@ export const SelectTrigger = React.forwardRef<HTMLButtonElement, SelectTriggerPr
|
|
|
687
305
|
};
|
|
688
306
|
|
|
689
307
|
if (asChild) {
|
|
690
|
-
const
|
|
691
|
-
const
|
|
692
|
-
|
|
693
|
-
(child
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
308
|
+
const childElement = children as React.ReactElement<{ children?: React.ReactNode; [key: string]: unknown }>;
|
|
309
|
+
const childChildren = React.Children.toArray(childElement.props.children);
|
|
310
|
+
const hasChevron = childChildren.some(child => {
|
|
311
|
+
if (React.isValidElement(child)) {
|
|
312
|
+
const childProps = child.props as { 'data-testid'?: string; [key: string]: unknown };
|
|
313
|
+
return child.type === ChevronDown ||
|
|
314
|
+
(child.type === 'svg' && childProps['data-testid'] === 'chevron-down') ||
|
|
315
|
+
(typeof child === 'object' && 'type' in child && typeof child.type === 'function' && child.type.name === 'ChevronDown');
|
|
316
|
+
}
|
|
317
|
+
return false;
|
|
318
|
+
});
|
|
697
319
|
|
|
698
320
|
// Merge child's className with triggerProps className
|
|
699
|
-
const childClassName = (children as React.ReactElement).props.className;
|
|
321
|
+
const childClassName = (children as React.ReactElement<any>).props.className;
|
|
700
322
|
const mergedClassName = cn(
|
|
701
323
|
triggerProps.className,
|
|
702
324
|
childClassName
|
|
703
325
|
);
|
|
704
326
|
|
|
705
|
-
return React.cloneElement(children as React.ReactElement
|
|
327
|
+
return React.cloneElement(children as React.ReactElement<any>, {
|
|
706
328
|
...triggerProps,
|
|
707
329
|
className: mergedClassName,
|
|
708
330
|
children: hasChevron ? childChildren : [
|
|
@@ -776,6 +398,14 @@ SelectTrigger.displayName = "SelectTrigger";
|
|
|
776
398
|
// VALUE COMPONENT
|
|
777
399
|
// ============================================================================
|
|
778
400
|
|
|
401
|
+
/**
|
|
402
|
+
* Select value display component.
|
|
403
|
+
* Shows the selected value or placeholder text.
|
|
404
|
+
*
|
|
405
|
+
* @param props - Select value configuration
|
|
406
|
+
* @param ref - Forwarded ref to the span element
|
|
407
|
+
* @returns The rendered select value display
|
|
408
|
+
*/
|
|
779
409
|
export const SelectValue = React.forwardRef<HTMLSpanElement, SelectValueProps>(
|
|
780
410
|
({ placeholder = "Select an option...", children }, ref) => {
|
|
781
411
|
const { selectedText } = useSelectContext();
|
|
@@ -798,6 +428,14 @@ SelectValue.displayName = "SelectValue";
|
|
|
798
428
|
// CONTENT COMPONENT
|
|
799
429
|
// ============================================================================
|
|
800
430
|
|
|
431
|
+
/**
|
|
432
|
+
* Select content/dropdown component.
|
|
433
|
+
* Contains the list of selectable options.
|
|
434
|
+
*
|
|
435
|
+
* @param props - Select content configuration
|
|
436
|
+
* @param ref - Forwarded ref to the list element
|
|
437
|
+
* @returns The rendered select content
|
|
438
|
+
*/
|
|
801
439
|
export const SelectContent = React.forwardRef<HTMLUListElement, SelectContentProps>(
|
|
802
440
|
({
|
|
803
441
|
children,
|
|
@@ -906,24 +544,19 @@ SelectContent.displayName = "SelectContent";
|
|
|
906
544
|
// ITEM COMPONENT
|
|
907
545
|
// ============================================================================
|
|
908
546
|
|
|
547
|
+
/**
|
|
548
|
+
* Select item component.
|
|
549
|
+
* Represents a single selectable option in the dropdown.
|
|
550
|
+
*
|
|
551
|
+
* @param props - Select item configuration
|
|
552
|
+
* @param ref - Forwarded ref to the list item element
|
|
553
|
+
* @returns The rendered select item
|
|
554
|
+
*/
|
|
909
555
|
export const SelectItem = React.forwardRef<HTMLLIElement, SelectItemProps>(
|
|
910
556
|
({ value, children, disabled = false, className, onClick }, ref) => {
|
|
911
557
|
const { value: selectedValue, actions, registerItem, unregisterItem } = useSelectContext();
|
|
912
558
|
const isSelected = selectedValue === value;
|
|
913
|
-
|
|
914
|
-
// Extract text content from children for display
|
|
915
|
-
const getTextContent = (children: React.ReactNode): string => {
|
|
916
|
-
if (typeof children === 'string') return children;
|
|
917
|
-
if (typeof children === 'number') return children.toString();
|
|
918
|
-
if (React.isValidElement(children) && children.props.children) {
|
|
919
|
-
return getTextContent(children.props.children);
|
|
920
|
-
}
|
|
921
|
-
if (Array.isArray(children)) {
|
|
922
|
-
return children.map(getTextContent).join('');
|
|
923
|
-
}
|
|
924
|
-
return '';
|
|
925
|
-
};
|
|
926
|
-
|
|
559
|
+
|
|
927
560
|
const itemText = getTextContent(children);
|
|
928
561
|
|
|
929
562
|
// Register this item when it mounts or text changes
|
|
@@ -1014,6 +647,14 @@ SelectItem.displayName = "SelectItem";
|
|
|
1014
647
|
// ADDITIONAL COMPONENTS (for backward compatibility)
|
|
1015
648
|
// ============================================================================
|
|
1016
649
|
|
|
650
|
+
/**
|
|
651
|
+
* Select group component.
|
|
652
|
+
* Groups related select items together.
|
|
653
|
+
*
|
|
654
|
+
* @param props - Select group configuration
|
|
655
|
+
* @param ref - Forwarded ref to the div element
|
|
656
|
+
* @returns The rendered select group
|
|
657
|
+
*/
|
|
1017
658
|
export const SelectGroup = React.forwardRef<HTMLDivElement, { children: React.ReactNode; className?: string }>(
|
|
1018
659
|
({ children, className }, ref) => {
|
|
1019
660
|
return (
|
|
@@ -1025,6 +666,14 @@ export const SelectGroup = React.forwardRef<HTMLDivElement, { children: React.Re
|
|
|
1025
666
|
);
|
|
1026
667
|
SelectGroup.displayName = "SelectGroup";
|
|
1027
668
|
|
|
669
|
+
/**
|
|
670
|
+
* Select label component.
|
|
671
|
+
* Provides a label for a group of select items.
|
|
672
|
+
*
|
|
673
|
+
* @param props - Select label configuration
|
|
674
|
+
* @param ref - Forwarded ref to the div element
|
|
675
|
+
* @returns The rendered select label
|
|
676
|
+
*/
|
|
1028
677
|
export const SelectLabel = React.forwardRef<HTMLDivElement, { children: React.ReactNode; className?: string }>(
|
|
1029
678
|
({ children, className }, ref) => {
|
|
1030
679
|
return (
|
|
@@ -1036,6 +685,14 @@ export const SelectLabel = React.forwardRef<HTMLDivElement, { children: React.Re
|
|
|
1036
685
|
);
|
|
1037
686
|
SelectLabel.displayName = "SelectLabel";
|
|
1038
687
|
|
|
688
|
+
/**
|
|
689
|
+
* Select separator component.
|
|
690
|
+
* Provides visual separation between groups of select items.
|
|
691
|
+
*
|
|
692
|
+
* @param props - Select separator configuration
|
|
693
|
+
* @param ref - Forwarded ref to the div element
|
|
694
|
+
* @returns The rendered select separator
|
|
695
|
+
*/
|
|
1039
696
|
export const SelectSeparator = React.forwardRef<HTMLDivElement, { className?: string }>(
|
|
1040
697
|
({ className }, ref) => {
|
|
1041
698
|
return (
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type { SelectContextValue } from "./types";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Select context for sharing state between Select components.
|
|
6
|
+
*/
|
|
7
|
+
export const SelectContext =
|
|
8
|
+
React.createContext<SelectContextValue | null>(null);
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Hook to access Select context.
|
|
12
|
+
* Must be used within a Select component.
|
|
13
|
+
*
|
|
14
|
+
* @returns Select context value
|
|
15
|
+
* @throws Error if used outside Select component
|
|
16
|
+
*/
|
|
17
|
+
export const useSelectContext = () => {
|
|
18
|
+
const context = React.useContext(SelectContext);
|
|
19
|
+
if (!context) {
|
|
20
|
+
throw new Error("Select components must be used within a Select");
|
|
21
|
+
}
|
|
22
|
+
return context;
|
|
23
|
+
};
|