@jmruthers/pace-core 0.5.136 → 0.5.139

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.
Files changed (292) hide show
  1. package/dist/{DataTable-CYOHOX3O.js → DataTable-JXFCA2BJ.js} +10 -9
  2. package/dist/{EventLogo-801uofbR.d.ts → EventLogo-rFL_kRjk.d.ts} +73 -1
  3. package/dist/{UnifiedAuthProvider-5E5TUNMS.js → UnifiedAuthProvider-XIQQ7LVU.js} +4 -5
  4. package/dist/{chunk-YLKIDTUK.js → chunk-22WKWKRX.js} +4 -4
  5. package/dist/{chunk-TVYPTYOY.js → chunk-4C7EXCAR.js} +60 -24
  6. package/dist/chunk-4C7EXCAR.js.map +1 -0
  7. package/dist/{chunk-NOHEVYVX.js → chunk-5JMOHWDI.js} +417 -319
  8. package/dist/chunk-5JMOHWDI.js.map +1 -0
  9. package/dist/{chunk-FHWWBIHA.js → chunk-6DXZ6V5Q.js} +5 -5
  10. package/dist/{chunk-2TWNJ46Y.js → chunk-6LAAY47Q.js} +2 -2
  11. package/dist/{chunk-444EZN6N.js → chunk-7QCC6MCP.js} +88 -1
  12. package/dist/chunk-7QCC6MCP.js.map +1 -0
  13. package/dist/chunk-BJPBT3CU.js +21 -0
  14. package/dist/chunk-BJPBT3CU.js.map +1 -0
  15. package/dist/{chunk-L6PGMCMD.js → chunk-BOOI7GK2.js} +38 -12
  16. package/dist/chunk-BOOI7GK2.js.map +1 -0
  17. package/dist/{chunk-XARJS7CD.js → chunk-INQLMHPF.js} +2 -2
  18. package/dist/chunk-JISYG63F.js +70 -0
  19. package/dist/chunk-JISYG63F.js.map +1 -0
  20. package/dist/{chunk-SL2YQDR6.js → chunk-MA6EPSGZ.js} +2 -2
  21. package/dist/{chunk-5DPZ5EAT.js → chunk-OWAG3GSU.js} +1 -3
  22. package/dist/{chunk-LTV3XIJJ.js → chunk-T6JN6LH6.js} +4 -4
  23. package/dist/{chunk-HJGGOMQ6.js → chunk-TLT2ZR3L.js} +147 -103
  24. package/dist/chunk-TLT2ZR3L.js.map +1 -0
  25. package/dist/{chunk-4MT5BGGL.js → chunk-YCWDTTUK.js} +4 -6
  26. package/dist/{chunk-4MT5BGGL.js.map → chunk-YCWDTTUK.js.map} +1 -1
  27. package/dist/components.d.ts +1 -1
  28. package/dist/components.js +12 -11
  29. package/dist/components.js.map +1 -1
  30. package/dist/hooks.js +8 -9
  31. package/dist/hooks.js.map +1 -1
  32. package/dist/index.d.ts +2 -2
  33. package/dist/index.js +15 -14
  34. package/dist/index.js.map +1 -1
  35. package/dist/providers.js +3 -4
  36. package/dist/rbac/index.js +8 -9
  37. package/dist/schema-DTDZQe2u.d.ts +28 -0
  38. package/dist/types.d.ts +152 -3
  39. package/dist/types.js +51 -16
  40. package/dist/types.js.map +1 -1
  41. package/dist/utils.d.ts +89 -4
  42. package/dist/utils.js +214 -96
  43. package/dist/utils.js.map +1 -1
  44. package/dist/validation.d.ts +1 -343
  45. package/dist/validation.js +3 -100
  46. package/docs/api/classes/ColumnFactory.md +1 -1
  47. package/docs/api/classes/ErrorBoundary.md +1 -1
  48. package/docs/api/classes/InvalidScopeError.md +1 -1
  49. package/docs/api/classes/MissingUserContextError.md +1 -1
  50. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  51. package/docs/api/classes/PermissionDeniedError.md +1 -1
  52. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  53. package/docs/api/classes/RBACAuditManager.md +1 -1
  54. package/docs/api/classes/RBACCache.md +1 -1
  55. package/docs/api/classes/RBACEngine.md +1 -1
  56. package/docs/api/classes/RBACError.md +1 -1
  57. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  58. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  59. package/docs/api/classes/StorageUtils.md +1 -1
  60. package/docs/api/enums/FileCategory.md +1 -1
  61. package/docs/api/interfaces/AggregateConfig.md +1 -1
  62. package/docs/api/interfaces/BadgeProps.md +27 -0
  63. package/docs/api/interfaces/ButtonProps.md +1 -1
  64. package/docs/api/interfaces/CardProps.md +1 -1
  65. package/docs/api/interfaces/ColorPalette.md +1 -1
  66. package/docs/api/interfaces/ColorShade.md +1 -1
  67. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  68. package/docs/api/interfaces/DataRecord.md +1 -1
  69. package/docs/api/interfaces/DataTableAction.md +1 -1
  70. package/docs/api/interfaces/DataTableColumn.md +1 -1
  71. package/docs/api/interfaces/DataTableProps.md +1 -1
  72. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  73. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  74. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  75. package/docs/api/interfaces/EventAppRoleData.md +1 -1
  76. package/docs/api/interfaces/EventLogoProps.md +1 -1
  77. package/docs/api/interfaces/ExportColumn.md +1 -1
  78. package/docs/api/interfaces/ExportOptions.md +1 -1
  79. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  80. package/docs/api/interfaces/FileMetadata.md +1 -1
  81. package/docs/api/interfaces/FileReference.md +1 -1
  82. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  83. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  84. package/docs/api/interfaces/FileUploadProps.md +1 -1
  85. package/docs/api/interfaces/FooterProps.md +1 -1
  86. package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
  87. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  88. package/docs/api/interfaces/InputProps.md +1 -1
  89. package/docs/api/interfaces/LabelProps.md +1 -1
  90. package/docs/api/interfaces/LoginFormProps.md +1 -1
  91. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  92. package/docs/api/interfaces/NavigationContextType.md +1 -1
  93. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  94. package/docs/api/interfaces/NavigationItem.md +1 -1
  95. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  96. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  97. package/docs/api/interfaces/Organisation.md +1 -1
  98. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  99. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  100. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  101. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  102. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  103. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  104. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  105. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  106. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  107. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  108. package/docs/api/interfaces/PaletteData.md +1 -1
  109. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  110. package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
  111. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  112. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  113. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  114. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  115. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  116. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  117. package/docs/api/interfaces/RBACConfig.md +1 -1
  118. package/docs/api/interfaces/RBACLogger.md +1 -1
  119. package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
  120. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  121. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  122. package/docs/api/interfaces/RoleManagementResult.md +1 -1
  123. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  124. package/docs/api/interfaces/RouteConfig.md +1 -1
  125. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  126. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  127. package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
  128. package/docs/api/interfaces/StorageConfig.md +1 -1
  129. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  130. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  131. package/docs/api/interfaces/StorageListOptions.md +1 -1
  132. package/docs/api/interfaces/StorageListResult.md +1 -1
  133. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  134. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  135. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  136. package/docs/api/interfaces/StyleImport.md +1 -1
  137. package/docs/api/interfaces/SwitchProps.md +1 -1
  138. package/docs/api/interfaces/ToastActionElement.md +1 -1
  139. package/docs/api/interfaces/ToastProps.md +1 -1
  140. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  141. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  142. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  143. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  144. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  145. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  146. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
  147. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  148. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  149. package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
  150. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  151. package/docs/api/interfaces/UserEventAccess.md +1 -1
  152. package/docs/api/interfaces/UserMenuProps.md +1 -1
  153. package/docs/api/interfaces/UserProfile.md +1 -1
  154. package/docs/api/modules.md +84 -15
  155. package/docs/architecture/README.md +0 -1
  156. package/docs/styles/README.md +0 -2
  157. package/examples/RBAC/CompleteRBACExample.tsx +324 -0
  158. package/examples/RBAC/EventBasedApp.tsx +239 -0
  159. package/examples/RBAC/PermissionExample.tsx +151 -0
  160. package/examples/RBAC/index.ts +13 -0
  161. package/examples/public-pages/CorrectPublicPageImplementation.tsx +301 -0
  162. package/examples/public-pages/PublicEventPage.tsx +274 -0
  163. package/examples/public-pages/PublicPageApp.tsx +308 -0
  164. package/examples/public-pages/PublicPageUsageExample.tsx +216 -0
  165. package/examples/public-pages/index.ts +14 -0
  166. package/package.json +1 -10
  167. package/src/__tests__/TEST_STANDARD.md +92 -0
  168. package/src/components/Badge/Badge.test.tsx +314 -0
  169. package/src/components/Badge/Badge.tsx +304 -0
  170. package/src/components/Badge/index.ts +3 -0
  171. package/src/components/DataTable/__tests__/DataTableCore.test-setup.ts +217 -0
  172. package/src/components/DataTable/__tests__/styles.test.ts +1 -1
  173. package/src/components/DataTable/components/ColumnFilter.tsx +8 -4
  174. package/src/components/DataTable/components/DataTableBody.tsx +461 -0
  175. package/src/components/DataTable/components/DraggableColumnHeader.tsx +144 -0
  176. package/src/components/DataTable/components/FilterRow.tsx +9 -3
  177. package/src/components/DataTable/components/PaginationControls.tsx +1 -0
  178. package/src/components/DataTable/components/VirtualizedDataTable.tsx +513 -0
  179. package/src/components/DataTable/components/__tests__/AccessDeniedPage.test.tsx +14 -68
  180. package/src/components/DataTable/components/__tests__/ColumnFilter.test.tsx +62 -0
  181. package/src/components/DataTable/components/__tests__/FilterRow.test.tsx +43 -0
  182. package/src/components/DataTable/core/ActionManager.ts +235 -0
  183. package/src/components/DataTable/core/ColumnManager.ts +205 -0
  184. package/src/components/DataTable/core/DataManager.ts +188 -0
  185. package/src/components/DataTable/core/DataTableContext.tsx +181 -0
  186. package/src/components/DataTable/core/LocalDataAdapter.ts +273 -0
  187. package/src/components/DataTable/core/PluginRegistry.ts +229 -0
  188. package/src/components/DataTable/core/StateManager.ts +311 -0
  189. package/src/components/DataTable/core/interfaces.ts +338 -0
  190. package/src/components/DataTable/styles.ts +27 -6
  191. package/src/components/DataTable/utils/__tests__/columnUtils.test.ts +94 -0
  192. package/src/components/DataTable/utils/columnUtils.ts +40 -0
  193. package/src/components/DataTable/utils/debugTools.ts +609 -0
  194. package/src/components/DataTable/utils/index.ts +1 -0
  195. package/src/components/Dialog/README.md +804 -0
  196. package/src/components/Dialog/utils/__tests__/safeHtml.unit.test.ts +611 -0
  197. package/src/components/Dialog/utils/safeHtml.ts +185 -0
  198. package/src/components/Footer/Footer.test.tsx +1 -1
  199. package/src/components/Form/Form.test.tsx +1 -1
  200. package/src/components/Form/FormErrorSummary.tsx +113 -0
  201. package/src/components/Form/FormFieldset.tsx +127 -0
  202. package/src/components/Form/FormLiveRegion.tsx +198 -0
  203. package/src/components/LoginForm/LoginForm.test.tsx +1 -1
  204. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.performance.test.tsx +76 -10
  205. package/src/components/PaceLoginPage/PaceLoginPage.tsx +1 -1
  206. package/src/components/PasswordReset/PasswordResetForm.test.tsx +597 -0
  207. package/src/components/PasswordReset/PasswordResetForm.tsx +201 -0
  208. package/src/components/PublicLayout/PublicPageDebugger.tsx +104 -0
  209. package/src/components/PublicLayout/PublicPageDiagnostic.tsx +162 -0
  210. package/src/components/PublicLayout/__tests__/PublicPageFooter.test.tsx +1 -1
  211. package/src/components/Select/Select.test.tsx +1 -1
  212. package/src/components/Select/Select.tsx +20 -8
  213. package/src/components/Table/__tests__/Table.test.tsx +1 -1
  214. package/src/components/index.ts +3 -0
  215. package/src/hooks/__tests__/useFileUrl.unit.test.ts +83 -85
  216. package/src/index.ts +4 -0
  217. package/src/rbac/hooks/useCan.test.ts +24 -0
  218. package/src/rbac/hooks/usePermissions.ts +49 -12
  219. package/src/styles/core.css +3 -0
  220. package/src/utils/appConfig.ts +47 -0
  221. package/src/utils/appIdResolver.test.ts +499 -0
  222. package/src/utils/appIdResolver.ts +130 -0
  223. package/src/utils/appNameResolver.simple.test.ts +212 -0
  224. package/src/utils/appNameResolver.test.ts +121 -0
  225. package/src/utils/appNameResolver.ts +191 -0
  226. package/src/utils/audit.ts +127 -0
  227. package/src/utils/auth-utils.ts +96 -0
  228. package/src/utils/bundleAnalysis.ts +129 -0
  229. package/src/utils/cn.ts +7 -0
  230. package/src/utils/debugLogger.ts +67 -0
  231. package/src/utils/deviceFingerprint.ts +215 -0
  232. package/src/utils/dynamicUtils.ts +105 -0
  233. package/src/utils/file-reference.test.ts +788 -0
  234. package/src/utils/file-reference.ts +519 -0
  235. package/src/utils/formatDate.test.ts +237 -0
  236. package/src/utils/formatting.ts +133 -0
  237. package/src/utils/index.ts +7 -0
  238. package/src/utils/lazyLoad.tsx +44 -0
  239. package/src/utils/logger.ts +179 -0
  240. package/src/utils/organisationContext.test.ts +322 -0
  241. package/src/utils/organisationContext.ts +153 -0
  242. package/src/utils/performanceBenchmark.ts +64 -0
  243. package/src/utils/performanceBudgets.ts +110 -0
  244. package/src/utils/permissionTypes.ts +37 -0
  245. package/src/utils/permissionUtils.test.ts +393 -0
  246. package/src/utils/permissionUtils.ts +34 -0
  247. package/src/utils/sanitization.ts +264 -0
  248. package/src/utils/schemaUtils.ts +37 -0
  249. package/src/utils/secureDataAccess.test.ts +711 -0
  250. package/src/utils/secureDataAccess.ts +377 -0
  251. package/src/utils/secureErrors.ts +79 -0
  252. package/src/utils/secureStorage.ts +244 -0
  253. package/src/utils/security.ts +156 -0
  254. package/src/utils/securityMonitor.ts +45 -0
  255. package/src/utils/sessionTracking.ts +126 -0
  256. package/src/utils/validation.ts +111 -0
  257. package/src/utils/validationUtils.ts +120 -0
  258. package/src/validation/index.ts +2 -2
  259. package/dist/chunk-444EZN6N.js.map +0 -1
  260. package/dist/chunk-APIBCTL2.js +0 -670
  261. package/dist/chunk-APIBCTL2.js.map +0 -1
  262. package/dist/chunk-HJGGOMQ6.js.map +0 -1
  263. package/dist/chunk-K2WWTH7O.js +0 -94
  264. package/dist/chunk-K2WWTH7O.js.map +0 -1
  265. package/dist/chunk-L6PGMCMD.js.map +0 -1
  266. package/dist/chunk-LMC26NLJ.js +0 -84
  267. package/dist/chunk-LMC26NLJ.js.map +0 -1
  268. package/dist/chunk-NOHEVYVX.js.map +0 -1
  269. package/dist/chunk-TVYPTYOY.js.map +0 -1
  270. package/dist/validation-8npbysjg.d.ts +0 -177
  271. /package/dist/{DataTable-CYOHOX3O.js.map → DataTable-JXFCA2BJ.js.map} +0 -0
  272. /package/dist/{UnifiedAuthProvider-5E5TUNMS.js.map → UnifiedAuthProvider-XIQQ7LVU.js.map} +0 -0
  273. /package/dist/{chunk-YLKIDTUK.js.map → chunk-22WKWKRX.js.map} +0 -0
  274. /package/dist/{chunk-FHWWBIHA.js.map → chunk-6DXZ6V5Q.js.map} +0 -0
  275. /package/dist/{chunk-2TWNJ46Y.js.map → chunk-6LAAY47Q.js.map} +0 -0
  276. /package/dist/{chunk-XARJS7CD.js.map → chunk-INQLMHPF.js.map} +0 -0
  277. /package/dist/{chunk-SL2YQDR6.js.map → chunk-MA6EPSGZ.js.map} +0 -0
  278. /package/dist/{chunk-5DPZ5EAT.js.map → chunk-OWAG3GSU.js.map} +0 -0
  279. /package/dist/{chunk-LTV3XIJJ.js.map → chunk-T6JN6LH6.js.map} +0 -0
  280. /package/examples/{components → components 2}/DataTable/HierarchicalActionsExample.tsx +0 -0
  281. /package/examples/{components → components 2}/DataTable/HierarchicalExample.tsx +0 -0
  282. /package/examples/{components → components 2}/DataTable/InitialPageSizeExample.tsx +0 -0
  283. /package/examples/{components → components 2}/DataTable/PerformanceExample.tsx +0 -0
  284. /package/examples/{components → components 2}/DataTable/index.ts +0 -0
  285. /package/examples/{components → components 2}/Dialog/BasicHtmlTest.tsx +0 -0
  286. /package/examples/{components → components 2}/Dialog/DebugHtmlExample.tsx +0 -0
  287. /package/examples/{components → components 2}/Dialog/HtmlDialogExample.tsx +0 -0
  288. /package/examples/{components → components 2}/Dialog/ScrollableDialogExample.tsx +0 -0
  289. /package/examples/{components → components 2}/Dialog/SimpleHtmlTest.tsx +0 -0
  290. /package/examples/{components → components 2}/Dialog/SmartDialogExample.tsx +0 -0
  291. /package/examples/{components → components 2}/Dialog/index.ts +0 -0
  292. /package/examples/{components → components 2}/index.ts +0 -0
