@jmruthers/pace-core 0.5.183 → 0.5.185
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 +38 -0
- package/README.md +60 -1
- package/core-usage-manifest.json +312 -0
- package/dist/{DataTable-QAB34V6K.js → DataTable-IX2NBUTP.js} +6 -6
- package/dist/{DataTable-Bz8ffqyA.d.ts → DataTable-Z9NLVJh0.d.ts} +1 -1
- package/dist/{index-Bl--n7-T.d.ts → PublicPageProvider-BABf6JCh.d.ts} +21 -10
- package/dist/{UnifiedAuthProvider-7F6T4B6K.js → UnifiedAuthProvider-A4BCQRJY.js} +4 -2
- package/dist/{UnifiedAuthProvider-F86d7dSi.d.ts → UnifiedAuthProvider-BG0AL5eE.d.ts} +2 -1
- package/dist/{api-ROMBCNKU.js → api-BMFCXVQX.js} +2 -2
- package/dist/{chunk-RA3JUFMW.js → chunk-445GEP27.js} +154 -4
- package/dist/{chunk-RA3JUFMW.js.map → chunk-445GEP27.js.map} +1 -1
- package/dist/{chunk-CSOFYHAG.js → chunk-AISXLWGZ.js} +374 -60
- package/dist/chunk-AISXLWGZ.js.map +1 -0
- package/dist/{chunk-FUEYYMX5.js → chunk-FXFJRTKI.js} +24 -3
- package/dist/chunk-FXFJRTKI.js.map +1 -0
- package/dist/{chunk-QETLRQI6.js → chunk-HC67NW5K.js} +380 -360
- package/dist/chunk-HC67NW5K.js.map +1 -0
- package/dist/chunk-HESYZWZW.js +388 -0
- package/dist/chunk-HESYZWZW.js.map +1 -0
- package/dist/{chunk-QUVSNGIP.js → chunk-HGPQUCBC.js} +34 -9
- package/dist/{chunk-QUVSNGIP.js.map → chunk-HGPQUCBC.js.map} +1 -1
- package/dist/{chunk-UHNYIBXL.js → chunk-IXSNYUCT.js} +1 -1
- package/dist/chunk-IXSNYUCT.js.map +1 -0
- package/dist/{chunk-MI7HBHN3.js → chunk-MX3EIJGQ.js} +4 -3
- package/dist/{chunk-MI7HBHN3.js.map → chunk-MX3EIJGQ.js.map} +1 -1
- package/dist/{chunk-PWAHJW4G.js → chunk-OKI34GZD.js} +86 -33
- package/dist/chunk-OKI34GZD.js.map +1 -0
- package/dist/{chunk-W22JP75J.js → chunk-STTZQK2I.js} +3 -3
- package/dist/chunk-THRPYOFK.js +215 -0
- package/dist/chunk-THRPYOFK.js.map +1 -0
- package/dist/{chunk-M7W4CP3M.js → chunk-U6WNSFX5.js} +2 -1
- package/dist/chunk-U6WNSFX5.js.map +1 -0
- package/dist/{chunk-QCDXODCA.js → chunk-XAUHJD3L.js} +2 -2
- package/dist/components.d.ts +182 -6
- package/dist/components.js +157 -11
- package/dist/components.js.map +1 -1
- package/dist/eslint-rules/pace-core-compliance.cjs +406 -0
- package/dist/{file-reference-D06mEEWW.d.ts → file-reference-BjR39ktt.d.ts} +7 -1
- package/dist/hooks.d.ts +7 -14
- package/dist/hooks.js +10 -22
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +11 -11
- package/dist/index.js +79 -16
- package/dist/index.js.map +1 -1
- package/dist/providers.d.ts +1 -1
- package/dist/providers.js +3 -1
- package/dist/rbac/index.d.ts +205 -14
- package/dist/rbac/index.js +28 -6
- package/dist/timezone-_pgH8qrY.d.ts +530 -0
- package/dist/{types-_x1f4QBF.d.ts → types-DUyCRSTj.d.ts} +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/dist/{usePublicRouteParams-JJczomYq.d.ts → usePublicRouteParams-CvnC3d-e.d.ts} +113 -2
- package/dist/utils.d.ts +109 -151
- package/dist/utils.js +128 -138
- package/dist/utils.js.map +1 -1
- package/docs/api/README.md +60 -1
- package/docs/api/classes/ColumnFactory.md +1 -1
- package/docs/api/classes/ErrorBoundary.md +1 -1
- package/docs/api/classes/InvalidScopeError.md +1 -1
- package/docs/api/classes/Logger.md +178 -0
- package/docs/api/classes/MissingUserContextError.md +1 -1
- 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 +2 -2
- package/docs/api/classes/RBACError.md +1 -1
- package/docs/api/classes/RBACNotInitializedError.md +1 -1
- package/docs/api/classes/SecureSupabaseClient.md +5 -5
- package/docs/api/classes/StorageUtils.md +1 -1
- package/docs/api/enums/FileCategory.md +1 -1
- package/docs/api/enums/LogLevel.md +54 -0
- package/docs/api/enums/RBACErrorCode.md +1 -1
- package/docs/api/enums/RPCFunction.md +1 -1
- package/docs/api/interfaces/AggregateConfig.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 +18 -2
- 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 +30 -0
- package/docs/api/interfaces/DataAccessRecord.md +1 -1
- 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 +85 -0
- package/docs/api/interfaces/DatabaseIssue.md +41 -0
- package/docs/api/interfaces/EmptyStateConfig.md +1 -1
- package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
- package/docs/api/interfaces/EventAppRoleData.md +6 -6
- package/docs/api/interfaces/ExportColumn.md +1 -1
- package/docs/api/interfaces/ExportOptions.md +1 -1
- 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 +24 -8
- package/docs/api/interfaces/FileUploadProps.md +24 -13
- 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 +9 -9
- 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 +62 -0
- package/docs/api/interfaces/LoginFormProps.md +1 -1
- 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 +1 -1
- package/docs/api/interfaces/NavigationMenuProps.md +1 -1
- 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 +36 -23
- package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
- package/docs/api/interfaces/PageAccessRecord.md +1 -1
- package/docs/api/interfaces/PagePermissionContextType.md +1 -1
- package/docs/api/interfaces/PagePermissionGuardProps.md +11 -11
- package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
- package/docs/api/interfaces/PaletteData.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 +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 +52 -0
- 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 +4 -4
- 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/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 +7 -7
- package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
- package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
- package/docs/api/interfaces/RoleManagementResult.md +5 -5
- package/docs/api/interfaces/RouteAccessRecord.md +1 -1
- package/docs/api/interfaces/RouteConfig.md +1 -1
- package/docs/api/interfaces/RuntimeComplianceResult.md +55 -0
- package/docs/api/interfaces/SecureDataContextType.md +1 -1
- package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
- package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
- package/docs/api/interfaces/SetupIssue.md +41 -0
- 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 +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 +1 -1
- package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
- package/docs/api/interfaces/UseFormDialogOptions.md +62 -0
- package/docs/api/interfaces/UseFormDialogReturn.md +117 -0
- 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 +2 -2
- package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
- package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
- package/docs/api/interfaces/UserEventAccess.md +1 -1
- package/docs/api/interfaces/UserMenuProps.md +1 -1
- package/docs/api/interfaces/UserProfile.md +1 -1
- package/docs/api/modules.md +738 -42
- package/docs/api-reference/hooks.md +111 -0
- package/docs/api-reference/rpc-functions.md +1 -1
- package/docs/api-reference/utilities.md +184 -0
- package/docs/getting-started/installation-guide.md +75 -16
- package/docs/getting-started/quick-start.md +61 -11
- package/docs/implementation-guides/authentication.md +88 -12
- package/docs/implementation-guides/file-reference-system.md +2 -1
- package/docs/implementation-guides/file-upload-storage.md +21 -0
- package/docs/rbac/README.md +1 -0
- package/docs/rbac/compliance/compliance-guide.md +544 -0
- package/docs/rbac/getting-started.md +158 -33
- package/docs/standards/pace-core-compliance.md +432 -0
- package/eslint-config-pace-core.cjs +93 -0
- package/package.json +15 -3
- package/scripts/analyze-bundle.js +232 -0
- package/scripts/build-css.js +56 -0
- package/scripts/build-docs-incremental.js +1015 -0
- package/scripts/check-pace-core-compliance.cjs +2353 -0
- package/scripts/generate-docs.js +157 -0
- package/scripts/setup-build-cache.js +73 -0
- package/scripts/utils/command-runner.js +131 -0
- package/scripts/utils/env.js +33 -0
- package/scripts/utils/index.js +10 -0
- package/scripts/utils/logger.js +88 -0
- package/scripts/utils/path-helpers.js +37 -0
- package/scripts/validate-formats.js +133 -0
- package/scripts/validate-master.js +155 -0
- package/scripts/validate-pre-publish.js +140 -0
- package/scripts/validate-theme.js +142 -0
- package/src/components/Calendar/Calendar.tsx +8 -1
- package/src/components/Card/Card.tsx +47 -8
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +314 -0
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +126 -0
- package/src/components/DatePickerWithTimezone/README.md +135 -0
- package/src/components/DatePickerWithTimezone/index.ts +10 -0
- package/src/components/DateTimeField/DateTimeField.test.tsx +358 -0
- package/src/components/DateTimeField/DateTimeField.tsx +232 -0
- package/src/components/DateTimeField/README.md +148 -0
- package/src/components/DateTimeField/index.ts +10 -0
- package/src/components/FileUpload/FileUpload.tsx +3 -0
- package/src/components/Header/Header.test.tsx +47 -18
- package/src/components/Header/Header.tsx +24 -6
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +29 -20
- package/src/components/PaceAppLayout/README.md +9 -0
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +1 -1
- package/src/components/ProtectedRoute/ProtectedRoute.test.tsx +37 -8
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +12 -4
- package/src/components/index.ts +8 -0
- package/src/eslint-rules/pace-core-compliance.cjs +406 -0
- package/src/eslint-rules/pace-core-compliance.js +640 -0
- package/src/hooks/__tests__/useFormDialog.test.ts +478 -0
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useFileReference.test.ts +1 -0
- package/src/hooks/useFormDialog.ts +147 -0
- package/src/index.ts +27 -0
- package/src/providers/services/OrganisationServiceProvider.tsx +6 -5
- package/src/providers/services/UnifiedAuthProvider.tsx +24 -3
- package/src/rbac/__tests__/scenarios.user-role.test.tsx +3 -0
- package/src/rbac/compliance/database-validator.ts +165 -0
- package/src/rbac/compliance/index.ts +38 -0
- package/src/rbac/compliance/quick-fix-suggestions.ts +209 -0
- package/src/rbac/compliance/runtime-compliance.ts +77 -0
- package/src/rbac/compliance/setup-validator.ts +131 -0
- package/src/rbac/components/PagePermissionGuard.tsx +8 -64
- package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +35 -21
- package/src/rbac/docs/event-based-apps.md +285 -0
- package/src/rbac/errors.ts +11 -0
- package/src/rbac/hooks/useRoleManagement.ts +292 -12
- package/src/rbac/index.ts +30 -0
- package/src/services/OrganisationService.ts +4 -0
- package/src/types/file-reference.ts +6 -0
- package/src/utils/__tests__/timezone.test.ts +345 -0
- package/src/utils/file-reference/__tests__/file-reference.test.ts +2 -0
- package/src/utils/file-reference/index.ts +1 -0
- package/src/utils/formatting/formatDateTimeTimezone.test.ts +167 -0
- package/src/utils/formatting/formatting.ts +179 -0
- package/src/utils/index.ts +27 -1
- package/src/utils/location/index.ts +16 -0
- package/src/utils/location/location.test.ts +286 -0
- package/src/utils/location/location.ts +175 -0
- package/src/utils/timezone/index.ts +17 -0
- package/src/utils/timezone/timezone.test.ts +349 -0
- package/src/utils/timezone/timezone.ts +281 -0
- package/dist/chunk-CSOFYHAG.js.map +0 -1
- package/dist/chunk-FUEYYMX5.js.map +0 -1
- package/dist/chunk-HKIT6O7W.js +0 -198
- package/dist/chunk-HKIT6O7W.js.map +0 -1
- package/dist/chunk-KUEN3HFB.js +0 -94
- package/dist/chunk-KUEN3HFB.js.map +0 -1
- package/dist/chunk-M7W4CP3M.js.map +0 -1
- package/dist/chunk-PWAHJW4G.js.map +0 -1
- package/dist/chunk-QETLRQI6.js.map +0 -1
- package/dist/chunk-UHNYIBXL.js.map +0 -1
- package/dist/formatting-5wETwiGF.d.ts +0 -162
- /package/dist/{DataTable-QAB34V6K.js.map → DataTable-IX2NBUTP.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-7F6T4B6K.js.map → UnifiedAuthProvider-A4BCQRJY.js.map} +0 -0
- /package/dist/{api-ROMBCNKU.js.map → api-BMFCXVQX.js.map} +0 -0
- /package/dist/{chunk-W22JP75J.js.map → chunk-STTZQK2I.js.map} +0 -0
- /package/dist/{chunk-QCDXODCA.js.map → chunk-XAUHJD3L.js.map} +0 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Master Validation Script
|
|
3
|
+
* Consolidated validation system for PACE Core package
|
|
4
|
+
* Now uses standardized test execution consistent with npm run test
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { join } from 'path';
|
|
8
|
+
import {
|
|
9
|
+
runCommand,
|
|
10
|
+
runStandardizedTests,
|
|
11
|
+
} from './utils/command-runner.js';
|
|
12
|
+
import { getPackageRoot, getRepoRoot } from './utils/path-helpers.js';
|
|
13
|
+
import { isCI, getDefaultTimeout, getTestTimeout } from './utils/env.js';
|
|
14
|
+
import { logger } from './utils/logger.js';
|
|
15
|
+
|
|
16
|
+
const packageRoot = getPackageRoot(import.meta.url);
|
|
17
|
+
const repoRoot = getRepoRoot(import.meta.url);
|
|
18
|
+
const DEFAULT_TIMEOUT = getDefaultTimeout(30000, 120000);
|
|
19
|
+
const TEST_TIMEOUT = getTestTimeout(300000, 300000);
|
|
20
|
+
|
|
21
|
+
// Individual validation steps - only include scripts that actually exist
|
|
22
|
+
const validationSteps = {
|
|
23
|
+
'type-check': {
|
|
24
|
+
name: 'Type Check',
|
|
25
|
+
command: ['npm', 'run', 'type-check']
|
|
26
|
+
},
|
|
27
|
+
'test': {
|
|
28
|
+
name: 'Test Suite',
|
|
29
|
+
customHandler: runStandardizedTestsWrapper,
|
|
30
|
+
skipInPrePublish: true
|
|
31
|
+
},
|
|
32
|
+
'build': {
|
|
33
|
+
name: 'Package Build',
|
|
34
|
+
command: ['npm', 'run', 'build'],
|
|
35
|
+
timeout: isCI() ? 180000 : 60000
|
|
36
|
+
},
|
|
37
|
+
'build:docs': {
|
|
38
|
+
name: 'Documentation Generation',
|
|
39
|
+
command: ['npm', 'run', 'build:docs'],
|
|
40
|
+
cwd: packageRoot,
|
|
41
|
+
timeout: isCI() ? 240000 : 120000
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// Standardized test execution wrapper
|
|
46
|
+
async function runStandardizedTestsWrapper() {
|
|
47
|
+
return runStandardizedTests(packageRoot, repoRoot, TEST_TIMEOUT);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Main validation orchestrator
|
|
51
|
+
async function runValidation(mode = 'standard') {
|
|
52
|
+
logger.header(`Running ${mode} validation`);
|
|
53
|
+
if (isCI()) {
|
|
54
|
+
logger.info('CI environment detected - using extended timeouts and live output');
|
|
55
|
+
logger.newline();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const results = [];
|
|
59
|
+
const startTime = Date.now();
|
|
60
|
+
let criticalFailures = 0;
|
|
61
|
+
|
|
62
|
+
for (const [stepKey, step] of Object.entries(validationSteps)) {
|
|
63
|
+
// Skip tests in pre-publish mode if configured to do so
|
|
64
|
+
if (mode === 'pre-publish' && step.skipInPrePublish) {
|
|
65
|
+
logger.skipped(`Skipping: ${step.name} (skipped in pre-publish mode)...`);
|
|
66
|
+
results.push({
|
|
67
|
+
step: stepKey,
|
|
68
|
+
name: step.name,
|
|
69
|
+
success: true,
|
|
70
|
+
duration: 0,
|
|
71
|
+
skipped: true
|
|
72
|
+
});
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const stepStartTime = Date.now();
|
|
77
|
+
logger.step(`Running: ${step.name}...`);
|
|
78
|
+
|
|
79
|
+
const result = step.customHandler
|
|
80
|
+
? await step.customHandler()
|
|
81
|
+
: await runCommand(step.command[0], step.command.slice(1), {
|
|
82
|
+
cwd: step.cwd || packageRoot,
|
|
83
|
+
timeout: step.timeout || DEFAULT_TIMEOUT,
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const stepDuration = Date.now() - stepStartTime;
|
|
87
|
+
const status = result.success ? '✅' : '❌';
|
|
88
|
+
|
|
89
|
+
logger.info(`${status} ${step.name} - ${stepDuration}ms`);
|
|
90
|
+
logger.newline();
|
|
91
|
+
|
|
92
|
+
results.push({
|
|
93
|
+
step: stepKey,
|
|
94
|
+
name: step.name,
|
|
95
|
+
success: result.success,
|
|
96
|
+
duration: stepDuration,
|
|
97
|
+
skipped: false
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
if (!result.success) {
|
|
101
|
+
criticalFailures++;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const totalDuration = Date.now() - startTime;
|
|
106
|
+
const passedSteps = results.filter(r => r.success).length;
|
|
107
|
+
const skippedSteps = results.filter(r => r.skipped).length;
|
|
108
|
+
|
|
109
|
+
// Summary
|
|
110
|
+
logger.header('VALIDATION SUMMARY');
|
|
111
|
+
logger.info(`Total steps: ${results.length}`);
|
|
112
|
+
logger.info(`Passed: ${passedSteps}`);
|
|
113
|
+
logger.info(`Skipped: ${skippedSteps}`);
|
|
114
|
+
logger.info(`Failed: ${criticalFailures}`);
|
|
115
|
+
logger.info(`Total duration: ${totalDuration}ms`);
|
|
116
|
+
logger.newline();
|
|
117
|
+
|
|
118
|
+
logger.info('Step Results:');
|
|
119
|
+
results.forEach(result => {
|
|
120
|
+
const status = result.skipped ? '⏭️' : (result.success ? '✅' : '❌');
|
|
121
|
+
const statusText = result.skipped ? 'skipped' : `${result.duration}ms`;
|
|
122
|
+
logger.info(` ${status} ${result.step} - ${statusText}`);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
logger.newline();
|
|
126
|
+
logger.separator();
|
|
127
|
+
|
|
128
|
+
if (criticalFailures === 0) {
|
|
129
|
+
logger.success('All validations passed!');
|
|
130
|
+
return true;
|
|
131
|
+
} else {
|
|
132
|
+
logger.error(`Validation failed! ${criticalFailures} validations failed`);
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// CLI handling
|
|
138
|
+
(async () => {
|
|
139
|
+
const mode = process.argv[2] || 'standard';
|
|
140
|
+
const success = await runValidation(mode);
|
|
141
|
+
|
|
142
|
+
if (!success) {
|
|
143
|
+
logger.newline();
|
|
144
|
+
logger.error('Package validation failed!');
|
|
145
|
+
logger.info('Please fix the issues above before publishing');
|
|
146
|
+
process.exit(1);
|
|
147
|
+
} else {
|
|
148
|
+
logger.newline();
|
|
149
|
+
logger.success('Package validation successful!');
|
|
150
|
+
process.exit(0);
|
|
151
|
+
}
|
|
152
|
+
})().catch(error => {
|
|
153
|
+
logger.error(`Validation script error: ${error.message}`);
|
|
154
|
+
process.exit(1);
|
|
155
|
+
});
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pre-publish Validation Script
|
|
5
|
+
* Now uses standardized test execution consistent with npm run test
|
|
6
|
+
* Simplified to rely on vitest exit codes rather than output parsing
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
import { existsSync } from 'fs';
|
|
11
|
+
import {
|
|
12
|
+
runCommand,
|
|
13
|
+
runStandardizedTests,
|
|
14
|
+
} from './utils/command-runner.js';
|
|
15
|
+
import { getPackageRoot, getRepoRoot } from './utils/path-helpers.js';
|
|
16
|
+
import { getDefaultTimeout, getTestTimeout } from './utils/env.js';
|
|
17
|
+
import { logger } from './utils/logger.js';
|
|
18
|
+
|
|
19
|
+
const packageRoot = getPackageRoot(import.meta.url);
|
|
20
|
+
const repoRoot = getRepoRoot(import.meta.url);
|
|
21
|
+
const DEFAULT_TIMEOUT = getDefaultTimeout(30000, 120000);
|
|
22
|
+
const TEST_TIMEOUT = getTestTimeout(300000, 300000);
|
|
23
|
+
|
|
24
|
+
logger.header('Running pre-publish validation');
|
|
25
|
+
|
|
26
|
+
// Check if typedoc and its plugins are installed
|
|
27
|
+
function checkTypedocInstallation() {
|
|
28
|
+
const requiredPackages = [
|
|
29
|
+
'typedoc',
|
|
30
|
+
'typedoc-plugin-markdown',
|
|
31
|
+
'typedoc-plugin-merge-modules'
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
logger.info('Checking TypeDoc installation...');
|
|
35
|
+
|
|
36
|
+
// Check for specific packages
|
|
37
|
+
const missingPackages = requiredPackages.filter(pkg => {
|
|
38
|
+
const pkgPath = join(packageRoot, 'node_modules', pkg);
|
|
39
|
+
return !existsSync(pkgPath);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
if (missingPackages.length > 0) {
|
|
43
|
+
logger.error(`Missing required packages: ${missingPackages.join(', ')}`);
|
|
44
|
+
logger.info(`Please run: npm install --save-dev ${missingPackages.join(' ')}`);
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
logger.success('TypeDoc installation verified');
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Main validation function
|
|
53
|
+
async function runPrePublishValidation() {
|
|
54
|
+
logger.info('Running validation steps...');
|
|
55
|
+
logger.newline();
|
|
56
|
+
|
|
57
|
+
// Step 1: Type check
|
|
58
|
+
logger.step('Step 1: Type check');
|
|
59
|
+
const typeCheck = await runCommand('npm', ['run', 'type-check'], {
|
|
60
|
+
cwd: packageRoot,
|
|
61
|
+
timeout: DEFAULT_TIMEOUT,
|
|
62
|
+
});
|
|
63
|
+
if (!typeCheck.success) {
|
|
64
|
+
logger.error('Type check failed');
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Step 2: Build validation
|
|
69
|
+
logger.newline();
|
|
70
|
+
logger.step('Step 2: Build validation');
|
|
71
|
+
const build = await runCommand('npm', ['run', 'build'], {
|
|
72
|
+
cwd: packageRoot,
|
|
73
|
+
timeout: getDefaultTimeout(60000, 180000),
|
|
74
|
+
});
|
|
75
|
+
if (!build.success) {
|
|
76
|
+
logger.error('Build failed');
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Step 3: Test validation - now uses standardized test execution
|
|
81
|
+
logger.newline();
|
|
82
|
+
logger.step('Step 3: Test validation');
|
|
83
|
+
const tests = await runStandardizedTests(packageRoot, repoRoot, TEST_TIMEOUT);
|
|
84
|
+
if (!tests.success) {
|
|
85
|
+
logger.error('Tests failed');
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Step 4: Documentation generation
|
|
90
|
+
logger.newline();
|
|
91
|
+
logger.step('Step 4: Documentation generation');
|
|
92
|
+
if (!checkTypedocInstallation()) {
|
|
93
|
+
logger.error('TypeDoc validation failed');
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const docs = await runCommand('npm', ['run', 'build:docs'], {
|
|
98
|
+
cwd: packageRoot,
|
|
99
|
+
timeout: getDefaultTimeout(120000, 240000),
|
|
100
|
+
});
|
|
101
|
+
if (!docs.success) {
|
|
102
|
+
logger.error('Documentation generation failed');
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Step 5: Format validation
|
|
107
|
+
logger.newline();
|
|
108
|
+
logger.step('Step 5: Format validation');
|
|
109
|
+
const format = await runCommand('npm', ['run', 'format:check'], {
|
|
110
|
+
cwd: packageRoot,
|
|
111
|
+
timeout: DEFAULT_TIMEOUT,
|
|
112
|
+
});
|
|
113
|
+
if (!format.success) {
|
|
114
|
+
logger.error('Format validation failed');
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
logger.newline();
|
|
119
|
+
logger.header('PRE-PUBLISH VALIDATION SUMMARY');
|
|
120
|
+
logger.success('All validation steps passed successfully!');
|
|
121
|
+
logger.success('Package meets publication requirements');
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Run the validation
|
|
126
|
+
(async () => {
|
|
127
|
+
const success = await runPrePublishValidation();
|
|
128
|
+
if (!success) {
|
|
129
|
+
logger.newline();
|
|
130
|
+
logger.info('Required fixes:');
|
|
131
|
+
logger.info('- Fix validation failures');
|
|
132
|
+
logger.info('- Resolve build or type check errors');
|
|
133
|
+
process.exit(1);
|
|
134
|
+
} else {
|
|
135
|
+
process.exit(0);
|
|
136
|
+
}
|
|
137
|
+
})().catch(error => {
|
|
138
|
+
logger.error(`Pre-publish validation error: ${error.message}`);
|
|
139
|
+
process.exit(1);
|
|
140
|
+
});
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Theme Validation Script
|
|
5
|
+
*
|
|
6
|
+
* Validates that the Tailwind v4 theme file is properly structured
|
|
7
|
+
* and doesn't have common issues like circular references or missing colors.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { readFileSync } from 'fs';
|
|
11
|
+
import { join } from 'path';
|
|
12
|
+
import { getPackageRoot } from './utils/path-helpers.js';
|
|
13
|
+
import { logger } from './utils/logger.js';
|
|
14
|
+
|
|
15
|
+
const packageRoot = getPackageRoot(import.meta.url);
|
|
16
|
+
const THEME_FILE = join(packageRoot, 'src/styles/core.css');
|
|
17
|
+
|
|
18
|
+
// Required semantic colors for Tailwind v4
|
|
19
|
+
const REQUIRED_SEMANTIC_COLORS = [
|
|
20
|
+
'--color-ring',
|
|
21
|
+
'--color-border',
|
|
22
|
+
'--color-input',
|
|
23
|
+
'--color-background',
|
|
24
|
+
'--color-foreground',
|
|
25
|
+
'--color-primary',
|
|
26
|
+
'--color-primary-foreground',
|
|
27
|
+
'--color-secondary',
|
|
28
|
+
'--color-secondary-foreground',
|
|
29
|
+
'--color-destructive',
|
|
30
|
+
'--color-destructive-foreground',
|
|
31
|
+
'--color-muted',
|
|
32
|
+
'--color-muted-foreground',
|
|
33
|
+
'--color-accent',
|
|
34
|
+
'--color-accent-foreground',
|
|
35
|
+
'--color-popover',
|
|
36
|
+
'--color-popover-foreground',
|
|
37
|
+
'--color-card',
|
|
38
|
+
'--color-card-foreground',
|
|
39
|
+
'--color-ring-offset'
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
// Component classes that should not have circular references
|
|
43
|
+
const COMPONENT_CLASSES = [
|
|
44
|
+
'.pace-button',
|
|
45
|
+
'.pace-button-primary',
|
|
46
|
+
'.pace-button-secondary',
|
|
47
|
+
'.pace-button-outline',
|
|
48
|
+
'.pace-button-ghost',
|
|
49
|
+
'.pace-button-destructive',
|
|
50
|
+
'.pace-input',
|
|
51
|
+
'.pace-card',
|
|
52
|
+
'.pace-card-header',
|
|
53
|
+
'.pace-card-title',
|
|
54
|
+
'.pace-card-description',
|
|
55
|
+
'.pace-card-content',
|
|
56
|
+
'.pace-card-footer'
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
function validateThemeFile() {
|
|
60
|
+
logger.info('Validating Tailwind v4 theme file...');
|
|
61
|
+
logger.newline();
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
const themeContent = readFileSync(THEME_FILE, 'utf8');
|
|
65
|
+
let errors = [];
|
|
66
|
+
let warnings = [];
|
|
67
|
+
|
|
68
|
+
// Check for required semantic colors
|
|
69
|
+
logger.success('Checking semantic colors...');
|
|
70
|
+
for (const color of REQUIRED_SEMANTIC_COLORS) {
|
|
71
|
+
if (!themeContent.includes(color)) {
|
|
72
|
+
errors.push(`Missing required semantic color: ${color}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Check for circular @apply references
|
|
77
|
+
logger.success('Checking for circular @apply references...');
|
|
78
|
+
for (const className of COMPONENT_CLASSES) {
|
|
79
|
+
const classRegex = new RegExp(`${className}\\s*\\{[^}]*@apply[^}]*${className}[^}]*\\}`, 's');
|
|
80
|
+
if (classRegex.test(themeContent)) {
|
|
81
|
+
errors.push(`Circular @apply reference found in ${className}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Check for undefined color references (skip if --color-*: initial is used)
|
|
86
|
+
logger.success('Checking for undefined color references...');
|
|
87
|
+
if (!themeContent.includes('--color-*: initial')) {
|
|
88
|
+
const colorRefs = themeContent.match(/--color-[a-zA-Z-]+/g) || [];
|
|
89
|
+
const definedColors = themeContent.match(/--color-[a-zA-Z-]+:/g) || [];
|
|
90
|
+
const definedColorNames = definedColors.map(c => c.replace(':', ''));
|
|
91
|
+
|
|
92
|
+
for (const ref of colorRefs) {
|
|
93
|
+
if (!definedColorNames.includes(ref)) {
|
|
94
|
+
warnings.push(`Potentially undefined color reference: ${ref}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Check for @theme declaration
|
|
100
|
+
logger.success('Checking for @theme declaration...');
|
|
101
|
+
if (!themeContent.includes('@theme {')) {
|
|
102
|
+
errors.push('Missing @theme declaration');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Check for @import tailwindcss
|
|
106
|
+
logger.success('Checking for Tailwind import...');
|
|
107
|
+
if (!themeContent.includes('@import "tailwindcss"')) {
|
|
108
|
+
errors.push('Missing @import "tailwindcss"');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Report results
|
|
112
|
+
logger.newline();
|
|
113
|
+
logger.header('Validation Results');
|
|
114
|
+
|
|
115
|
+
if (errors.length === 0 && warnings.length === 0) {
|
|
116
|
+
logger.success('All checks passed! Theme file is valid.');
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (errors.length > 0) {
|
|
121
|
+
logger.newline();
|
|
122
|
+
logger.error('Errors found:');
|
|
123
|
+
errors.forEach(error => console.log(` - ${error}`));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (warnings.length > 0) {
|
|
127
|
+
logger.newline();
|
|
128
|
+
logger.warning('Warnings:');
|
|
129
|
+
warnings.forEach(warning => console.log(` - ${warning}`));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return errors.length === 0;
|
|
133
|
+
|
|
134
|
+
} catch (error) {
|
|
135
|
+
logger.error(`Failed to read theme file: ${error.message}`);
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Run validation
|
|
141
|
+
const isValid = validateThemeFile();
|
|
142
|
+
process.exit(isValid ? 0 : 1);
|
|
@@ -63,7 +63,7 @@ import { cn } from '../../utils/core/cn';
|
|
|
63
63
|
// CALENDAR COMPONENT
|
|
64
64
|
// ============================================================================
|
|
65
65
|
|
|
66
|
-
export interface CalendarProps extends Omit<DayPickerProps, 'className' | 'classNames' | 'styles'> {
|
|
66
|
+
export interface CalendarProps extends Omit<DayPickerProps, 'className' | 'classNames' | 'styles' | 'onSelect'> {
|
|
67
67
|
/**
|
|
68
68
|
* Additional CSS classes to apply to the calendar wrapper
|
|
69
69
|
*/
|
|
@@ -72,6 +72,13 @@ export interface CalendarProps extends Omit<DayPickerProps, 'className' | 'class
|
|
|
72
72
|
* Custom classNames for DayPicker sub-components
|
|
73
73
|
*/
|
|
74
74
|
classNames?: DayPickerProps['classNames'];
|
|
75
|
+
/**
|
|
76
|
+
* Date selection handler. Signature depends on mode:
|
|
77
|
+
* - mode="single": (date: Date | undefined) => void
|
|
78
|
+
* - mode="range": (range: { from: Date; to?: Date } | undefined) => void
|
|
79
|
+
* - mode="multiple": (dates: Date[]) => void
|
|
80
|
+
*/
|
|
81
|
+
onSelect?: ((date: Date | undefined) => void) | ((range: { from: Date; to?: Date } | undefined) => void) | ((dates: Date[]) => void);
|
|
75
82
|
}
|
|
76
83
|
|
|
77
84
|
/**
|
|
@@ -97,9 +97,10 @@ function getCardClasses(variant: CardProps['variant'] = 'default', size: CardPro
|
|
|
97
97
|
return `${baseClasses} ${variantClasses[variant]} ${sizeClasses[size]}`;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
-
|
|
100
|
+
// Internal component for cards with navigation (requires Router context)
|
|
101
|
+
const CardWithNavigation = React.forwardRef<
|
|
101
102
|
HTMLElement,
|
|
102
|
-
CardProps
|
|
103
|
+
CardProps & { link: string }
|
|
103
104
|
>(({ className, variant, size, isLink, link, onClick, ...props }, ref) => {
|
|
104
105
|
const navigate = useNavigate();
|
|
105
106
|
|
|
@@ -131,19 +132,57 @@ const Card = React.forwardRef<
|
|
|
131
132
|
shouldShowLinkStyles && "cursor-pointer hover:bg-acc-200 hover:shadow-xl/30",
|
|
132
133
|
className
|
|
133
134
|
)}
|
|
134
|
-
onClick={
|
|
135
|
-
role=
|
|
136
|
-
tabIndex={
|
|
137
|
-
onKeyDown={
|
|
135
|
+
onClick={handleClick}
|
|
136
|
+
role="link"
|
|
137
|
+
tabIndex={0}
|
|
138
|
+
onKeyDown={(e) => {
|
|
138
139
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
139
140
|
e.preventDefault();
|
|
140
141
|
handleClick(e as unknown as React.MouseEvent<HTMLElement>);
|
|
141
142
|
}
|
|
142
|
-
}
|
|
143
|
+
}}
|
|
143
144
|
{...props}
|
|
144
145
|
/>
|
|
145
146
|
);
|
|
146
|
-
})
|
|
147
|
+
});
|
|
148
|
+
CardWithNavigation.displayName = "CardWithNavigation";
|
|
149
|
+
|
|
150
|
+
// Internal component for cards without navigation (no Router context required)
|
|
151
|
+
const CardWithoutNavigation = React.forwardRef<
|
|
152
|
+
HTMLElement,
|
|
153
|
+
Omit<CardProps, 'link'>
|
|
154
|
+
>(({ className, variant, size, isLink, onClick, ...props }, ref) => {
|
|
155
|
+
// Automatically enable link styles if isLink is true
|
|
156
|
+
const shouldShowLinkStyles = isLink;
|
|
157
|
+
|
|
158
|
+
return (
|
|
159
|
+
<article
|
|
160
|
+
ref={ref}
|
|
161
|
+
className={cn(
|
|
162
|
+
"grid grid-rows-[auto_1fr_auto] min-w-0 overflow-visible",
|
|
163
|
+
getCardClasses(variant, size),
|
|
164
|
+
shouldShowLinkStyles && "cursor-pointer hover:bg-acc-200 hover:shadow-xl/30",
|
|
165
|
+
className
|
|
166
|
+
)}
|
|
167
|
+
onClick={onClick}
|
|
168
|
+
{...props}
|
|
169
|
+
/>
|
|
170
|
+
);
|
|
171
|
+
});
|
|
172
|
+
CardWithoutNavigation.displayName = "CardWithoutNavigation";
|
|
173
|
+
|
|
174
|
+
// Main Card component that conditionally renders the appropriate variant
|
|
175
|
+
const Card = React.forwardRef<
|
|
176
|
+
HTMLElement,
|
|
177
|
+
CardProps
|
|
178
|
+
>(({ link, ...props }, ref) => {
|
|
179
|
+
// Only use navigation component if link is provided
|
|
180
|
+
// This prevents useNavigate() from being called when Router context is not available
|
|
181
|
+
if (link) {
|
|
182
|
+
return <CardWithNavigation ref={ref} link={link} {...props} />;
|
|
183
|
+
}
|
|
184
|
+
return <CardWithoutNavigation ref={ref} {...props} />;
|
|
185
|
+
});
|
|
147
186
|
Card.displayName = "Card"
|
|
148
187
|
|
|
149
188
|
const CardHeader = React.forwardRef<
|