@jmruthers/pace-core 0.5.189 → 0.5.191
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/core-usage-manifest.json +0 -4
- package/dist/{AuthService-B-cd2MA4.d.ts → AuthService-CbP_utw2.d.ts} +7 -3
- package/dist/{DataTable-IVYljGJ6.d.ts → DataTable-Be6dH_dR.d.ts} +1 -1
- package/dist/{DataTable-GUFUNZ3N.js → DataTable-WKRZD47S.js} +8 -8
- package/dist/{PublicPageProvider-B8HaLe69.d.ts → PublicPageProvider-ULXC_u6U.d.ts} +84 -25
- package/dist/{UnifiedAuthProvider-BG0AL5eE.d.ts → UnifiedAuthProvider-BYA9qB-o.d.ts} +4 -3
- package/dist/{UnifiedAuthProvider-643PUAIM.js → UnifiedAuthProvider-FTSG5XH7.js} +4 -2
- package/dist/{api-YP7XD5L6.js → api-IHKALJZD.js} +4 -2
- package/dist/{chunk-VGZZXKBR.js → chunk-6LTQQAT6.js} +351 -157
- package/dist/chunk-6LTQQAT6.js.map +1 -0
- package/dist/{chunk-MX64ZF6I.js → chunk-6TQDD426.js} +15 -15
- package/dist/chunk-6TQDD426.js.map +1 -0
- package/dist/{chunk-YHCN776L.js → chunk-G37KK66H.js} +2 -75
- package/dist/chunk-G37KK66H.js.map +1 -0
- package/dist/{chunk-THRPYOFK.js → chunk-HW3OVDUF.js} +5 -5
- package/dist/chunk-HW3OVDUF.js.map +1 -0
- package/dist/{chunk-F2IMUDXZ.js → chunk-I7PSE6JW.js} +75 -2
- package/dist/chunk-I7PSE6JW.js.map +1 -0
- package/dist/{chunk-IM4QE42D.js → chunk-LOMZXPSN.js} +141 -326
- package/dist/chunk-LOMZXPSN.js.map +1 -0
- package/dist/chunk-OETXORNB.js +614 -0
- package/dist/chunk-OETXORNB.js.map +1 -0
- package/dist/{chunk-HESYZWZW.js → chunk-QWWZ5CAQ.js} +2 -2
- package/dist/{chunk-HEHYGYOX.js → chunk-ROXMHMY2.js} +403 -46
- package/dist/chunk-ROXMHMY2.js.map +1 -0
- package/dist/{chunk-2UUZZJFT.js → chunk-ULHIJK66.js} +228 -177
- package/dist/{chunk-2UUZZJFT.js.map → chunk-ULHIJK66.js.map} +1 -1
- package/dist/{chunk-YGPFYGA6.js → chunk-VKB2CO4Z.js} +838 -503
- package/dist/chunk-VKB2CO4Z.js.map +1 -0
- package/dist/{chunk-3GOZZZYH.js → chunk-VRGWKHDB.js} +238 -301
- package/dist/chunk-VRGWKHDB.js.map +1 -0
- package/dist/{chunk-UCQSRW7Z.js → chunk-XNYQOL3Z.js} +431 -384
- package/dist/chunk-XNYQOL3Z.js.map +1 -0
- package/dist/{chunk-DDM4CCYT.js → chunk-XYXSXPUK.js} +79 -59
- package/dist/chunk-XYXSXPUK.js.map +1 -0
- package/dist/{chunk-SAUPYVLF.js → chunk-ZSAAAMVR.js} +1 -1
- package/dist/chunk-ZSAAAMVR.js.map +1 -0
- package/dist/components.d.ts +5 -6
- package/dist/components.js +19 -19
- package/dist/components.js.map +1 -1
- package/dist/{database.generated-DI89OQeI.d.ts → database.generated-CzIvgcPu.d.ts} +165 -201
- package/dist/eslint-rules/pace-core-compliance.cjs +0 -2
- package/dist/{file-reference-D037xOFK.d.ts → file-reference-BavO2eQj.d.ts} +13 -10
- package/dist/hooks.d.ts +20 -15
- package/dist/hooks.js +14 -8
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +17 -15
- package/dist/index.js +86 -81
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +3 -3
- package/dist/providers.js +3 -1
- package/dist/rbac/index.d.ts +77 -13
- package/dist/rbac/index.js +12 -9
- package/dist/{types-Bwgl--Xo.d.ts → types-CEpcvwwF.d.ts} +1 -1
- package/dist/types.d.ts +3 -3
- package/dist/types.js +1 -1
- package/dist/{usePublicRouteParams-CTDELQ7H.d.ts → usePublicRouteParams-TZe0gy-4.d.ts} +17 -10
- package/dist/utils.d.ts +8 -8
- package/dist/utils.js +16 -16
- package/docs/README.md +2 -2
- package/docs/api/classes/ColumnFactory.md +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +2 -2
- package/docs/api/classes/Logger.md +1 -1
- package/docs/api/classes/MissingUserContextError.md +2 -2
- package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
- package/docs/api/classes/PermissionDeniedError.md +1 -1
- package/docs/api/classes/RBACAuditManager.md +2 -2
- package/docs/api/classes/RBACCache.md +1 -1
- package/docs/api/classes/RBACEngine.md +5 -5
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +2 -2
- package/docs/api/classes/SecureSupabaseClient.md +25 -20
- package/docs/api/classes/StorageUtils.md +7 -4
- 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 +1 -1
- package/docs/api/interfaces/AutocompleteOptions.md +1 -1
- package/docs/api/interfaces/AvatarProps.md +1 -1
- package/docs/api/interfaces/BadgeProps.md +1 -1
- package/docs/api/interfaces/ButtonProps.md +1 -1
- package/docs/api/interfaces/CalendarProps.md +20 -6
- package/docs/api/interfaces/CardProps.md +1 -1
- 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 +1 -1
- package/docs/api/interfaces/DataTableColumn.md +1 -1
- package/docs/api/interfaces/DataTableProps.md +1 -1
- package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
- package/docs/api/interfaces/DatabaseComplianceResult.md +1 -1
- package/docs/api/interfaces/DatabaseIssue.md +1 -1
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventAppRoleData.md +1 -1
- package/docs/api/interfaces/ExportColumn.md +1 -1
- package/docs/api/interfaces/ExportOptions.md +1 -1
- package/docs/api/interfaces/FileDisplayProps.md +62 -16
- package/docs/api/interfaces/FileMetadata.md +1 -1
- package/docs/api/interfaces/FileReference.md +2 -2
- package/docs/api/interfaces/FileSizeLimits.md +1 -1
- package/docs/api/interfaces/FileUploadOptions.md +26 -12
- package/docs/api/interfaces/FileUploadProps.md +30 -19
- package/docs/api/interfaces/FooterProps.md +1 -1
- package/docs/api/interfaces/FormFieldProps.md +1 -1
- 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 +1 -1
- package/docs/api/interfaces/LabelProps.md +1 -1
- package/docs/api/interfaces/LoggerConfig.md +1 -1
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- package/docs/api/interfaces/NavigationAccessRecord.md +10 -10
- package/docs/api/interfaces/NavigationContextType.md +9 -9
- package/docs/api/interfaces/NavigationGuardProps.md +1 -1
- package/docs/api/interfaces/NavigationItem.md +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- package/docs/api/interfaces/NavigationProviderProps.md +7 -7
- 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 +1 -1
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +8 -8
- package/docs/api/interfaces/PagePermissionContextType.md +8 -8
- package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
- package/docs/api/interfaces/PagePermissionProviderProps.md +7 -7
- package/docs/api/interfaces/PaletteData.md +1 -1
- package/docs/api/interfaces/ParsedAddress.md +2 -2
- package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
- package/docs/api/interfaces/ProgressProps.md +3 -11
- package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
- package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
- package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
- package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
- 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 +2 -2
- 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 +8 -8
- package/docs/api/interfaces/RoleBasedRouterProps.md +10 -10
- package/docs/api/interfaces/RoleManagementResult.md +1 -1
- package/docs/api/interfaces/RouteAccessRecord.md +10 -10
- package/docs/api/interfaces/RouteConfig.md +10 -10
- 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 +1 -1
- package/docs/api/interfaces/SetupIssue.md +1 -1
- package/docs/api/interfaces/StorageConfig.md +4 -4
- package/docs/api/interfaces/StorageFileInfo.md +7 -7
- package/docs/api/interfaces/StorageFileMetadata.md +25 -14
- package/docs/api/interfaces/StorageListOptions.md +22 -9
- package/docs/api/interfaces/StorageListResult.md +4 -4
- package/docs/api/interfaces/StorageUploadOptions.md +21 -8
- package/docs/api/interfaces/StorageUploadResult.md +6 -6
- package/docs/api/interfaces/StorageUrlOptions.md +19 -6
- 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 +1 -1
- package/docs/api/interfaces/TextareaProps.md +1 -1
- package/docs/api/interfaces/ToastActionElement.md +1 -1
- package/docs/api/interfaces/ToastProps.md +1 -1
- package/docs/api/interfaces/UnifiedAuthContextType.md +53 -53
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +13 -13
- package/docs/api/interfaces/UseFormDialogOptions.md +1 -1
- package/docs/api/interfaces/UseFormDialogReturn.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
- package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventLogoOptions.md +2 -2
- package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
- package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
- package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
- package/docs/api/interfaces/UsePublicFileDisplayOptions.md +2 -2
- package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
- package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
- package/docs/api/interfaces/UseResolvedScopeOptions.md +5 -5
- package/docs/api/interfaces/UseResolvedScopeReturn.md +4 -4
- package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +11 -11
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +165 -106
- package/docs/api-reference/components.md +15 -7
- package/docs/api-reference/providers.md +2 -2
- package/docs/api-reference/rpc-functions.md +1 -0
- package/docs/best-practices/README.md +1 -1
- package/docs/best-practices/deployment.md +8 -8
- package/docs/getting-started/examples/README.md +2 -2
- package/docs/getting-started/installation-guide.md +4 -4
- package/docs/getting-started/quick-start.md +3 -3
- package/docs/migration/MIGRATION_GUIDE.md +3 -3
- package/docs/migration/README.md +18 -0
- package/docs/migration/database-changes-december-2025.md +767 -0
- package/docs/migration/person-scoped-profiles-migration-guide.md +472 -0
- package/docs/rbac/compliance/compliance-guide.md +2 -2
- package/docs/rbac/event-based-apps.md +2 -2
- package/docs/rbac/getting-started.md +2 -2
- package/docs/rbac/quick-start.md +2 -2
- package/docs/security/README.md +4 -4
- package/docs/standards/07-rbac-and-rls-standard.md +430 -7
- package/docs/troubleshooting/README.md +2 -2
- package/docs/troubleshooting/migration.md +3 -3
- package/package.json +1 -3
- package/scripts/check-pace-core-compliance.cjs +1 -1
- package/scripts/check-pace-core-compliance.js +1 -1
- package/src/__tests__/fixtures/supabase.ts +301 -0
- package/src/__tests__/public-recipe-view.test.ts +19 -19
- package/src/__tests__/rls-policies.test.ts +210 -74
- package/src/components/AddressField/AddressField.test.tsx +42 -0
- package/src/components/AddressField/AddressField.tsx +71 -60
- package/src/components/AddressField/README.md +7 -6
- package/src/components/Alert/Alert.test.tsx +50 -10
- package/src/components/Alert/Alert.tsx +5 -3
- package/src/components/Avatar/Avatar.test.tsx +95 -43
- package/src/components/Avatar/Avatar.tsx +16 -16
- package/src/components/Button/Button.test.tsx +2 -1
- package/src/components/Button/Button.tsx +3 -3
- package/src/components/Calendar/Calendar.test.tsx +53 -37
- package/src/components/Calendar/Calendar.tsx +409 -82
- package/src/components/Card/Card.test.tsx +7 -4
- package/src/components/Card/Card.tsx +3 -6
- package/src/components/Checkbox/Checkbox.tsx +2 -2
- package/src/components/DataTable/components/ActionButtons.tsx +5 -5
- package/src/components/DataTable/components/BulkOperationsDropdown.tsx +2 -2
- package/src/components/DataTable/components/ColumnFilter.tsx +1 -1
- package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +3 -3
- package/src/components/DataTable/components/DataTableBody.tsx +12 -12
- package/src/components/DataTable/components/DataTableCore.tsx +3 -3
- package/src/components/DataTable/components/DataTableToolbar.tsx +5 -5
- package/src/components/DataTable/components/DraggableColumnHeader.tsx +3 -3
- package/src/components/DataTable/components/EditableRow.tsx +2 -2
- package/src/components/DataTable/components/EmptyState.tsx +3 -3
- package/src/components/DataTable/components/GroupHeader.tsx +2 -2
- package/src/components/DataTable/components/GroupingDropdown.tsx +1 -1
- package/src/components/DataTable/components/ImportModal.tsx +4 -4
- package/src/components/DataTable/components/LoadingState.tsx +1 -1
- package/src/components/DataTable/components/PaginationControls.tsx +11 -11
- package/src/components/DataTable/components/UnifiedTableBody.tsx +9 -9
- package/src/components/DataTable/components/ViewRowModal.tsx +2 -2
- package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +11 -37
- package/src/components/DataTable/components/__tests__/DataTableToolbar.test.tsx +157 -0
- package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +2 -1
- package/src/components/DataTable/components/__tests__/VirtualizedDataTable.test.tsx +128 -0
- package/src/components/DataTable/core/__tests__/ActionManager.test.ts +19 -0
- package/src/components/DataTable/core/__tests__/ColumnFactory.test.ts +51 -0
- package/src/components/DataTable/core/__tests__/ColumnManager.test.ts +84 -0
- package/src/components/DataTable/core/__tests__/DataManager.test.ts +14 -0
- package/src/components/DataTable/core/__tests__/DataTableContext.test.tsx +136 -0
- package/src/components/DataTable/core/__tests__/LocalDataAdapter.test.ts +16 -0
- package/src/components/DataTable/core/__tests__/PluginRegistry.test.ts +18 -0
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +28 -7
- package/src/components/DataTable/utils/__tests__/hierarchicalUtils.test.ts +30 -1
- package/src/components/DataTable/utils/hierarchicalUtils.ts +38 -10
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +8 -3
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +4 -4
- package/src/components/Dialog/Dialog.tsx +2 -2
- package/src/components/EventSelector/EventSelector.tsx +7 -7
- package/src/components/FileDisplay/FileDisplay.tsx +291 -179
- package/src/components/FileUpload/FileUpload.tsx +7 -4
- package/src/components/Header/Header.test.tsx +28 -0
- package/src/components/Header/Header.tsx +22 -9
- package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +2 -2
- package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +19 -14
- package/src/components/LoadingSpinner/LoadingSpinner.tsx +5 -5
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +127 -1
- package/src/components/OrganisationSelector/OrganisationSelector.tsx +42 -22
- package/src/components/PaceAppLayout/PaceAppLayout.integration.test.tsx +4 -0
- package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +3 -0
- package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +3 -0
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +16 -6
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +37 -3
- package/src/components/PaceAppLayout/test-setup.tsx +1 -0
- package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +66 -45
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +6 -4
- package/src/components/Progress/Progress.test.tsx +18 -19
- package/src/components/Progress/Progress.tsx +31 -32
- package/src/components/PublicLayout/PublicLayout.test.tsx +6 -6
- package/src/components/PublicLayout/PublicPageProvider.tsx +5 -3
- package/src/components/Select/Select.test.tsx +4 -1
- package/src/components/Select/Select.tsx +65 -20
- package/src/components/Switch/Switch.test.tsx +2 -1
- package/src/components/Switch/Switch.tsx +1 -1
- package/src/components/Toast/Toast.tsx +1 -1
- package/src/components/Tooltip/Tooltip.test.tsx +8 -2
- package/src/components/UserMenu/UserMenu.tsx +3 -3
- package/src/eslint-rules/pace-core-compliance.cjs +0 -2
- package/src/eslint-rules/pace-core-compliance.js +0 -2
- package/src/hooks/__tests__/hooks.integration.test.tsx +4 -1
- package/src/hooks/__tests__/useAppConfig.unit.test.ts +76 -5
- package/src/hooks/__tests__/useDataTableState.test.ts +76 -0
- package/src/hooks/__tests__/useFileUrl.unit.test.ts +25 -69
- package/src/hooks/__tests__/useFileUrlCache.test.ts +129 -0
- package/src/hooks/__tests__/usePreventTabReload.test.ts +88 -0
- package/src/hooks/__tests__/usePublicEvent.simple.test.ts +1 -1
- package/src/hooks/__tests__/usePublicEvent.test.ts +608 -0
- package/src/hooks/__tests__/useQueryCache.test.ts +144 -0
- package/src/hooks/__tests__/useSecureDataAccess.unit.test.tsx +67 -24
- package/src/hooks/index.ts +1 -1
- package/src/hooks/public/usePublicEvent.ts +10 -10
- package/src/hooks/public/usePublicFileDisplay.ts +173 -87
- package/src/hooks/useAppConfig.ts +24 -5
- package/src/hooks/useFileDisplay.ts +298 -36
- package/src/hooks/useFileReference.ts +56 -11
- package/src/hooks/useFileUrl.ts +1 -1
- package/src/hooks/useInactivityTracker.ts +16 -7
- package/src/hooks/usePermissionCache.test.ts +85 -8
- package/src/hooks/useQueryCache.ts +27 -6
- package/src/hooks/useSecureDataAccess.test.ts +87 -42
- package/src/hooks/useSecureDataAccess.ts +95 -48
- package/src/providers/__tests__/OrganisationProvider.test.tsx +27 -21
- package/src/providers/services/EventServiceProvider.tsx +37 -17
- package/src/providers/services/InactivityServiceProvider.tsx +4 -4
- package/src/providers/services/OrganisationServiceProvider.tsx +8 -1
- package/src/providers/services/UnifiedAuthProvider.tsx +115 -29
- package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +451 -0
- package/src/rbac/__tests__/engine.comprehensive.test.ts +12 -0
- package/src/rbac/__tests__/rbac-engine-core-logic.test.ts +8 -0
- package/src/rbac/__tests__/rbac-engine-simplified.test.ts +4 -0
- package/src/rbac/api.ts +240 -36
- package/src/rbac/cache-invalidation.ts +21 -7
- package/src/rbac/compliance/quick-fix-suggestions.ts +1 -1
- package/src/rbac/components/NavigationGuard.tsx +23 -63
- package/src/rbac/components/NavigationProvider.test.tsx +52 -23
- package/src/rbac/components/NavigationProvider.tsx +13 -11
- package/src/rbac/components/PagePermissionGuard.tsx +77 -203
- package/src/rbac/components/PagePermissionProvider.tsx +13 -11
- package/src/rbac/components/PermissionEnforcer.tsx +24 -62
- package/src/rbac/components/RoleBasedRouter.tsx +14 -12
- package/src/rbac/components/SecureDataProvider.tsx +13 -11
- package/src/rbac/components/__tests__/NavigationGuard.test.tsx +104 -41
- package/src/rbac/components/__tests__/NavigationProvider.test.tsx +49 -12
- package/src/rbac/components/__tests__/PagePermissionGuard.race-condition.test.tsx +22 -1
- package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +161 -82
- package/src/rbac/components/__tests__/PagePermissionGuard.verification.test.tsx +22 -1
- package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +77 -30
- package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +39 -5
- package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +47 -4
- package/src/rbac/engine.ts +4 -2
- package/src/rbac/hooks/__tests__/useSecureSupabase.test.ts +144 -52
- package/src/rbac/hooks/index.ts +3 -0
- package/src/rbac/hooks/useCan.test.ts +101 -53
- package/src/rbac/hooks/usePermissions.ts +108 -41
- package/src/rbac/hooks/useRBAC.test.ts +11 -3
- package/src/rbac/hooks/useRBAC.ts +83 -40
- package/src/rbac/hooks/useResolvedScope.test.ts +189 -63
- package/src/rbac/hooks/useResolvedScope.ts +128 -70
- package/src/rbac/hooks/useSecureSupabase.ts +36 -19
- package/src/rbac/hooks/useSuperAdminBypass.ts +126 -0
- package/src/rbac/request-deduplication.ts +1 -1
- package/src/rbac/secureClient.ts +72 -12
- package/src/rbac/security.ts +29 -23
- package/src/rbac/types.ts +10 -0
- package/src/rbac/utils/__tests__/contextValidator.test.ts +150 -0
- package/src/rbac/utils/__tests__/deep-equal.test.ts +53 -0
- package/src/rbac/utils/__tests__/eventContext.test.ts +8 -3
- package/src/rbac/utils/__tests__/eventContext.unit.test.ts +74 -12
- package/src/rbac/utils/contextValidator.ts +288 -0
- package/src/rbac/utils/eventContext.ts +52 -3
- package/src/services/AuthService.ts +37 -8
- package/src/services/EventService.ts +165 -21
- package/src/services/OrganisationService.ts +125 -137
- package/src/services/__tests__/EventService.test.ts +26 -21
- package/src/services/__tests__/OrganisationService.pagination.test.ts +34 -8
- package/src/services/__tests__/OrganisationService.test.ts +218 -86
- package/src/types/database.generated.ts +166 -201
- package/src/types/file-reference.ts +13 -10
- package/src/types/supabase.ts +2 -2
- package/src/utils/__tests__/secureDataAccess.unit.test.ts +3 -2
- package/src/utils/app/appNameResolver.test.ts +346 -73
- package/src/utils/context/superAdminOverride.ts +58 -0
- package/src/utils/file-reference/index.ts +65 -37
- package/src/utils/google-places/googlePlacesUtils.test.ts +98 -0
- package/src/utils/google-places/googlePlacesUtils.ts +1 -1
- package/src/utils/google-places/loadGoogleMapsScript.test.ts +83 -0
- package/src/utils/google-places/types.ts +1 -1
- package/src/utils/request-deduplication.ts +4 -4
- package/src/utils/security/secureDataAccess.test.ts +1 -1
- package/src/utils/security/secureDataAccess.ts +7 -4
- package/src/utils/storage/README.md +1 -1
- package/src/utils/storage/helpers.test.ts +1 -1
- package/src/utils/storage/helpers.ts +38 -19
- package/src/utils/storage/types.ts +15 -8
- package/src/utils/validation/__tests__/csrf.test.ts +105 -0
- package/src/utils/validation/__tests__/sqlInjectionProtection.test.ts +92 -0
- package/src/vite-env.d.ts +2 -2
- package/dist/chunk-3GOZZZYH.js.map +0 -1
- package/dist/chunk-DDM4CCYT.js.map +0 -1
- package/dist/chunk-E7UAOUMY.js +0 -75
- package/dist/chunk-E7UAOUMY.js.map +0 -1
- package/dist/chunk-F2IMUDXZ.js.map +0 -1
- package/dist/chunk-HEHYGYOX.js.map +0 -1
- package/dist/chunk-IM4QE42D.js.map +0 -1
- package/dist/chunk-MX64ZF6I.js.map +0 -1
- package/dist/chunk-SAUPYVLF.js.map +0 -1
- package/dist/chunk-THRPYOFK.js.map +0 -1
- package/dist/chunk-UCQSRW7Z.js.map +0 -1
- package/dist/chunk-VGZZXKBR.js.map +0 -1
- package/dist/chunk-YGPFYGA6.js.map +0 -1
- package/dist/chunk-YHCN776L.js.map +0 -1
- /package/dist/{DataTable-GUFUNZ3N.js.map → DataTable-WKRZD47S.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-643PUAIM.js.map → UnifiedAuthProvider-FTSG5XH7.js.map} +0 -0
- /package/dist/{api-YP7XD5L6.js.map → api-IHKALJZD.js.map} +0 -0
- /package/dist/{chunk-HESYZWZW.js.map → chunk-QWWZ5CAQ.js.map} +0 -0
|
@@ -12,8 +12,15 @@
|
|
|
12
12
|
* - Debounced input with caching
|
|
13
13
|
* - Keyboard navigation (Arrow keys, Enter, Escape)
|
|
14
14
|
* - Accessible ARIA attributes
|
|
15
|
+
* - Semantic HTML (description list for suggestions)
|
|
15
16
|
* - Loading and error states
|
|
16
17
|
* - place_id storage for later retrieval
|
|
18
|
+
*
|
|
19
|
+
* @accessibility
|
|
20
|
+
* - Uses semantic HTML: `<dl>`, `<dt>`, `<dd>` for address suggestions
|
|
21
|
+
* - Proper ARIA attributes for combobox pattern
|
|
22
|
+
* - Keyboard navigation support
|
|
23
|
+
* - Screen reader friendly
|
|
17
24
|
*/
|
|
18
25
|
|
|
19
26
|
import * as React from 'react';
|
|
@@ -74,8 +81,8 @@ const AddressField = React.forwardRef<HTMLInputElement, AddressFieldProps>(
|
|
|
74
81
|
const [inputFocused, setInputFocused] = React.useState(false);
|
|
75
82
|
|
|
76
83
|
const inputRef = React.useRef<HTMLInputElement>(null);
|
|
77
|
-
const suggestionsRef = React.useRef<
|
|
78
|
-
const containerRef = React.useRef<
|
|
84
|
+
const suggestionsRef = React.useRef<HTMLDListElement>(null);
|
|
85
|
+
const containerRef = React.useRef<HTMLFormElement>(null);
|
|
79
86
|
|
|
80
87
|
// Use controlled or uncontrolled value
|
|
81
88
|
const value = controlledValue !== undefined ? controlledValue : internalValue;
|
|
@@ -216,57 +223,61 @@ const AddressField = React.forwardRef<HTMLInputElement, AddressFieldProps>(
|
|
|
216
223
|
}
|
|
217
224
|
}, [isOpen]);
|
|
218
225
|
|
|
226
|
+
// Generate unique ID for suggestions list
|
|
227
|
+
const suggestionsId = React.useId();
|
|
228
|
+
|
|
219
229
|
// Scroll selected item into view
|
|
230
|
+
// Uses getElementById instead of querySelector to handle React.useId() generated IDs
|
|
231
|
+
// which may contain colons (e.g., ':r15:') that are invalid in CSS selectors
|
|
220
232
|
React.useEffect(() => {
|
|
221
|
-
if (selectedIndex >= 0
|
|
222
|
-
const selectedItem =
|
|
233
|
+
if (selectedIndex >= 0) {
|
|
234
|
+
const selectedItem = document.getElementById(
|
|
235
|
+
`${suggestionsId}-item-${selectedIndex}`
|
|
236
|
+
) as HTMLElement;
|
|
223
237
|
if (selectedItem && typeof selectedItem.scrollIntoView === 'function') {
|
|
224
238
|
selectedItem.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
|
|
225
239
|
}
|
|
226
240
|
}
|
|
227
|
-
}, [selectedIndex]);
|
|
241
|
+
}, [selectedIndex, suggestionsId]);
|
|
228
242
|
|
|
229
243
|
// Combine refs
|
|
230
244
|
React.useImperativeHandle(ref, () => inputRef.current as HTMLInputElement);
|
|
231
245
|
|
|
232
|
-
const suggestionsId = React.useId();
|
|
233
246
|
const hasError = error || !!autocompleteError;
|
|
234
247
|
|
|
235
248
|
return (
|
|
236
|
-
<
|
|
237
|
-
<
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
<
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
)}
|
|
266
|
-
</div>
|
|
249
|
+
<form ref={containerRef} className={cn('relative w-full', className)}>
|
|
250
|
+
<Input
|
|
251
|
+
ref={inputRef}
|
|
252
|
+
type="text"
|
|
253
|
+
value={value}
|
|
254
|
+
onChange={handleInputChange}
|
|
255
|
+
onKeyDown={handleKeyDown}
|
|
256
|
+
onFocus={handleFocus}
|
|
257
|
+
onBlur={handleBlur}
|
|
258
|
+
placeholder={placeholder}
|
|
259
|
+
disabled={disabled}
|
|
260
|
+
error={hasError}
|
|
261
|
+
size={size}
|
|
262
|
+
variant={variant}
|
|
263
|
+
role="combobox"
|
|
264
|
+
aria-expanded={isOpen}
|
|
265
|
+
aria-autocomplete="list"
|
|
266
|
+
aria-controls={suggestionsId}
|
|
267
|
+
aria-haspopup="listbox"
|
|
268
|
+
aria-activedescendant={
|
|
269
|
+
selectedIndex >= 0 ? `${suggestionsId}-item-${selectedIndex}` : undefined
|
|
270
|
+
}
|
|
271
|
+
{...props}
|
|
272
|
+
/>
|
|
273
|
+
{isLoading && (
|
|
274
|
+
<p className="absolute right-3 top-1/2 -translate-y-1/2 pointer-events-none">
|
|
275
|
+
<LoadingSpinner size="sm" />
|
|
276
|
+
</p>
|
|
277
|
+
)}
|
|
267
278
|
|
|
268
279
|
{isOpen && suggestions.length > 0 && (
|
|
269
|
-
<
|
|
280
|
+
<dl
|
|
270
281
|
ref={suggestionsRef}
|
|
271
282
|
id={suggestionsId}
|
|
272
283
|
role="listbox"
|
|
@@ -278,32 +289,32 @@ const AddressField = React.forwardRef<HTMLInputElement, AddressFieldProps>(
|
|
|
278
289
|
data-testid="address-suggestions"
|
|
279
290
|
>
|
|
280
291
|
{suggestions.map((suggestion, index) => (
|
|
281
|
-
<
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
292
|
+
<React.Fragment key={suggestion.place_id}>
|
|
293
|
+
<dt
|
|
294
|
+
id={`${suggestionsId}-item-${index}`}
|
|
295
|
+
role="option"
|
|
296
|
+
aria-selected={selectedIndex === index}
|
|
297
|
+
className={cn(
|
|
298
|
+
'px-3 py-2 cursor-pointer text-sm',
|
|
299
|
+
'hover:bg-main-100 focus:bg-main-100',
|
|
300
|
+
'border-b border-main-200 last:border-b-0',
|
|
301
|
+
selectedIndex === index && 'bg-main-100',
|
|
302
|
+
'font-medium text-main-900'
|
|
303
|
+
)}
|
|
304
|
+
onClick={() => handleSelectAddress(suggestion.place_id)}
|
|
305
|
+
onMouseEnter={() => setSelectedIndex(index)}
|
|
306
|
+
data-testid={`address-suggestion-${index}`}
|
|
307
|
+
>
|
|
297
308
|
{suggestion.structured_formatting?.main_text || suggestion.description}
|
|
298
|
-
</
|
|
309
|
+
</dt>
|
|
299
310
|
{suggestion.structured_formatting?.secondary_text && (
|
|
300
|
-
<
|
|
311
|
+
<dd className="px-3 pb-2 text-xs text-main-600 mt-0.5">
|
|
301
312
|
{suggestion.structured_formatting.secondary_text}
|
|
302
|
-
</
|
|
313
|
+
</dd>
|
|
303
314
|
)}
|
|
304
|
-
</
|
|
315
|
+
</React.Fragment>
|
|
305
316
|
))}
|
|
306
|
-
</
|
|
317
|
+
</dl>
|
|
307
318
|
)}
|
|
308
319
|
|
|
309
320
|
{autocompleteError && (
|
|
@@ -311,7 +322,7 @@ const AddressField = React.forwardRef<HTMLInputElement, AddressFieldProps>(
|
|
|
311
322
|
{autocompleteError.message}
|
|
312
323
|
</p>
|
|
313
324
|
)}
|
|
314
|
-
</
|
|
325
|
+
</form>
|
|
315
326
|
);
|
|
316
327
|
}
|
|
317
328
|
);
|
|
@@ -154,7 +154,7 @@ if (address) {
|
|
|
154
154
|
|
|
155
155
|
## ParsedAddress Type
|
|
156
156
|
|
|
157
|
-
The `onChange` callback receives a `ParsedAddress` object matching the `
|
|
157
|
+
The `onChange` callback receives a `ParsedAddress` object matching the `core_address` table structure:
|
|
158
158
|
|
|
159
159
|
```typescript
|
|
160
160
|
interface ParsedAddress {
|
|
@@ -202,7 +202,7 @@ The component uses intelligent caching to reduce API costs:
|
|
|
202
202
|
|
|
203
203
|
The `place_id` is a stable identifier for a location that doesn't change. It's crucial for:
|
|
204
204
|
|
|
205
|
-
1. **Storing in Database**: Save `place_id` in your `
|
|
205
|
+
1. **Storing in Database**: Save `place_id` in your `core_address` table
|
|
206
206
|
2. **Retrieving Later**: Use `getAddressByPlaceId()` to get full address without autocomplete
|
|
207
207
|
3. **Verification**: Verify addresses haven't changed
|
|
208
208
|
4. **Cost Efficiency**: Place details lookups are cheaper than autocomplete searches
|
|
@@ -215,7 +215,7 @@ The `place_id` is a stable identifier for a location that doesn't change. It's c
|
|
|
215
215
|
apiKey={apiKey}
|
|
216
216
|
onChange={async (address) => {
|
|
217
217
|
// Store in database
|
|
218
|
-
await supabase.from('
|
|
218
|
+
await supabase.from('core_address').insert({
|
|
219
219
|
place_id: address.place_id, // Store this!
|
|
220
220
|
full_address: address.full_address,
|
|
221
221
|
lat: address.lat,
|
|
@@ -242,6 +242,7 @@ const address = await getAddressByPlaceId(storedPlaceId, apiKey);
|
|
|
242
242
|
|
|
243
243
|
The component follows WCAG 2.1 guidelines:
|
|
244
244
|
|
|
245
|
+
- **Semantic HTML**: Uses description list (`<dl>`, `<dt>`, `<dd>`) for address suggestions, providing proper semantic meaning
|
|
245
246
|
- **ARIA Attributes**: Proper `role`, `aria-expanded`, `aria-autocomplete`, `aria-controls`
|
|
246
247
|
- **Keyboard Navigation**: Full keyboard support
|
|
247
248
|
- **Screen Readers**: Proper announcements and labels
|
|
@@ -256,16 +257,16 @@ The component handles various error scenarios:
|
|
|
256
257
|
- **Rate Limiting**: Informs user about quota limits
|
|
257
258
|
- **Invalid Requests**: Validates input and displays errors
|
|
258
259
|
|
|
259
|
-
## Integration with
|
|
260
|
+
## Integration with core_address Table
|
|
260
261
|
|
|
261
|
-
The `ParsedAddress` type matches the `
|
|
262
|
+
The `ParsedAddress` type matches the `core_address` table structure, making it easy to store results:
|
|
262
263
|
|
|
263
264
|
```tsx
|
|
264
265
|
<AddressField
|
|
265
266
|
apiKey={apiKey}
|
|
266
267
|
onChange={async (address) => {
|
|
267
268
|
const { data, error } = await supabase
|
|
268
|
-
.from('
|
|
269
|
+
.from('core_address')
|
|
269
270
|
.insert({
|
|
270
271
|
place_id: address.place_id,
|
|
271
272
|
full_address: address.full_address,
|
|
@@ -11,6 +11,19 @@ import { renderWithProviders } from '../../__tests__/helpers/test-utils';
|
|
|
11
11
|
|
|
12
12
|
describe('Alert Component', () => {
|
|
13
13
|
describe('Rendering', () => {
|
|
14
|
+
it('renders as semantic aside element', () => {
|
|
15
|
+
renderWithProviders(
|
|
16
|
+
<Alert>
|
|
17
|
+
<AlertTitle>Test Title</AlertTitle>
|
|
18
|
+
<AlertDescription>Test description</AlertDescription>
|
|
19
|
+
</Alert>
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
const alert = screen.getByRole('alert');
|
|
23
|
+
expect(alert.tagName).toBe('ASIDE');
|
|
24
|
+
expect(alert).toBeInTheDocument();
|
|
25
|
+
});
|
|
26
|
+
|
|
14
27
|
it('renders with default variant', () => {
|
|
15
28
|
renderWithProviders(
|
|
16
29
|
<Alert>
|
|
@@ -21,6 +34,7 @@ describe('Alert Component', () => {
|
|
|
21
34
|
|
|
22
35
|
const alert = screen.getByRole('alert');
|
|
23
36
|
expect(alert).toBeInTheDocument();
|
|
37
|
+
expect(alert.tagName).toBe('ASIDE');
|
|
24
38
|
expect(alert).toHaveClass('relative', 'w-full', 'rounded-lg', 'border', 'p-4');
|
|
25
39
|
});
|
|
26
40
|
|
|
@@ -34,6 +48,7 @@ describe('Alert Component', () => {
|
|
|
34
48
|
|
|
35
49
|
const alert = screen.getByRole('alert');
|
|
36
50
|
expect(alert).toBeInTheDocument();
|
|
51
|
+
expect(alert.tagName).toBe('ASIDE');
|
|
37
52
|
expect(alert).toHaveClass('border-destructive', 'text-destructive');
|
|
38
53
|
});
|
|
39
54
|
|
|
@@ -58,6 +73,7 @@ describe('Alert Component', () => {
|
|
|
58
73
|
);
|
|
59
74
|
|
|
60
75
|
const alert = screen.getByRole('alert');
|
|
76
|
+
expect(alert.tagName).toBe('ASIDE');
|
|
61
77
|
expect(alert).toHaveClass('custom-alert-class');
|
|
62
78
|
});
|
|
63
79
|
|
|
@@ -72,7 +88,7 @@ describe('Alert Component', () => {
|
|
|
72
88
|
});
|
|
73
89
|
|
|
74
90
|
it('forwards ref correctly', () => {
|
|
75
|
-
const ref = React.createRef<
|
|
91
|
+
const ref = React.createRef<HTMLElement>();
|
|
76
92
|
|
|
77
93
|
renderWithProviders(
|
|
78
94
|
<Alert ref={ref}>
|
|
@@ -80,7 +96,8 @@ describe('Alert Component', () => {
|
|
|
80
96
|
</Alert>
|
|
81
97
|
);
|
|
82
98
|
|
|
83
|
-
expect(ref.current).toBeInstanceOf(
|
|
99
|
+
expect(ref.current).toBeInstanceOf(HTMLElement);
|
|
100
|
+
expect(ref.current?.tagName).toBe('ASIDE');
|
|
84
101
|
expect(ref.current).toHaveAttribute('role', 'alert');
|
|
85
102
|
});
|
|
86
103
|
});
|
|
@@ -258,6 +275,7 @@ describe('Alert Component', () => {
|
|
|
258
275
|
|
|
259
276
|
const alert = screen.getByRole('alert');
|
|
260
277
|
expect(alert).toBeInTheDocument();
|
|
278
|
+
expect(alert.tagName).toBe('ASIDE');
|
|
261
279
|
});
|
|
262
280
|
|
|
263
281
|
it('does not have role="alert" for inline variant', () => {
|
|
@@ -283,6 +301,7 @@ describe('Alert Component', () => {
|
|
|
283
301
|
|
|
284
302
|
const alert = screen.getByRole('alert');
|
|
285
303
|
expect(alert).toBeInTheDocument();
|
|
304
|
+
expect(alert.tagName).toBe('ASIDE');
|
|
286
305
|
|
|
287
306
|
// Screen readers will announce the content within the alert
|
|
288
307
|
expect(screen.getByText('Important Notice')).toBeInTheDocument();
|
|
@@ -297,9 +316,11 @@ describe('Alert Component', () => {
|
|
|
297
316
|
</Alert>
|
|
298
317
|
);
|
|
299
318
|
|
|
319
|
+
const alert = screen.getByRole('alert');
|
|
300
320
|
const title = screen.getByRole('heading', { level: 5 });
|
|
301
321
|
const description = screen.getByText('Semantic description with proper heading structure');
|
|
302
322
|
|
|
323
|
+
expect(alert.tagName).toBe('ASIDE');
|
|
303
324
|
expect(title).toBeInTheDocument();
|
|
304
325
|
expect(description.tagName).toBe('P');
|
|
305
326
|
});
|
|
@@ -320,7 +341,9 @@ describe('Alert Component', () => {
|
|
|
320
341
|
</Alert>
|
|
321
342
|
);
|
|
322
343
|
|
|
323
|
-
|
|
344
|
+
const alert = screen.getByRole('alert');
|
|
345
|
+
expect(alert).toBeInTheDocument();
|
|
346
|
+
expect(alert.tagName).toBe('ASIDE');
|
|
324
347
|
expect(screen.getByText('⚠️')).toBeInTheDocument();
|
|
325
348
|
expect(screen.getByRole('button', { name: 'Dismiss' })).toBeInTheDocument();
|
|
326
349
|
});
|
|
@@ -334,7 +357,9 @@ describe('Alert Component', () => {
|
|
|
334
357
|
</Alert>
|
|
335
358
|
);
|
|
336
359
|
|
|
337
|
-
|
|
360
|
+
const alert = screen.getByRole('alert');
|
|
361
|
+
expect(alert).toBeInTheDocument();
|
|
362
|
+
expect(alert.tagName).toBe('ASIDE');
|
|
338
363
|
expect(screen.getByText('First description')).toBeInTheDocument();
|
|
339
364
|
expect(screen.getByText('Second description')).toBeInTheDocument();
|
|
340
365
|
});
|
|
@@ -346,7 +371,9 @@ describe('Alert Component', () => {
|
|
|
346
371
|
</Alert>
|
|
347
372
|
);
|
|
348
373
|
|
|
349
|
-
|
|
374
|
+
const alert = screen.getByRole('alert');
|
|
375
|
+
expect(alert).toBeInTheDocument();
|
|
376
|
+
expect(alert.tagName).toBe('ASIDE');
|
|
350
377
|
expect(screen.getByText('Description without title')).toBeInTheDocument();
|
|
351
378
|
});
|
|
352
379
|
|
|
@@ -357,7 +384,9 @@ describe('Alert Component', () => {
|
|
|
357
384
|
</Alert>
|
|
358
385
|
);
|
|
359
386
|
|
|
360
|
-
|
|
387
|
+
const alert = screen.getByRole('alert');
|
|
388
|
+
expect(alert).toBeInTheDocument();
|
|
389
|
+
expect(alert.tagName).toBe('ASIDE');
|
|
361
390
|
expect(screen.getByRole('heading', { level: 5 })).toHaveTextContent('Title without description');
|
|
362
391
|
});
|
|
363
392
|
});
|
|
@@ -368,6 +397,7 @@ describe('Alert Component', () => {
|
|
|
368
397
|
|
|
369
398
|
const alert = screen.getByRole('alert');
|
|
370
399
|
expect(alert).toBeInTheDocument();
|
|
400
|
+
expect(alert.tagName).toBe('ASIDE');
|
|
371
401
|
expect(alert).toBeEmptyDOMElement();
|
|
372
402
|
});
|
|
373
403
|
|
|
@@ -395,7 +425,9 @@ describe('Alert Component', () => {
|
|
|
395
425
|
);
|
|
396
426
|
|
|
397
427
|
// Should fallback to default behavior
|
|
398
|
-
|
|
428
|
+
const alert = screen.getByRole('alert');
|
|
429
|
+
expect(alert).toBeInTheDocument();
|
|
430
|
+
expect(alert.tagName).toBe('ASIDE');
|
|
399
431
|
});
|
|
400
432
|
|
|
401
433
|
it('handles rapid variant changes', () => {
|
|
@@ -424,7 +456,9 @@ describe('Alert Component', () => {
|
|
|
424
456
|
</Alert>
|
|
425
457
|
);
|
|
426
458
|
|
|
427
|
-
|
|
459
|
+
const alert = screen.getByRole('alert');
|
|
460
|
+
expect(alert).toBeInTheDocument();
|
|
461
|
+
expect(alert.tagName).toBe('ASIDE');
|
|
428
462
|
});
|
|
429
463
|
});
|
|
430
464
|
|
|
@@ -441,7 +475,9 @@ describe('Alert Component', () => {
|
|
|
441
475
|
</form>
|
|
442
476
|
);
|
|
443
477
|
|
|
444
|
-
|
|
478
|
+
const alert = screen.getByRole('alert');
|
|
479
|
+
expect(alert).toBeInTheDocument();
|
|
480
|
+
expect(alert.tagName).toBe('ASIDE');
|
|
445
481
|
expect(screen.getByRole('textbox')).toBeInTheDocument();
|
|
446
482
|
expect(screen.getByRole('button', { name: 'Submit' })).toBeInTheDocument();
|
|
447
483
|
});
|
|
@@ -462,6 +498,8 @@ describe('Alert Component', () => {
|
|
|
462
498
|
|
|
463
499
|
const alerts = screen.getAllByRole('alert');
|
|
464
500
|
expect(alerts).toHaveLength(2);
|
|
501
|
+
expect(alerts[0].tagName).toBe('ASIDE');
|
|
502
|
+
expect(alerts[1].tagName).toBe('ASIDE');
|
|
465
503
|
expect(alerts[0]).toHaveClass('bg-background', 'text-foreground');
|
|
466
504
|
expect(alerts[1]).toHaveClass('border-destructive', 'text-destructive');
|
|
467
505
|
});
|
|
@@ -482,7 +520,9 @@ describe('Alert Component', () => {
|
|
|
482
520
|
</Alert>
|
|
483
521
|
);
|
|
484
522
|
|
|
485
|
-
|
|
523
|
+
const alert = screen.getByRole('alert');
|
|
524
|
+
expect(alert).toBeInTheDocument();
|
|
525
|
+
expect(alert.tagName).toBe('ASIDE');
|
|
486
526
|
expect(screen.getByText('Complex Alert')).toBeInTheDocument();
|
|
487
527
|
expect(screen.getByText('This is a complex description with')).toBeInTheDocument();
|
|
488
528
|
expect(screen.getByText('Multiple elements')).toBeInTheDocument();
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
* Features:
|
|
11
11
|
* - Multiple visual variants (default, destructive, inline)
|
|
12
12
|
* - Title and description support
|
|
13
|
+
* - Semantic HTML: renders as `<aside>` element
|
|
13
14
|
* - ARIA role="alert" for accessibility
|
|
14
15
|
* - Keyboard and screen reader accessible
|
|
15
16
|
* - Composable with icons and actions
|
|
@@ -40,6 +41,7 @@
|
|
|
40
41
|
* ```
|
|
41
42
|
*
|
|
42
43
|
* @accessibility
|
|
44
|
+
* - Uses semantic HTML: `<aside>` element for better semantic meaning
|
|
43
45
|
* - Uses role="alert" for screen reader announcement
|
|
44
46
|
* - Title and description are semantically structured
|
|
45
47
|
* - Supports keyboard navigation and focus
|
|
@@ -65,8 +67,8 @@ const getAlertClasses = (variant: "default" | "destructive" | "inline" = "defaul
|
|
|
65
67
|
};
|
|
66
68
|
|
|
67
69
|
const Alert = React.forwardRef<
|
|
68
|
-
|
|
69
|
-
React.HTMLAttributes<
|
|
70
|
+
HTMLElement,
|
|
71
|
+
React.HTMLAttributes<HTMLElement> & { variant?: "default" | "destructive" | "inline" }
|
|
70
72
|
>(({ className, variant = "default", ...props }, ref) => {
|
|
71
73
|
const contextValue = React.useMemo(() => ({ variant }), [variant])
|
|
72
74
|
|
|
@@ -80,7 +82,7 @@ const Alert = React.forwardRef<
|
|
|
80
82
|
|
|
81
83
|
return (
|
|
82
84
|
<AlertContext.Provider value={contextValue}>
|
|
83
|
-
<
|
|
85
|
+
<aside
|
|
84
86
|
ref={ref}
|
|
85
87
|
className={cn(getAlertClasses(variant), className)}
|
|
86
88
|
role="alert"
|