@@ -0,0 +1,216 @@
1
+ /**
2
+ * @file Public Page Usage Example
3
+ * @package @jmruthers/pace-core
4
+ * @module Examples/PublicPages
5
+ * @since 1.0.0
6
+ *
7
+ * A complete example showing the correct usage pattern for public pages.
8
+ * This example demonstrates how to properly implement public pages without
9
+ * authentication context conflicts.
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * import { PublicPageUsageExample } from '@jmruthers/pace-core/examples';
14
+ *
15
+ * function App() {
16
+ * return (
17
+ * <BrowserRouter>
18
+ * <Routes>
19
+ * <Route path="/events/:eventCode/recipe-grid-report" element={<PublicPageUsageExample />} />
20
+ * </Routes>
21
+ * </BrowserRouter>
22
+ * );
23
+ * }
24
+ * ```
25
+ */
26
+
27
+ import React from 'react';
28
+ import {
29
+ PublicPageLayout,
30
+ PublicPageHeader,
31
+ PublicPageFooter,
32
+ EventLogo,
33
+ usePublicEvent,
34
+ usePublicRouteParams,
35
+ PublicLoadingSpinner,
36
+ PublicErrorBoundary
37
+ } from '../../src';
38
+
39
+ /**
40
+ * Correct usage pattern for public pages
41
+ *
42
+ * This component demonstrates the proper way to implement public pages:
43
+ * 1. Use usePublicRouteParams to get the eventCode from URL
44
+ * 2. Use usePublicEvent to fetch event data
45
+ * 3. Pass event data to PublicPageLayout
46
+ * 4. No authentication context is triggered
47
+ */
48
+ export function PublicPageUsageExample() {
49
+ // Step 1: Extract event code from URL parameters
50
+ const { eventCode } = usePublicRouteParams({ fetchEventData: false });
51
+
52
+ // Step 2: Fetch event data using the event code
53
+ const { event, isLoading, error, refetch } = usePublicEvent(eventCode || '');
54
+
55
+ // Step 3: Handle loading state
56
+ if (isLoading) {
57
+ return (
58
+ <PublicLoadingSpinner
59
+ message="Loading event details..."
60
+ />
61
+ );
62
+ }
63
+
64
+ // Step 4: Handle error state
65
+ if (error) {
66
+ return (
67
+ <div className="min-h-screen bg-main-50 flex items-center justify-center">
68
+ <div className="max-w-md mx-auto text-center px-4">
69
+ <div className="mb-6">
70
+ <div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-acc-100 mb-4">
71
+ <svg className="h-6 w-6 text-acc-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
72
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z" />
73
+ </svg>
74
+ </div>
75
+ <h1 className="text-2xl font-bold text-sec-900 mb-2">
76
+ Event Not Found
77
+ </h1>
78
+ <p className="text-sec-600 mb-6">
79
+ The event code "{eventCode}" is invalid or the event is not available for public viewing.
80
+ </p>
81
+ <button
82
+ onClick={refetch}
83
+ className="px-4 py-2 bg-main-600 text-main-50 rounded-md hover:bg-main-700 transition-colors"
84
+ >
85
+ Try Again
86
+ </button>
87
+ </div>
88
+ </div>
89
+ </div>
90
+ );
91
+ }
92
+
93
+ // Step 5: Handle missing event
94
+ if (!event) {
95
+ return (
96
+ <div className="min-h-screen bg-main-50 flex items-center justify-center">
97
+ <div className="max-w-md mx-auto text-center px-4">
98
+ <h1 className="text-2xl font-bold text-sec-900 mb-4">
99
+ Event Not Available
100
+ </h1>
101
+ <p className="text-sec-600 mb-6">
102
+ This event is not available for public viewing.
103
+ </p>
104
+ <button
105
+ onClick={refetch}
106
+ className="px-4 py-2 bg-main-600 text-main-50 rounded-md hover:bg-main-700 transition-colors"
107
+ >
108
+ Try Again
109
+ </button>
110
+ </div>
111
+ </div>
112
+ );
113
+ }
114
+
115
+ // Step 6: Render the public page with event data
116
+ return (
117
+ <PublicErrorBoundary>
118
+ <PublicPageLayout eventCode={eventCode || ''} event={event}>
119
+ <PublicPageHeader
120
+ event={event}
121
+ eventCode={eventCode || ''}
122
+ title="Recipe Grid Report"
123
+ description="Public recipe grid report for this event"
124
+ />
125
+
126
+ <main className="max-w-6xl mx-auto px-4 py-8">
127
+ {/* Event Overview */}
128
+ <div className="grid grid-cols-1 lg:grid-cols-3 gap-8 mb-12">
129
+ {/* Event Information */}
130
+ <div className="lg:col-span-2 space-y-6">
131
+ <div>
132
+ <h2 className="text-2xl font-bold text-sec-900 mb-4">Event Information</h2>
133
+ <div className="bg-sec-50 rounded-lg p-6 space-y-4">
134
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
135
+ <div>
136
+ <h3 className="font-semibold text-sec-700">Date</h3>
137
+ <p className="text-sec-900">
138
+ {event.event_date ? new Date(event.event_date).toLocaleDateString('en-AU', {
139
+ weekday: 'long',
140
+ year: 'numeric',
141
+ month: 'long',
142
+ day: 'numeric'
143
+ }) : 'TBA'}
144
+ </p>
145
+ </div>
146
+ <div>
147
+ <h3 className="font-semibold text-sec-700">Venue</h3>
148
+ <p className="text-sec-900">{event.event_venue || 'TBA'}</p>
149
+ </div>
150
+ <div>
151
+ <h3 className="font-semibold text-sec-700">Participants</h3>
152
+ <p className="text-sec-900">{event.event_participants || 'TBA'}</p>
153
+ </div>
154
+ <div>
155
+ <h3 className="font-semibold text-sec-700">Event Code</h3>
156
+ <p className="text-sec-900 font-mono">{event.event_code}</p>
157
+ </div>
158
+ </div>
159
+
160
+ {event.event_news && (
161
+ <div>
162
+ <h3 className="font-semibold text-sec-700 mb-2">Event News</h3>
163
+ <p className="text-sec-900">{event.event_news}</p>
164
+ </div>
165
+ )}
166
+ </div>
167
+ </div>
168
+ </div>
169
+
170
+ {/* Event Logo */}
171
+ <div className="flex justify-center lg:justify-start">
172
+ <div className="text-center">
173
+ <EventLogo
174
+ eventId={event.event_id}
175
+ eventName={event.event_name}
176
+ organisationId={event.organisation_id}
177
+ size="2xl"
178
+ className="rounded-lg shadow-lg"
179
+ />
180
+ <p className="mt-4 text-sm text-sec-600">Event Logo</p>
181
+ </div>
182
+ </div>
183
+ </div>
184
+
185
+ {/* Recipe Grid Report Content */}
186
+ <div className="mb-12">
187
+ <div className="bg-main-50 border border-main-200 rounded-lg p-6">
188
+ <h3 className="font-semibold text-main-900 mb-2">Recipe Grid Report</h3>
189
+ <p className="text-main-800">
190
+ This is where your recipe grid report content would go.
191
+ The public page is now working correctly without authentication context conflicts.
192
+ </p>
193
+ <div className="mt-4 text-sm text-main-700">
194
+ <p><strong>Event Code:</strong> {eventCode}</p>
195
+ <p><strong>Event ID:</strong> {event.event_id}</p>
196
+ <p><strong>Event Name:</strong> {event.event_name}</p>
197
+ </div>
198
+ </div>
199
+ </div>
200
+
201
+ {/* Event Footer Information */}
202
+ {event.event_footer && (
203
+ <div className="bg-sec-50 rounded-lg p-6">
204
+ <h3 className="font-semibold text-sec-900 mb-2">Additional Information</h3>
205
+ <p className="text-sec-700">{event.event_footer}</p>
206
+ </div>
207
+ )}
208
+ </main>
209
+
210
+ <PublicPageFooter event={event} />
211
+ </PublicPageLayout>
212
+ </PublicErrorBoundary>
213
+ );
214
+ }
215
+
216
+ export default PublicPageUsageExample;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @file Public Pages Examples
3
+ * @package @jmruthers/pace-core
4
+ * @module Examples/PublicPages
5
+ * @since 1.0.0
6
+ *
7
+ * Public page workflow examples
8
+ */
9
+
10
+ export { PublicPageUsageExample } from './PublicPageUsageExample';
11
+ export { PublicEventPage, PublicEventPageCompact } from './PublicEventPage';
12
+ export { PublicPageApp } from './PublicPageApp';
13
+ export { RootApp as CorrectPublicPageImplementation } from './CorrectPublicPageImplementation';
14
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jmruthers/pace-core",
3
- "version": "0.5.136",
3
+ "version": "0.5.139",
4
4
  "description": "Clean, modern React component library with Tailwind v4 styling and native utilities",
5
5
  "private": false,
6
6
  "publishConfig": {
@@ -27,11 +27,6 @@
27
27
  "import": "./dist/providers.js",
28
28
  "default": "./dist/providers.js"
29
29
  },
30
- "./validation": {
31
- "types": "./dist/validation.d.ts",
32
- "import": "./dist/validation.js",
33
- "default": "./dist/validation.js"
34
- },
35
30
  "./hooks": {
36
31
  "types": "./dist/hooks.d.ts",
37
32
  "import": "./dist/hooks.js",
@@ -93,10 +88,6 @@
93
88
  "import": "./src/types/index.ts",
94
89
  "default": "./src/types/index.ts"
95
90
  },
96
- "./source/validation": {
97
- "import": "./src/validation/index.ts",
98
- "default": "./src/validation/index.ts"
99
- },
100
91
  "./source/rbac": {
101
92
  "import": "./src/rbac/index.ts",
102
93
  "default": "./src/rbac/index.ts"
@@ -289,6 +289,50 @@ mockSupabase.rpc.mockImplementation((functionName, params) => {
289
289
  });
290
290
  ```
291
291
 
292
+ ### Mock Isolation Best Practices
293
+
294
+ **Problem**: Mocks can leak between test files, causing flaky tests.
295
+
296
+ **Solutions**:
297
+
298
+ ```typescript
299
+ // ✅ Good: Use vi.hoisted() for module mocks
300
+ const { mockGetSignedUrl } = vi.hoisted(() => {
301
+ return {
302
+ mockGetSignedUrl: vi.fn(() => Promise.resolve({ url: 'test' }))
303
+ };
304
+ });
305
+
306
+ vi.mock('../utils/storage/helpers', () => ({
307
+ getSignedUrl: mockGetSignedUrl
308
+ }));
309
+
310
+ // ✅ Good: Reset mocks in beforeEach
311
+ beforeEach(() => {
312
+ mockGetSignedUrl.mockClear();
313
+ mockGetSignedUrl.mockReset();
314
+ mockGetSignedUrl.mockImplementation(() => Promise.resolve({ url: 'test' }));
315
+ });
316
+
317
+ // ✅ Good: Use vi.spyOn for better isolation (when possible)
318
+ const getSignedUrlSpy = vi.spyOn(storageHelpers, 'getSignedUrl');
319
+ getSignedUrlSpy.mockResolvedValue({ url: 'test' });
320
+
321
+ // ✅ Good: Use vi.stubGlobal for global mocks
322
+ vi.stubGlobal('window', undefined);
323
+ // Automatically restored after test
324
+
325
+ // ❌ Bad: Directly modifying global objects
326
+ delete global.window; // Can break other tests
327
+ ```
328
+
329
+ **Mock Isolation Checklist**:
330
+ - [ ] Use `vi.hoisted()` for module mocks
331
+ - [ ] Reset mocks in `beforeEach`/`afterEach`
332
+ - [ ] Use `vi.stubGlobal` for global mocks
333
+ - [ ] Prefer `vi.spyOn` over `vi.mock` when possible
334
+ - [ ] Test files in isolation to verify no leakage
335
+
292
336
  ### Cleanup Best Practices
293
337
 
294
338
  ```typescript
@@ -821,6 +865,54 @@ it.skip('should navigate between headers with arrow keys', async () => {
821
865
  3. Use `findBy` queries instead of `waitFor` + `getBy`
822
866
  4. Reduce timeout values for fast operations
823
867
 
868
+ ### Performance Testing Best Practices
869
+
870
+ **When to Test Performance**:
871
+ - ✅ Critical user-facing operations (rendering, navigation)
872
+ - ✅ Operations that must complete within SLA
873
+ - ✅ Memory-intensive operations
874
+ - ❌ Avoid testing performance of mocked operations
875
+ - ❌ Avoid absolute timing thresholds in CI environments
876
+
877
+ **Performance Test Patterns**:
878
+
879
+ ```typescript
880
+ // ❌ Bad: Absolute thresholds (brittle in CI/coverage)
881
+ const startTime = performance.now();
882
+ render(<Component />);
883
+ const endTime = performance.now();
884
+ expect(endTime - startTime).toBeLessThan(200); // Fails under coverage
885
+
886
+ // ✅ Good: Behavioral assertions (focus on outcomes)
887
+ await waitFor(() => {
888
+ expect(screen.getByRole('navigation')).toBeInTheDocument();
889
+ }, { timeout: 2000 });
890
+
891
+ // ✅ Good: Relative comparisons (more tolerant)
892
+ const baseline = measureBaseline();
893
+ const actual = measureActual();
894
+ expect(actual).toBeLessThan(baseline * 1.5); // 50% tolerance
895
+
896
+ // ✅ Good: Smoke tests (verify no regressions)
897
+ it('renders without performance degradation', async () => {
898
+ const startTime = performance.now();
899
+ render(<Component />);
900
+ await waitFor(() => {
901
+ expect(screen.getByRole('main')).toBeInTheDocument();
902
+ });
903
+ const endTime = performance.now();
904
+ // Only fail if significantly slower (e.g., > 5s)
905
+ expect(endTime - startTime).toBeLessThan(5000);
906
+ });
907
+ ```
908
+
909
+ **Performance Test Guidelines**:
910
+ 1. **Prefer Behavioral Tests**: Focus on what users see, not raw timing
911
+ 2. **Use Relative Thresholds**: Compare against baseline, not absolute values
912
+ 3. **Skip in Coverage Mode**: Use `if (process.env.COVERAGE) return;` for timing tests
913
+ 4. **Document Thresholds**: Explain why specific timing is critical
914
+ 5. **Separate Performance Suite**: Consider separate test suite for performance tests
915
+
824
916
  ### Memory Management
825
917
 
826
918
  ```typescript
@@ -0,0 +1,314 @@
1
+ /**
2
+ * @file Badge Component Tests
3
+ * @description Comprehensive test suite for Badge component
4
+ */
5
+
6
+ import React from 'react';
7
+ import { screen } from '@testing-library/react';
8
+ import { describe, it, expect, vi } from 'vitest';
9
+ import { Badge } from './Badge';
10
+ import { renderWithProviders } from '../../__tests__/helpers/test-utils';
11
+ import type { BadgeVariant } from './Badge';
12
+
13
+ describe('Badge Component', () => {
14
+ describe('Rendering', () => {
15
+ it('renders with text content', () => {
16
+ renderWithProviders(<Badge>New</Badge>);
17
+ expect(screen.getByText('New')).toBeInTheDocument();
18
+ });
19
+
20
+ it('renders as span element', () => {
21
+ const { container } = renderWithProviders(<Badge>Test</Badge>);
22
+ const badge = container.querySelector('span');
23
+ expect(badge).toBeInTheDocument();
24
+ });
25
+
26
+ it('renders with custom className', () => {
27
+ renderWithProviders(<Badge className="custom-class">Styled badge</Badge>);
28
+ const badge = screen.getByText('Styled badge');
29
+ expect(badge).toHaveClass('custom-class');
30
+ });
31
+
32
+ it('renders with data-testid', () => {
33
+ renderWithProviders(<Badge data-testid="test-badge">Test</Badge>);
34
+ expect(screen.getByTestId('test-badge')).toBeInTheDocument();
35
+ });
36
+
37
+ it('renders with children elements', () => {
38
+ renderWithProviders(
39
+ <Badge>
40
+ <span>Icon</span> Text
41
+ </Badge>
42
+ );
43
+ expect(screen.getByText('Icon')).toBeInTheDocument();
44
+ expect(screen.getByText('Text')).toBeInTheDocument();
45
+ });
46
+ });
47
+
48
+ describe('Variants', () => {
49
+ const allVariants: BadgeVariant[] = [
50
+ // Solid variants
51
+ 'solid-main-muted',
52
+ 'solid-main-normal',
53
+ 'solid-main-strong',
54
+ 'solid-sec-muted',
55
+ 'solid-sec-normal',
56
+ 'solid-sec-strong',
57
+ 'solid-acc-muted',
58
+ 'solid-acc-normal',
59
+ 'solid-acc-strong',
60
+ // Outline variants
61
+ 'outline-main-muted',
62
+ 'outline-main-normal',
63
+ 'outline-main-strong',
64
+ 'outline-sec-muted',
65
+ 'outline-sec-normal',
66
+ 'outline-sec-strong',
67
+ 'outline-acc-muted',
68
+ 'outline-acc-normal',
69
+ 'outline-acc-strong',
70
+ // Soft variants
71
+ 'soft-main-muted',
72
+ 'soft-main-normal',
73
+ 'soft-main-strong',
74
+ 'soft-sec-muted',
75
+ 'soft-sec-normal',
76
+ 'soft-sec-strong',
77
+ 'soft-acc-muted',
78
+ 'soft-acc-normal',
79
+ 'soft-acc-strong',
80
+ ];
81
+
82
+ allVariants.forEach((variant) => {
83
+ it(`renders ${variant} variant correctly`, () => {
84
+ renderWithProviders(<Badge variant={variant}>Test {variant}</Badge>);
85
+ const badge = screen.getByText(`Test ${variant}`);
86
+ expect(badge).toBeInTheDocument();
87
+ expect(badge).toHaveClass('inline-flex', 'items-center', 'rounded-2xl');
88
+ });
89
+ });
90
+
91
+ it('uses default variant when not specified', () => {
92
+ renderWithProviders(<Badge>Default</Badge>);
93
+ const badge = screen.getByText('Default');
94
+ expect(badge).toHaveClass('bg-main-500', 'text-main-50');
95
+ });
96
+
97
+ it('applies solid variant classes correctly', () => {
98
+ renderWithProviders(<Badge variant="solid-main-normal">Solid</Badge>);
99
+ const badge = screen.getByText('Solid');
100
+ expect(badge).toHaveClass('bg-main-500', 'text-main-50');
101
+ });
102
+
103
+ it('applies outline variant classes correctly', () => {
104
+ renderWithProviders(<Badge variant="outline-sec-muted">Outline</Badge>);
105
+ const badge = screen.getByText('Outline');
106
+ expect(badge).toHaveClass('outline', 'outline-1');
107
+ });
108
+
109
+ it('applies soft variant classes correctly', () => {
110
+ renderWithProviders(<Badge variant="soft-acc-strong">Soft</Badge>);
111
+ const badge = screen.getByText('Soft');
112
+ expect(badge).toHaveClass('shadow-badge-soft');
113
+ });
114
+ });
115
+
116
+ describe('Color System', () => {
117
+ it('uses main color palette for main variants', () => {
118
+ renderWithProviders(<Badge variant="solid-main-normal">Main</Badge>);
119
+ const badge = screen.getByText('Main');
120
+ expect(badge).toHaveClass('bg-main-500', 'text-main-50');
121
+ });
122
+
123
+ it('uses sec color palette for sec variants', () => {
124
+ renderWithProviders(<Badge variant="solid-sec-normal">Sec</Badge>);
125
+ const badge = screen.getByText('Sec');
126
+ expect(badge).toHaveClass('bg-sec-500', 'text-sec-50');
127
+ });
128
+
129
+ it('uses acc color palette for acc variants', () => {
130
+ renderWithProviders(<Badge variant="solid-acc-normal">Acc</Badge>);
131
+ const badge = screen.getByText('Acc');
132
+ expect(badge).toHaveClass('bg-acc-500', 'text-acc-50');
133
+ });
134
+
135
+ it('does not use standard Tailwind colors', () => {
136
+ renderWithProviders(<Badge variant="solid-main-normal">Test</Badge>);
137
+ const badge = screen.getByText('Test');
138
+ // Should not have standard Tailwind color classes
139
+ expect(badge).not.toHaveClass('bg-blue-', 'text-red-', 'bg-gray-', 'bg-white', 'bg-black');
140
+ });
141
+ });
142
+
143
+ describe('Shade Variants', () => {
144
+ it('applies muted shade correctly (dark text on light)', () => {
145
+ renderWithProviders(<Badge variant="solid-main-muted">Muted</Badge>);
146
+ const badge = screen.getByText('Muted');
147
+ expect(badge).toHaveClass('bg-main-200', 'text-main-600');
148
+ });
149
+
150
+ it('applies normal shade correctly (light text on medium)', () => {
151
+ renderWithProviders(<Badge variant="solid-main-normal">Normal</Badge>);
152
+ const badge = screen.getByText('Normal');
153
+ expect(badge).toHaveClass('bg-main-500', 'text-main-50');
154
+ });
155
+
156
+ it('applies strong shade correctly (light text on dark)', () => {
157
+ renderWithProviders(<Badge variant="solid-main-strong">Strong</Badge>);
158
+ const badge = screen.getByText('Strong');
159
+ expect(badge).toHaveClass('bg-main-700', 'text-main-50');
160
+ });
161
+ });
162
+
163
+ describe('Props Forwarding', () => {
164
+ it('forwards HTML attributes', () => {
165
+ renderWithProviders(
166
+ <Badge id="test-id" data-custom="value" aria-label="Test badge">
167
+ Test
168
+ </Badge>
169
+ );
170
+ const badge = screen.getByText('Test');
171
+ expect(badge).toHaveAttribute('id', 'test-id');
172
+ expect(badge).toHaveAttribute('data-custom', 'value');
173
+ expect(badge).toHaveAttribute('aria-label', 'Test badge');
174
+ });
175
+
176
+ it('forwards ref correctly', () => {
177
+ const ref = React.createRef<HTMLSpanElement>();
178
+ renderWithProviders(<Badge ref={ref}>Ref badge</Badge>);
179
+ expect(ref.current).toBeInstanceOf(HTMLSpanElement);
180
+ expect(ref.current).toHaveTextContent('Ref badge');
181
+ });
182
+
183
+ it('merges custom className with variant classes', () => {
184
+ renderWithProviders(
185
+ <Badge variant="solid-main-normal" className="custom-class px-4">
186
+ Custom
187
+ </Badge>
188
+ );
189
+ const badge = screen.getByText('Custom');
190
+ expect(badge).toHaveClass('custom-class', 'px-4', 'bg-main-500');
191
+ });
192
+ });
193
+
194
+ describe('Accessibility', () => {
195
+ it('renders as non-interactive span element', () => {
196
+ const { container } = renderWithProviders(<Badge>Test</Badge>);
197
+ const badge = container.querySelector('span');
198
+ expect(badge).toBeInTheDocument();
199
+ // Should not be a button or have button role
200
+ expect(badge).not.toHaveAttribute('role', 'button');
201
+ });
202
+
203
+ it('does not have focus styles by default', () => {
204
+ renderWithProviders(<Badge>Test</Badge>);
205
+ const badge = screen.getByText('Test');
206
+ // Badge should not have focus-visible classes since it's non-interactive
207
+ expect(badge).not.toHaveClass('focus-visible:outline-none');
208
+ });
209
+
210
+ it('supports aria-label for screen readers', () => {
211
+ renderWithProviders(<Badge aria-label="Status: Active">Active</Badge>);
212
+ const badge = screen.getByText('Active');
213
+ expect(badge).toHaveAttribute('aria-label', 'Status: Active');
214
+ });
215
+
216
+ it('supports aria-describedby', () => {
217
+ renderWithProviders(
218
+ <Badge aria-describedby="badge-description">Test</Badge>
219
+ );
220
+ const badge = screen.getByText('Test');
221
+ expect(badge).toHaveAttribute('aria-describedby', 'badge-description');
222
+ });
223
+ });
224
+
225
+ describe('Integration', () => {
226
+ it('works with React.forwardRef', () => {
227
+ const ref = React.createRef<HTMLSpanElement>();
228
+ renderWithProviders(<Badge ref={ref}>Ref badge</Badge>);
229
+ expect(ref.current).toBeInstanceOf(HTMLSpanElement);
230
+ });
231
+
232
+ it('can be wrapped in interactive elements', () => {
233
+ const handleClick = vi.fn();
234
+ renderWithProviders(
235
+ <button onClick={handleClick}>
236
+ <Badge>Clickable badge</Badge>
237
+ </button>
238
+ );
239
+ const button = screen.getByRole('button');
240
+ expect(button).toBeInTheDocument();
241
+ expect(button).toHaveTextContent('Clickable badge');
242
+ });
243
+
244
+ it('works in complex layouts', () => {
245
+ renderWithProviders(
246
+ <div>
247
+ <h2>Status</h2>
248
+ <Badge variant="solid-main-normal">Active</Badge>
249
+ <Badge variant="outline-sec-muted">Pending</Badge>
250
+ <Badge variant="soft-acc-strong">Featured</Badge>
251
+ </div>
252
+ );
253
+ expect(screen.getByText('Active')).toBeInTheDocument();
254
+ expect(screen.getByText('Pending')).toBeInTheDocument();
255
+ expect(screen.getByText('Featured')).toBeInTheDocument();
256
+ });
257
+ });
258
+
259
+ describe('Edge Cases', () => {
260
+ it('handles empty children gracefully', () => {
261
+ const { container } = renderWithProviders(<Badge></Badge>);
262
+ const badge = container.querySelector('span');
263
+ expect(badge).toBeInTheDocument();
264
+ });
265
+
266
+ it('handles very long text content', () => {
267
+ const longText = 'This is a very long badge text that might cause layout issues but should be handled gracefully';
268
+ renderWithProviders(<Badge>{longText}</Badge>);
269
+ expect(screen.getByText(longText)).toBeInTheDocument();
270
+ });
271
+
272
+ it('handles special characters in text', () => {
273
+ const specialText = 'Badge with special chars: !@#$%^&*()_+-=[]{}|;:,.<>?';
274
+ renderWithProviders(<Badge>{specialText}</Badge>);
275
+ expect(screen.getByText(specialText)).toBeInTheDocument();
276
+ });
277
+
278
+ it('handles numeric content', () => {
279
+ renderWithProviders(<Badge>123</Badge>);
280
+ expect(screen.getByText('123')).toBeInTheDocument();
281
+ });
282
+
283
+ it('handles multiple className overrides', () => {
284
+ renderWithProviders(
285
+ <Badge variant="solid-main-normal" className="px-8 py-2 text-sm">
286
+ Override
287
+ </Badge>
288
+ );
289
+ const badge = screen.getByText('Override');
290
+ expect(badge).toHaveClass('px-8', 'py-2', 'text-sm');
291
+ });
292
+ });
293
+
294
+ describe('Style Variants', () => {
295
+ it('solid variant has no border or blur', () => {
296
+ renderWithProviders(<Badge variant="solid-main-normal">Solid</Badge>);
297
+ const badge = screen.getByText('Solid');
298
+ expect(badge).not.toHaveClass('border', 'backdrop-blur');
299
+ });
300
+
301
+ it('outline variant has outline', () => {
302
+ renderWithProviders(<Badge variant="outline-main-muted">Outline</Badge>);
303
+ const badge = screen.getByText('Outline');
304
+ expect(badge).toHaveClass('outline', 'outline-1');
305
+ });
306
+
307
+ it('soft variant has shadow', () => {
308
+ renderWithProviders(<Badge variant="soft-main-normal">Soft</Badge>);
309
+ const badge = screen.getByText('Soft');
310
+ expect(badge).toHaveClass('shadow-badge-soft');
311
+ });
312
+ });
313
+ });
314
+