@jmruthers/pace-core 0.5.76 → 0.5.78

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 (447) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/{RBACService-C4udt_Zp.d.ts → AuthService-Df3IozMG.d.ts} +10 -118
  3. package/dist/{DataTable-ntgmhO2W.d.ts → DataTable-BE0OXZKQ.d.ts} +9 -2
  4. package/dist/{DataTable-4GAVPIEG.js → DataTable-ETGVF4Y5.js} +50 -13
  5. package/dist/{PublicLoadingSpinner-BiNER8F5.d.ts → PublicLoadingSpinner-CnUaz0vG.d.ts} +5 -2
  6. package/dist/{UnifiedAuthProvider-Bj6YCf7c.d.ts → UnifiedAuthProvider-B391Aqum.d.ts} +42 -45
  7. package/dist/{UnifiedAuthProvider-3NKDOSOK.js → UnifiedAuthProvider-P5SOJAQ6.js} +4 -5
  8. package/dist/{api-DDMUKIUD.js → api-KG4A2X7P.js} +9 -3
  9. package/dist/{audit-6TOCAMKO.js → audit-65VNHEV2.js} +2 -2
  10. package/dist/{chunk-K34IM5CT.js → chunk-2OGV6IRV.js} +196 -626
  11. package/dist/chunk-2OGV6IRV.js.map +1 -0
  12. package/dist/{chunk-NTNILOBC.js → chunk-5BO3MI5Y.js} +4 -4
  13. package/dist/{chunk-XLZ7U46Z.js → chunk-CVMVPYAL.js} +9 -60
  14. package/dist/chunk-CVMVPYAL.js.map +1 -0
  15. package/dist/{chunk-URUTVZ7N.js → chunk-FL4ZCQLD.js} +2 -2
  16. package/dist/{chunk-LW7MMEAQ.js → chunk-FT2M4R4F.js} +2 -2
  17. package/dist/{chunk-5BSLGBYI.js → chunk-JCQZ6LA7.js} +2 -8
  18. package/dist/{chunk-5BSLGBYI.js.map → chunk-JCQZ6LA7.js.map} +1 -1
  19. package/dist/{chunk-KHJS6VIA.js → chunk-LRQ6RBJC.js} +157 -112
  20. package/dist/chunk-LRQ6RBJC.js.map +1 -0
  21. package/dist/{chunk-WN6XJWOS.js → chunk-MNJXXD6C.js} +274 -743
  22. package/dist/chunk-MNJXXD6C.js.map +1 -0
  23. package/dist/{chunk-KK73ZB4E.js → chunk-PTR5PMPE.js} +153 -132
  24. package/dist/chunk-PTR5PMPE.js.map +1 -0
  25. package/dist/{chunk-B2WTCLCV.js → chunk-Q7APDV6H.js} +18 -8
  26. package/dist/chunk-Q7APDV6H.js.map +1 -0
  27. package/dist/{chunk-A4FUBC7B.js → chunk-QGVSOUJ2.js} +2 -4
  28. package/dist/{chunk-A4FUBC7B.js.map → chunk-QGVSOUJ2.js.map} +1 -1
  29. package/dist/{chunk-FGMFQSHX.js → chunk-S63MFSY6.js} +500 -551
  30. package/dist/chunk-S63MFSY6.js.map +1 -0
  31. package/dist/{chunk-AFGTSUAD.js → chunk-VSOKOFRF.js} +4 -4
  32. package/dist/chunk-WUXCWRL6.js +20 -0
  33. package/dist/chunk-WUXCWRL6.js.map +1 -0
  34. package/dist/{chunk-Y6TXWPJO.js → chunk-YVVGHRGI.js} +105 -31
  35. package/dist/chunk-YVVGHRGI.js.map +1 -0
  36. package/dist/{chunk-M5IWZRBT.js → chunk-ZMNXIJP4.js} +2187 -981
  37. package/dist/chunk-ZMNXIJP4.js.map +1 -0
  38. package/dist/components.d.ts +6 -6
  39. package/dist/components.js +14 -18
  40. package/dist/components.js.map +1 -1
  41. package/dist/{database-C3Szpi5J.d.ts → database-BXAfr2Y_.d.ts} +18 -0
  42. package/dist/hooks.d.ts +5 -5
  43. package/dist/hooks.js +8 -9
  44. package/dist/hooks.js.map +1 -1
  45. package/dist/index.d.ts +19 -27
  46. package/dist/index.js +21 -29
  47. package/dist/index.js.map +1 -1
  48. package/dist/{organisation-BtshODVF.d.ts → organisation-D6qRDtbF.d.ts} +1 -1
  49. package/dist/providers.d.ts +7 -21
  50. package/dist/providers.js +3 -10
  51. package/dist/rbac/index.d.ts +71 -221
  52. package/dist/rbac/index.js +15 -16
  53. package/dist/{types-CGX9Vyf5.d.ts → types-BDg1mAGG.d.ts} +36 -6
  54. package/dist/types.d.ts +3 -3
  55. package/dist/types.js +61 -18
  56. package/dist/types.js.map +1 -1
  57. package/dist/{unified-CM7T0aTK.d.ts → unified-DQ4VcT7H.d.ts} +1 -1
  58. package/dist/{usePublicRouteParams-B-CumWRc.d.ts → usePublicRouteParams-BlgwXweB.d.ts} +3 -3
  59. package/dist/utils.d.ts +2 -2
  60. package/dist/utils.js +52 -9
  61. package/dist/utils.js.map +1 -1
  62. package/docs/CONTENT_AUDIT_REPORT.md +253 -0
  63. package/docs/DOCUMENTATION_AUDIT.md +172 -0
  64. package/docs/README.md +142 -147
  65. package/docs/STYLE_GUIDE.md +37 -0
  66. package/docs/api/classes/ColumnFactory.md +17 -17
  67. package/docs/api/classes/ErrorBoundary.md +1 -1
  68. package/docs/api/classes/InvalidScopeError.md +4 -4
  69. package/docs/api/classes/MissingUserContextError.md +4 -4
  70. package/docs/api/classes/OrganisationContextRequiredError.md +4 -4
  71. package/docs/api/classes/PermissionDeniedError.md +5 -5
  72. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  73. package/docs/api/classes/RBACAuditManager.md +8 -8
  74. package/docs/api/classes/RBACCache.md +35 -5
  75. package/docs/api/classes/RBACEngine.md +49 -20
  76. package/docs/api/classes/RBACError.md +4 -4
  77. package/docs/api/classes/RBACNotInitializedError.md +4 -4
  78. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  79. package/docs/api/classes/StorageUtils.md +1 -1
  80. package/docs/api/enums/FileCategory.md +1 -1
  81. package/docs/api/interfaces/AggregateConfig.md +4 -4
  82. package/docs/api/interfaces/ButtonProps.md +1 -1
  83. package/docs/api/interfaces/CardProps.md +1 -1
  84. package/docs/api/interfaces/ColorPalette.md +1 -1
  85. package/docs/api/interfaces/ColorShade.md +1 -1
  86. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  87. package/docs/api/interfaces/DataRecord.md +11 -0
  88. package/docs/api/interfaces/DataTableAction.md +65 -29
  89. package/docs/api/interfaces/DataTableColumn.md +36 -23
  90. package/docs/api/interfaces/DataTableProps.md +80 -38
  91. package/docs/api/interfaces/DataTableToolbarButton.md +7 -7
  92. package/docs/api/interfaces/EmptyStateConfig.md +5 -5
  93. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  94. package/docs/api/interfaces/EventLogoProps.md +1 -1
  95. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  96. package/docs/api/interfaces/FileMetadata.md +1 -1
  97. package/docs/api/interfaces/FileReference.md +1 -1
  98. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  99. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  100. package/docs/api/interfaces/FileUploadProps.md +1 -1
  101. package/docs/api/interfaces/FooterProps.md +1 -1
  102. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  103. package/docs/api/interfaces/InputProps.md +1 -1
  104. package/docs/api/interfaces/LabelProps.md +1 -1
  105. package/docs/api/interfaces/LoginFormProps.md +1 -1
  106. package/docs/api/interfaces/NavigationAccessRecord.md +11 -11
  107. package/docs/api/interfaces/NavigationContextType.md +9 -9
  108. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  109. package/docs/api/interfaces/NavigationItem.md +1 -1
  110. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  111. package/docs/api/interfaces/NavigationProviderProps.md +7 -7
  112. package/docs/api/interfaces/Organisation.md +1 -1
  113. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  114. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  115. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  116. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  117. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  118. package/docs/api/interfaces/PaceLoginPageProps.md +16 -3
  119. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  120. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  121. package/docs/api/interfaces/PagePermissionGuardProps.md +2 -2
  122. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  123. package/docs/api/interfaces/PaletteData.md +1 -1
  124. package/docs/api/interfaces/PermissionEnforcerProps.md +4 -4
  125. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  126. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  127. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  128. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  129. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  130. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  131. package/docs/api/interfaces/RBACConfig.md +1 -1
  132. package/docs/api/interfaces/RBACLogger.md +1 -1
  133. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  134. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  135. package/docs/api/interfaces/RouteAccessRecord.md +2 -2
  136. package/docs/api/interfaces/RouteConfig.md +2 -2
  137. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  138. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  139. package/docs/api/interfaces/StorageConfig.md +1 -1
  140. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  141. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  142. package/docs/api/interfaces/StorageListOptions.md +1 -1
  143. package/docs/api/interfaces/StorageListResult.md +1 -1
  144. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  145. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  146. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  147. package/docs/api/interfaces/StyleImport.md +1 -1
  148. package/docs/api/interfaces/SwitchProps.md +1 -1
  149. package/docs/api/interfaces/ToastActionElement.md +1 -1
  150. package/docs/api/interfaces/ToastProps.md +1 -1
  151. package/docs/api/interfaces/UnifiedAuthContextType.md +94 -521
  152. package/docs/api/interfaces/UnifiedAuthProviderProps.md +16 -16
  153. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  154. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  155. package/docs/api/interfaces/UsePublicEventLogoOptions.md +1 -1
  156. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  157. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  158. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  159. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  160. package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
  161. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  162. package/docs/api/interfaces/UserEventAccess.md +11 -11
  163. package/docs/api/interfaces/UserMenuProps.md +1 -1
  164. package/docs/api/interfaces/UserProfile.md +1 -1
  165. package/docs/api/modules.md +251 -269
  166. package/docs/api-reference/components.md +193 -0
  167. package/docs/api-reference/hooks.md +265 -0
  168. package/docs/api-reference/providers.md +6 -0
  169. package/docs/api-reference/types.md +6 -0
  170. package/docs/api-reference/utilities.md +207 -0
  171. package/docs/architecture/README.md +6 -0
  172. package/docs/{database-schema-requirements.md → architecture/database-schema-requirements.md} +6 -0
  173. package/docs/architecture/rbac-security-architecture.md +258 -0
  174. package/docs/architecture/services.md +9 -1
  175. package/docs/best-practices/README.md +6 -0
  176. package/docs/best-practices/accessibility.md +6 -0
  177. package/docs/{common-patterns.md → best-practices/common-patterns.md} +6 -0
  178. package/docs/best-practices/deployment.md +6 -0
  179. package/docs/best-practices/performance.md +475 -2
  180. package/docs/best-practices/security.md +6 -0
  181. package/docs/best-practices/testing.md +6 -0
  182. package/docs/core-concepts/authentication.md +6 -0
  183. package/docs/core-concepts/events.md +6 -0
  184. package/docs/core-concepts/organisations.md +6 -0
  185. package/docs/core-concepts/permissions.md +6 -0
  186. package/docs/core-concepts/rbac-system.md +8 -0
  187. package/docs/documentation-index.md +121 -182
  188. package/docs/{consuming-app-vite-config.md → getting-started/consuming-app-vite-config.md} +6 -0
  189. package/docs/getting-started/documentation-index.md +40 -0
  190. package/docs/getting-started/examples/README.md +878 -35
  191. package/docs/{faq.md → getting-started/faq.md} +7 -1
  192. package/docs/getting-started/installation-guide.md +6 -0
  193. package/docs/{quick-reference.md → getting-started/quick-reference.md} +6 -0
  194. package/docs/implementation-guides/app-layout.md +6 -0
  195. package/docs/implementation-guides/authentication.md +1021 -0
  196. package/docs/implementation-guides/component-styling.md +6 -0
  197. package/docs/implementation-guides/data-tables.md +1264 -2076
  198. package/docs/implementation-guides/dynamic-colors.md +6 -0
  199. package/docs/implementation-guides/event-theming-summary.md +6 -0
  200. package/docs/{file-reference-system.md → implementation-guides/file-reference-system.md} +6 -0
  201. package/docs/implementation-guides/file-upload-storage.md +6 -0
  202. package/docs/implementation-guides/forms.md +6 -0
  203. package/docs/implementation-guides/inactivity-tracking.md +6 -0
  204. package/docs/implementation-guides/navigation.md +6 -0
  205. package/docs/implementation-guides/organisation-security.md +6 -0
  206. package/docs/implementation-guides/permission-enforcement.md +6 -0
  207. package/docs/implementation-guides/public-pages-advanced.md +6 -0
  208. package/docs/implementation-guides/public-pages.md +6 -0
  209. package/docs/migration/MIGRATION_GUIDE.md +827 -351
  210. package/docs/migration/README.md +7 -1
  211. package/docs/migration/organisation-context-timing-fix.md +6 -0
  212. package/docs/migration/rbac-migration.md +44 -1
  213. package/docs/migration/service-architecture.md +6 -0
  214. package/docs/migration/v0.4.15-tailwind-scanning.md +6 -0
  215. package/docs/migration/v0.4.16-css-first-approach.md +6 -0
  216. package/docs/migration/v0.4.17-source-path-fix.md +6 -0
  217. package/docs/rbac/README-rbac-rls-integration.md +6 -0
  218. package/docs/rbac/README.md +6 -0
  219. package/docs/rbac/advanced-patterns.md +6 -0
  220. package/docs/rbac/api-reference.md +7 -1
  221. package/docs/rbac/breaking-changes-v3.md +222 -0
  222. package/docs/rbac/examples/rbac-rls-integration-example.md +6 -0
  223. package/docs/rbac/examples.md +6 -0
  224. package/docs/rbac/getting-started.md +6 -0
  225. package/docs/rbac/migration-guide.md +260 -0
  226. package/docs/rbac/quick-start.md +70 -13
  227. package/docs/rbac/rbac-rls-integration.md +6 -0
  228. package/docs/rbac/super-admin-guide.md +6 -0
  229. package/docs/rbac/troubleshooting.md +6 -0
  230. package/docs/security/README.md +6 -0
  231. package/docs/security/checklist.md +6 -0
  232. package/docs/styles/README.md +7 -1
  233. package/docs/{usage.md → styles/usage.md} +6 -0
  234. package/docs/testing/README.md +6 -0
  235. package/docs/{visual-testing.md → testing/visual-testing.md} +6 -0
  236. package/docs/troubleshooting/README.md +387 -5
  237. package/docs/troubleshooting/cake-page-permission-guard-issue-summary.md +6 -0
  238. package/docs/troubleshooting/common-issues.md +6 -0
  239. package/docs/troubleshooting/database-view-compatibility.md +6 -0
  240. package/docs/troubleshooting/organisation-context-setup.md +6 -0
  241. package/docs/troubleshooting/react-hooks-issue-analysis.md +6 -0
  242. package/docs/troubleshooting/styling-issues.md +6 -0
  243. package/docs/troubleshooting/tailwind-content-scanning.md +6 -0
  244. package/package.json +1 -1
  245. package/src/__tests__/helpers/__tests__/test-providers.test.tsx +2 -1
  246. package/src/__tests__/helpers/test-providers.tsx +3 -53
  247. package/src/components/DataTable/DataTable.test.tsx +319 -0
  248. package/src/components/DataTable/DataTable.tsx +32 -11
  249. package/src/components/DataTable/__tests__/{DataTable.comprehensive.test.tsx → DataTable.comprehensive.test.tsx.skip} +6 -4
  250. package/src/components/DataTable/__tests__/{DataTable.test.tsx → DataTable.test.tsx.skip} +6 -4
  251. package/src/components/DataTable/__tests__/DataTableCore.test.tsx +31 -9
  252. package/src/components/DataTable/__tests__/a11y.basic.test.tsx +601 -0
  253. package/src/components/DataTable/__tests__/keyboard.test.tsx +615 -0
  254. package/src/components/DataTable/__tests__/pagination.modes.test.tsx +639 -0
  255. package/src/components/DataTable/__tests__/ssr.strict-mode.test.tsx.skip +330 -0
  256. package/src/components/DataTable/components/AccessDeniedPage.tsx +2 -2
  257. package/src/components/DataTable/components/ActionButtons.tsx +88 -104
  258. package/src/components/DataTable/components/DataTableCore.tsx +309 -337
  259. package/src/components/DataTable/components/DataTableErrorBoundary.tsx +4 -2
  260. package/src/components/DataTable/components/DataTableModals.tsx +22 -1
  261. package/src/components/DataTable/components/EditableRow.tsx +69 -84
  262. package/src/components/DataTable/components/EmptyState.tsx +5 -1
  263. package/src/components/DataTable/components/ImportModal.tsx +65 -36
  264. package/src/components/DataTable/components/PaginationControls.tsx +40 -100
  265. package/src/components/DataTable/components/UnifiedTableBody.tsx +125 -148
  266. package/src/components/DataTable/context/DataTableContext.tsx +1 -1
  267. package/src/components/DataTable/core/ColumnFactory.ts +5 -0
  268. package/src/components/DataTable/examples/HierarchicalActionsExample.tsx +12 -10
  269. package/src/components/DataTable/examples/HierarchicalExample.tsx +1 -1
  270. package/src/components/DataTable/examples/InitialPageSizeExample.tsx +1 -0
  271. package/src/components/DataTable/examples/PerformanceExample.tsx +1 -0
  272. package/src/components/DataTable/hooks/__tests__/useColumnOrderPersistence.test.ts +1 -5
  273. package/src/components/DataTable/hooks/__tests__/useColumnVisibilityPersistence.test.ts +167 -0
  274. package/src/components/DataTable/hooks/index.ts +7 -0
  275. package/src/components/DataTable/hooks/useColumnOrderPersistence.ts +32 -15
  276. package/src/components/DataTable/hooks/useColumnVisibilityPersistence.ts +102 -0
  277. package/src/components/DataTable/hooks/useDataTableConfiguration.ts +89 -0
  278. package/src/components/DataTable/hooks/useDataTableDataPipeline.ts +117 -0
  279. package/src/components/DataTable/hooks/useDataTablePermissions.ts +71 -27
  280. package/src/components/DataTable/hooks/useDataTableState.ts +39 -11
  281. package/src/components/DataTable/hooks/useEffectiveColumnOrder.ts +33 -0
  282. package/src/components/DataTable/hooks/useHierarchicalState.ts +15 -1
  283. package/src/components/DataTable/hooks/useKeyboardNavigation.ts +447 -0
  284. package/src/components/DataTable/hooks/useServerSideDataEffect.ts +94 -0
  285. package/src/components/DataTable/hooks/useTableColumns.ts +10 -7
  286. package/src/components/DataTable/hooks/useTableHandlers.ts +174 -0
  287. package/src/components/DataTable/index.ts +12 -3
  288. package/src/components/DataTable/types.ts +129 -9
  289. package/src/components/DataTable/utils/__tests__/exportUtils.test.ts +159 -22
  290. package/src/components/DataTable/utils/__tests__/flexibleImport.test.ts +111 -0
  291. package/src/components/DataTable/utils/__tests__/rowUtils.test.ts +15 -29
  292. package/src/components/DataTable/utils/a11yUtils.ts +244 -0
  293. package/src/components/DataTable/utils/debugTools.ts +609 -0
  294. package/src/components/DataTable/utils/exportUtils.ts +114 -16
  295. package/src/components/DataTable/utils/flexibleImport.ts +202 -32
  296. package/src/components/DataTable/utils/hierarchicalUtils.ts +1 -1
  297. package/src/components/DataTable/utils/index.ts +2 -0
  298. package/src/components/DataTable/utils/paginationUtils.ts +350 -0
  299. package/src/components/DataTable/utils/rowUtils.ts +6 -5
  300. package/src/components/NavigationMenu/NavigationMenu.test.tsx +19 -24
  301. package/src/components/NavigationMenu/NavigationMenu.tsx +19 -8
  302. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.security.test.tsx +1 -23
  303. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +56 -6
  304. package/src/components/PaceLoginPage/PaceLoginPage.tsx +137 -13
  305. package/src/components/PublicLayout/__tests__/PublicPageHeader.test.tsx +1 -1
  306. package/src/components/Select/Select.tsx +1 -0
  307. package/src/components/examples/PermissionExample.tsx +173 -0
  308. package/src/examples/CorrectPublicPageImplementation.tsx +301 -0
  309. package/src/examples/PublicEventPage.tsx +274 -0
  310. package/src/examples/PublicPageApp.tsx +308 -0
  311. package/src/examples/PublicPageUsageExample.tsx +216 -0
  312. package/src/hooks/__tests__/useOrganisationPermissions.unit.test.tsx +12 -1
  313. package/src/hooks/__tests__/useOrganisationSecurity.unit.test.tsx +129 -17
  314. package/src/hooks/__tests__/useRBAC.unit.test.ts +151 -846
  315. package/src/hooks/useOrganisationPermissions.test.ts +42 -18
  316. package/src/hooks/useOrganisationPermissions.ts +12 -6
  317. package/src/hooks/useOrganisationSecurity.test.ts +138 -85
  318. package/src/hooks/useOrganisationSecurity.ts +41 -10
  319. package/src/index.ts +0 -1
  320. package/src/providers/AuthProvider.simplified.tsx +880 -0
  321. package/src/providers/UnifiedAuthProvider.test.simple.tsx +8 -8
  322. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +29 -19
  323. package/src/providers/index.ts +0 -1
  324. package/src/providers/services/EventServiceProvider.tsx +19 -15
  325. package/src/providers/services/InactivityServiceProvider.tsx +19 -15
  326. package/src/providers/services/OrganisationServiceProvider.tsx +19 -15
  327. package/src/providers/services/UnifiedAuthProvider.tsx +156 -127
  328. package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +1 -1
  329. package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +3 -3
  330. package/src/rbac/README.md +1 -1
  331. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +25 -27
  332. package/src/rbac/__tests__/auth-rbac-security.integration.test.tsx +313 -0
  333. package/src/rbac/__tests__/engine.comprehensive.test.ts +114 -348
  334. package/src/rbac/__tests__/rbac-engine-core-logic.test.ts +28 -110
  335. package/src/rbac/__tests__/rbac-engine-simplified.test.ts +33 -85
  336. package/src/rbac/__tests__/scenarios.user-role.test.tsx +2 -2
  337. package/src/rbac/adapters.tsx +26 -69
  338. package/src/rbac/api.test.ts +90 -27
  339. package/src/rbac/api.ts +61 -10
  340. package/src/rbac/audit.test.ts +33 -38
  341. package/src/rbac/audit.ts +21 -6
  342. package/src/rbac/cache.ts +33 -1
  343. package/src/rbac/components/NavigationGuard.tsx +11 -11
  344. package/src/rbac/components/NavigationProvider.test.tsx +11 -5
  345. package/src/rbac/components/NavigationProvider.tsx +37 -13
  346. package/src/rbac/components/PagePermissionGuard.tsx +111 -50
  347. package/src/rbac/components/PagePermissionProvider.tsx +5 -5
  348. package/src/rbac/components/PermissionEnforcer.tsx +11 -11
  349. package/src/rbac/components/RoleBasedRouter.tsx +5 -5
  350. package/src/rbac/components/SecureDataProvider.tsx +5 -5
  351. package/src/rbac/components/__tests__/NavigationGuard.test.tsx +8 -8
  352. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +14 -14
  353. package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +12 -12
  354. package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +6 -6
  355. package/src/rbac/engine.test.simple.ts +19 -13
  356. package/src/rbac/engine.test.ts +1 -0
  357. package/src/rbac/engine.ts +330 -766
  358. package/src/rbac/errors.ts +156 -0
  359. package/src/rbac/hooks/usePermissions.ts +32 -10
  360. package/src/rbac/hooks/useRBAC.test.ts +126 -512
  361. package/src/rbac/hooks/useRBAC.ts +147 -193
  362. package/src/rbac/hooks/useResolvedScope.ts +12 -0
  363. package/src/rbac/index.ts +7 -4
  364. package/src/rbac/security.ts +109 -18
  365. package/src/rbac/types.ts +12 -1
  366. package/src/services/AuthService.ts +2 -15
  367. package/src/services/EventService.ts +43 -46
  368. package/src/services/OrganisationService.ts +51 -31
  369. package/src/services/__tests__/AuthService.test.ts +1 -1
  370. package/src/services/__tests__/EventService.test.ts +1 -1
  371. package/src/services/__tests__/OrganisationService.test.ts +1 -1
  372. package/src/services/base/BaseService.ts +8 -0
  373. package/src/styles/base.css +208 -0
  374. package/src/styles/semantic.css +24 -0
  375. package/src/types/database.generated.ts +7347 -0
  376. package/src/types/database.ts +20 -0
  377. package/src/utils/logger.ts +179 -0
  378. package/src/utils/organisationContext.ts +11 -4
  379. package/src/utils/storage/__tests__/helpers.unit.test.ts +6 -2
  380. package/dist/appNameResolver-UURKN7NF.js +0 -22
  381. package/dist/audit-6TOCAMKO.js.map +0 -1
  382. package/dist/chunk-B2WTCLCV.js.map +0 -1
  383. package/dist/chunk-FGMFQSHX.js.map +0 -1
  384. package/dist/chunk-K34IM5CT.js.map +0 -1
  385. package/dist/chunk-KHJS6VIA.js.map +0 -1
  386. package/dist/chunk-KK73ZB4E.js.map +0 -1
  387. package/dist/chunk-M5IWZRBT.js.map +0 -1
  388. package/dist/chunk-ULBI5JGB.js +0 -109
  389. package/dist/chunk-ULBI5JGB.js.map +0 -1
  390. package/dist/chunk-WN6XJWOS.js.map +0 -1
  391. package/dist/chunk-XLZ7U46Z.js.map +0 -1
  392. package/dist/chunk-Y6TXWPJO.js.map +0 -1
  393. package/docs/DOCUMENTATION_CHECKLIST.md +0 -281
  394. package/docs/TERMINOLOGY.md +0 -231
  395. package/docs/api/interfaces/RBACContextType.md +0 -468
  396. package/docs/api/interfaces/RBACProviderProps.md +0 -107
  397. package/docs/best-practices/performance-expansion.md +0 -473
  398. package/docs/breaking-changes.md +0 -179
  399. package/docs/consuming-app-example.md +0 -290
  400. package/docs/documentation-templates.md +0 -539
  401. package/docs/examples/navigation-menu-auth-fix.md +0 -344
  402. package/docs/getting-started/examples/basic-auth-app.md +0 -520
  403. package/docs/getting-started/examples/full-featured-app.md +0 -616
  404. package/docs/getting-started/quick-start.md +0 -376
  405. package/docs/implementation-guides/datatable-filtering.md +0 -313
  406. package/docs/implementation-guides/datatable-rbac-usage.md +0 -317
  407. package/docs/implementation-guides/hierarchical-datatable.md +0 -850
  408. package/docs/implementation-guides/large-datasets.md +0 -281
  409. package/docs/implementation-guides/performance.md +0 -403
  410. package/docs/migration/quick-migration-guide.md +0 -320
  411. package/docs/migration-guide.md +0 -193
  412. package/docs/migration-guides/unified-auth-provider-mandatory-timeouts.md +0 -226
  413. package/docs/performance/README.md +0 -551
  414. package/docs/style-guide.md +0 -964
  415. package/docs/troubleshooting/authentication-issues.md +0 -334
  416. package/docs/troubleshooting/debugging.md +0 -1117
  417. package/docs/troubleshooting/migration.md +0 -918
  418. package/src/__tests__/hooks/usePermissions.test.ts +0 -261
  419. package/src/components/PaceAppLayout/__tests__/PaceAppLayout.rbac.test.tsx +0 -574
  420. package/src/hooks/__tests__/ServiceHooks.test.tsx +0 -613
  421. package/src/hooks/services/__tests__/useServiceHooks.test.tsx +0 -137
  422. package/src/hooks/services/usePermissions.ts +0 -70
  423. package/src/hooks/services/useRBACService.ts +0 -30
  424. package/src/hooks/usePermissionCheck.ts +0 -150
  425. package/src/providers/__tests__/ServiceProviders.test.tsx +0 -477
  426. package/src/providers/services/RBACServiceProvider.tsx +0 -79
  427. package/src/rbac/__tests__/integration.authflow.test.tsx +0 -119
  428. package/src/rbac/__tests__/integration.navigation.test.tsx +0 -69
  429. package/src/rbac/__tests__/integration.securedata.test.tsx +0 -92
  430. package/src/rbac/__tests__/integration.smoke.test.tsx +0 -73
  431. package/src/rbac/providers/RBACProvider.tsx +0 -645
  432. package/src/rbac/providers/__tests__/RBACProvider.integration.test.tsx +0 -688
  433. package/src/rbac/providers/__tests__/RBACProvider.test.tsx +0 -1186
  434. package/src/rbac/providers/index.ts +0 -11
  435. package/src/services/RBACService.ts +0 -522
  436. package/src/services/__tests__/RBACService.test.ts +0 -492
  437. package/src/services/interfaces/IRBACService.ts +0 -62
  438. package/src/utils/appNameResolver.test 2.ts +0 -494
  439. /package/dist/{DataTable-4GAVPIEG.js.map → DataTable-ETGVF4Y5.js.map} +0 -0
  440. /package/dist/{UnifiedAuthProvider-3NKDOSOK.js.map → UnifiedAuthProvider-P5SOJAQ6.js.map} +0 -0
  441. /package/dist/{api-DDMUKIUD.js.map → api-KG4A2X7P.js.map} +0 -0
  442. /package/dist/{appNameResolver-UURKN7NF.js.map → audit-65VNHEV2.js.map} +0 -0
  443. /package/dist/{chunk-NTNILOBC.js.map → chunk-5BO3MI5Y.js.map} +0 -0
  444. /package/dist/{chunk-URUTVZ7N.js.map → chunk-FL4ZCQLD.js.map} +0 -0
  445. /package/dist/{chunk-LW7MMEAQ.js.map → chunk-FT2M4R4F.js.map} +0 -0
  446. /package/dist/{chunk-AFGTSUAD.js.map → chunk-VSOKOFRF.js.map} +0 -0
  447. /package/docs/{app.css.example → styles/app.css.example} +0 -0
@@ -0,0 +1,330 @@
1
+ /**
2
+ * @file SSR and Strict Mode Compatibility Tests
3
+ * @package @jmruthers/pace-core
4
+ * @module Components/DataTable/__tests__
5
+ * @since 2.0.0
6
+ *
7
+ * Tests for SSR safety and React 18 strict mode compatibility.
8
+ */
9
+
10
+ import React from 'react';
11
+ import { render, act } from '@testing-library/react';
12
+ import { vi, beforeEach, afterEach } from 'vitest';
13
+ import { DataTable } from '../DataTable';
14
+ import type { DataTableColumn } from '../types';
15
+
16
+ // Mock data for testing
17
+ interface TestData {
18
+ id: string;
19
+ name: string;
20
+ email: string;
21
+ }
22
+
23
+ const testData: TestData[] = [
24
+ { id: '1', name: 'John Doe', email: 'john@example.com' },
25
+ { id: '2', name: 'Jane Smith', email: 'jane@example.com' },
26
+ ];
27
+
28
+ const testColumns: DataTableColumn<TestData>[] = [
29
+ {
30
+ accessorKey: 'name',
31
+ header: 'Name',
32
+ sortable: true,
33
+ },
34
+ {
35
+ accessorKey: 'email',
36
+ header: 'Email',
37
+ sortable: true,
38
+ },
39
+ ];
40
+
41
+ const defaultProps = {
42
+ data: testData,
43
+ columns: testColumns,
44
+ rbac: { pageId: 'test' },
45
+ features: {
46
+ search: true,
47
+ pagination: true,
48
+ sorting: true,
49
+ filtering: true,
50
+ export: true,
51
+ import: true,
52
+ selection: true,
53
+ creation: true,
54
+ editing: true,
55
+ deletion: true,
56
+ deleteSelected: true,
57
+ grouping: true,
58
+ columnVisibility: true,
59
+ columnReordering: true,
60
+ },
61
+ };
62
+
63
+ describe('DataTable SSR and Strict Mode Compatibility', () => {
64
+ beforeEach(() => {
65
+ // Mock window object for SSR testing - only if window exists
66
+ if (typeof window !== 'undefined') {
67
+ Object.defineProperty(window, 'matchMedia', {
68
+ writable: true,
69
+ value: vi.fn().mockImplementation(query => ({
70
+ matches: false,
71
+ media: query,
72
+ onchange: null,
73
+ addListener: vi.fn(),
74
+ removeListener: vi.fn(),
75
+ addEventListener: vi.fn(),
76
+ removeEventListener: vi.fn(),
77
+ dispatchEvent: vi.fn(),
78
+ })),
79
+ });
80
+ }
81
+ });
82
+
83
+ afterEach(() => {
84
+ vi.clearAllMocks();
85
+ });
86
+
87
+ describe('SSR Safety', () => {
88
+ it('should render without browser APIs in SSR environment', () => {
89
+ // Mock SSR environment by setting window to undefined
90
+ const originalWindow = global.window;
91
+ (global as any).window = undefined;
92
+
93
+ expect(() => {
94
+ render(<DataTable {...defaultProps} />);
95
+ }).not.toThrow();
96
+
97
+ // Restore window
98
+ global.window = originalWindow;
99
+ });
100
+
101
+ it('should handle missing localStorage gracefully', () => {
102
+ const originalLocalStorage = global.localStorage;
103
+ delete (global as any).localStorage;
104
+
105
+ expect(() => {
106
+ render(<DataTable {...defaultProps} />);
107
+ }).not.toThrow();
108
+
109
+ // Restore localStorage
110
+ global.localStorage = originalLocalStorage;
111
+ });
112
+
113
+ it('should handle missing document gracefully', () => {
114
+ const originalDocument = global.document;
115
+ delete (global as any).document;
116
+
117
+ expect(() => {
118
+ render(<DataTable {...defaultProps} />);
119
+ }).not.toThrow();
120
+
121
+ // Restore document
122
+ global.document = originalDocument;
123
+ });
124
+ });
125
+
126
+ describe('React 18 Strict Mode Compatibility', () => {
127
+ it('should handle strict mode double-invoke without errors', () => {
128
+ let renderCount = 0;
129
+
130
+ const TestComponent = () => {
131
+ renderCount++;
132
+ return <DataTable {...defaultProps} />;
133
+ };
134
+
135
+ expect(() => {
136
+ render(
137
+ <React.StrictMode>
138
+ <TestComponent />
139
+ </React.StrictMode>
140
+ );
141
+ }).not.toThrow();
142
+
143
+ // In strict mode, components are rendered twice
144
+ expect(renderCount).toBe(2);
145
+ });
146
+
147
+ it('should handle strict mode with useEffect cleanup', () => {
148
+ const TestComponent = () => {
149
+ React.useEffect(() => {
150
+ // This effect should be called twice in strict mode
151
+ return () => {
152
+ // Cleanup should be called twice in strict mode
153
+ };
154
+ }, []);
155
+
156
+ return <DataTable {...defaultProps} />;
157
+ };
158
+
159
+ expect(() => {
160
+ render(
161
+ <React.StrictMode>
162
+ <TestComponent />
163
+ </React.StrictMode>
164
+ );
165
+ }).not.toThrow();
166
+ });
167
+
168
+ it('should handle strict mode with state updates', () => {
169
+ const TestComponent = () => {
170
+ const [count, setCount] = React.useState(0);
171
+
172
+ React.useEffect(() => {
173
+ setCount(prev => prev + 1);
174
+ }, []);
175
+
176
+ return (
177
+ <div>
178
+ <span data-testid="count">{count}</span>
179
+ <DataTable {...defaultProps} />
180
+ </div>
181
+ );
182
+ };
183
+
184
+ expect(() => {
185
+ render(
186
+ <React.StrictMode>
187
+ <TestComponent />
188
+ </React.StrictMode>
189
+ );
190
+ }).not.toThrow();
191
+ });
192
+ });
193
+
194
+ describe('Browser API Guards', () => {
195
+ it('should guard window.location.reload calls', () => {
196
+ const originalReload = window.location.reload;
197
+ const mockReload = vi.fn();
198
+ window.location.reload = mockReload;
199
+
200
+ // Test AccessDeniedPage component
201
+ const { AccessDeniedPage } = require('../components/AccessDeniedPage');
202
+
203
+ render(
204
+ <AccessDeniedPage
205
+ resource="test"
206
+ operation="read"
207
+ />
208
+ );
209
+
210
+ // Should not call reload during SSR
211
+ expect(mockReload).not.toHaveBeenCalled();
212
+
213
+ window.location.reload = originalReload;
214
+ });
215
+
216
+ it('should guard window.history.back calls', () => {
217
+ const originalBack = window.history.back;
218
+ const mockBack = vi.fn();
219
+ window.history.back = mockBack;
220
+
221
+ // Test AccessDeniedPage component
222
+ const { AccessDeniedPage } = require('../components/AccessDeniedPage');
223
+
224
+ render(
225
+ <AccessDeniedPage
226
+ resource="test"
227
+ operation="read"
228
+ />
229
+ );
230
+
231
+ // Should not call back during SSR
232
+ expect(mockBack).not.toHaveBeenCalled();
233
+
234
+ window.history.back = originalBack;
235
+ });
236
+
237
+ it('should guard localStorage access', () => {
238
+ const originalLocalStorage = global.localStorage;
239
+ const mockLocalStorage = {
240
+ getItem: vi.fn(),
241
+ setItem: vi.fn(),
242
+ removeItem: vi.fn(),
243
+ clear: vi.fn(),
244
+ };
245
+ global.localStorage = mockLocalStorage as any;
246
+
247
+ // Test column order persistence hook
248
+ const { useColumnOrderPersistence } = require('../hooks/useColumnOrderPersistence');
249
+
250
+ const TestComponent = () => {
251
+ const { columnOrder } = useColumnOrderPersistence({
252
+ tableId: 'test',
253
+ enablePersistence: true,
254
+ });
255
+ return <div data-testid="order">{JSON.stringify(columnOrder)}</div>;
256
+ };
257
+
258
+ render(<TestComponent />);
259
+
260
+ // Should not access localStorage during SSR
261
+ expect(mockLocalStorage.getItem).not.toHaveBeenCalled();
262
+
263
+ global.localStorage = originalLocalStorage;
264
+ });
265
+ });
266
+
267
+ describe('Hydration Safety', () => {
268
+ it('should not cause hydration mismatches', () => {
269
+ const TestComponent = () => {
270
+ const [mounted, setMounted] = React.useState(false);
271
+
272
+ React.useEffect(() => {
273
+ setMounted(true);
274
+ }, []);
275
+
276
+ return (
277
+ <div>
278
+ <DataTable {...defaultProps} />
279
+ {mounted && <div data-testid="client-only">Client Only</div>}
280
+ </div>
281
+ );
282
+ };
283
+
284
+ expect(() => {
285
+ render(<TestComponent />);
286
+ }).not.toThrow();
287
+ });
288
+
289
+ it('should handle server/client differences gracefully', () => {
290
+ const TestComponent = () => {
291
+ const [isClient, setIsClient] = React.useState(false);
292
+
293
+ React.useEffect(() => {
294
+ setIsClient(true);
295
+ }, []);
296
+
297
+ return (
298
+ <div>
299
+ <DataTable {...defaultProps} />
300
+ <div data-testid="client-state">
301
+ {isClient ? 'Client' : 'Server'}
302
+ </div>
303
+ </div>
304
+ );
305
+ };
306
+
307
+ expect(() => {
308
+ render(<TestComponent />);
309
+ }).not.toThrow();
310
+ });
311
+ });
312
+
313
+ describe('Performance in Strict Mode', () => {
314
+ it('should not cause performance issues with double rendering', () => {
315
+ const startTime = performance.now();
316
+
317
+ render(
318
+ <React.StrictMode>
319
+ <DataTable {...defaultProps} />
320
+ </React.StrictMode>
321
+ );
322
+
323
+ const endTime = performance.now();
324
+ const renderTime = endTime - startTime;
325
+
326
+ // Should render quickly even with double rendering
327
+ expect(renderTime).toBeLessThan(1000); // 1 second threshold
328
+ });
329
+ });
330
+ });
@@ -92,7 +92,7 @@ export function AccessDeniedPage({
92
92
  const handleRetry = () => {
93
93
  if (onRetry) {
94
94
  onRetry();
95
- } else {
95
+ } else if (typeof window !== 'undefined') {
96
96
  window.location.reload();
97
97
  }
98
98
  };
@@ -100,7 +100,7 @@ export function AccessDeniedPage({
100
100
  const handleBack = () => {
101
101
  if (onBack) {
102
102
  onBack();
103
- } else {
103
+ } else if (typeof window !== 'undefined') {
104
104
  window.history.back();
105
105
  }
106
106
  };
@@ -1,38 +1,22 @@
1
- import React from 'react';
2
- import { Button } from '../../Button/Button';
1
+ import React, { useMemo } from 'react';
3
2
  import { MoreHorizontal } from 'lucide-react';
3
+ import type { Row } from '@tanstack/react-table';
4
+
5
+ import { Button } from '../../Button/Button';
4
6
  import {
5
7
  Select,
6
8
  SelectContent,
7
9
  SelectItem,
8
10
  SelectTrigger,
9
- SelectValue,
10
11
  } from '../../Select/Select';
12
+ import { createLogger } from '../../../utils/logger';
13
+ import type { ActionCondition, DataRecord, DataTableAction } from '../types';
11
14
 
12
- interface ActionButtonsProps {
13
- row: any;
15
+ interface ActionButtonsProps<TData extends DataRecord = DataRecord> {
16
+ row: Row<TData>;
14
17
  rowId?: string;
15
18
  index?: number;
16
- actions?: Array<{
17
- label: string;
18
- onClick: (row: any) => void;
19
- icon?: any;
20
- variant?: 'default' | 'destructive' | 'outline' | 'secondary' | 'ghost';
21
- disabled?: boolean | ((row?: any) => boolean);
22
- visible?: boolean | ((row?: any) => boolean);
23
- testId?: string;
24
- showInEditMode?: boolean;
25
- hideInViewMode?: boolean;
26
- showInViewMode?: boolean;
27
- hidden?: boolean;
28
- // Hierarchical action properties
29
- showForParent?: boolean;
30
- showForChild?: boolean;
31
- parentIcon?: any;
32
- childIcon?: any;
33
- parentLabel?: string;
34
- childLabel?: string;
35
- }>;
19
+ actions?: DataTableAction<TData>[];
36
20
  isEditing?: boolean;
37
21
  className?: string;
38
22
  // Hierarchical context
@@ -53,98 +37,109 @@ interface ActionButtonsProps {
53
37
  };
54
38
  }
55
39
 
56
- export function ActionButtons({
57
- row,
40
+ const evaluateCondition = <TData extends DataRecord>(
41
+ condition: ActionCondition<TData> | undefined,
42
+ row: TData,
43
+ defaultValue: boolean
44
+ ): boolean => {
45
+ if (typeof condition === 'boolean') {
46
+ return condition;
47
+ }
48
+
49
+ if (typeof condition === 'function') {
50
+ return condition(row);
51
+ }
52
+
53
+ return defaultValue;
54
+ };
55
+
56
+ function ActionButtonsComponent<TData extends DataRecord = DataRecord>({
57
+ row,
58
58
  actions = [],
59
59
  isEditing = false,
60
60
  isParent = false,
61
61
  hierarchical = false,
62
- rbac,
63
62
  permissions,
64
- }: ActionButtonsProps) {
65
- // Return null if no actions are provided
63
+ }: ActionButtonsProps<TData>) {
64
+ const logger = createLogger('ActionButtons');
65
+ const rowData = row.original;
66
+
66
67
  if (actions.length === 0) {
67
68
  return null;
68
69
  }
69
70
 
70
- // Use provided actions
71
- const allActions = actions;
72
-
73
- // Filter actions based on current mode, visibility, hierarchical context, and RBAC
74
- const visibleActions = allActions.filter(action => {
71
+ const visibleActions = useMemo(() => actions.filter(action => {
75
72
  if (action.hidden) return false;
76
-
77
- // MANDATORY: Check RBAC permissions for standard actions
73
+
78
74
  if (permissions) {
79
75
  if (action.label === 'Edit' && !permissions.canUpdate.can) return false;
80
76
  if (action.label === 'Delete' && !permissions.canDelete.can) return false;
81
77
  }
82
-
83
- // Check hierarchical visibility
78
+
84
79
  if (hierarchical) {
85
80
  if (action.showForParent && !isParent) return false;
86
81
  if (action.showForChild && isParent) return false;
87
82
  }
88
-
89
- // Check visible function/boolean if provided
90
- if (typeof action.visible === 'function') {
91
- return action.visible(row);
92
- } else if (typeof action.visible === 'boolean') {
93
- return action.visible;
83
+
84
+ if (!evaluateCondition(action.visible, rowData, true)) {
85
+ return false;
94
86
  }
95
-
87
+
96
88
  if (isEditing) {
97
89
  return action.showInEditMode !== false;
98
- } else {
99
- return action.hideInViewMode !== true && action.showInViewMode !== false;
100
90
  }
101
- });
102
91
 
103
- // Return null if no visible actions
92
+ return action.hideInViewMode !== true && action.showInViewMode !== false;
93
+ }), [actions, permissions, hierarchical, isParent, rowData, isEditing]);
94
+
104
95
  if (visibleActions.length === 0) {
105
96
  return null;
106
97
  }
107
98
 
108
- // If only a few actions, render them as individual icon-only buttons
99
+ const renderAction = (action: DataTableAction<TData>, actionIndex: number) => {
100
+ let Icon = action.icon;
101
+ let label = action.label;
102
+
103
+ if (hierarchical) {
104
+ if (isParent && action.parentIcon) {
105
+ Icon = action.parentIcon;
106
+ } else if (!isParent && action.childIcon) {
107
+ Icon = action.childIcon;
108
+ }
109
+
110
+ if (isParent && action.parentLabel) {
111
+ label = action.parentLabel;
112
+ } else if (!isParent && action.childLabel) {
113
+ label = action.childLabel;
114
+ }
115
+ }
116
+
117
+ return { Icon, label } as const;
118
+ };
119
+
109
120
  if (visibleActions.length <= 6) {
110
121
  return (
111
122
  <>
112
123
  {visibleActions.map((action, actionIndex) => {
113
- // Determine the appropriate icon and label based on hierarchical context
114
- let Icon = action.icon;
115
- let label = action.label;
116
-
117
- if (hierarchical) {
118
- if (isParent && action.parentIcon) {
119
- Icon = action.parentIcon;
120
- } else if (!isParent && action.childIcon) {
121
- Icon = action.childIcon;
122
- }
123
-
124
- if (isParent && action.parentLabel) {
125
- label = action.parentLabel;
126
- } else if (!isParent && action.childLabel) {
127
- label = action.childLabel;
128
- }
129
- }
130
-
131
- const isDisabled = typeof action.disabled === 'function' ? action.disabled(row) : action.disabled;
132
-
124
+ const { Icon, label } = renderAction(action, actionIndex);
125
+ const isDisabled = evaluateCondition(action.disabled, rowData, false);
126
+
133
127
  return (
134
128
  <Button
135
129
  key={actionIndex}
136
130
  variant={action.variant === 'destructive' ? 'destructive' : 'ghost'}
137
131
  size="sm"
138
132
  onClick={() => {
139
- // DEBUG: Log action click
140
- if (import.meta.env.MODE === 'development') {
141
- console.log('[ActionButtons] Action clicked:', {
133
+ if (import.meta.env.MODE === 'development' && !isDisabled) {
134
+ logger.debug('Action clicked:', {
142
135
  actionLabel: action.label,
143
- rowOriginal: row.original,
136
+ rowOriginal: rowData,
144
137
  actionOnClick: typeof action.onClick
145
138
  });
146
139
  }
147
- action.onClick(row.original);
140
+ if (!isDisabled) {
141
+ action.onClick(rowData);
142
+ }
148
143
  }}
149
144
  disabled={isDisabled}
150
145
  aria-disabled={isDisabled}
@@ -160,7 +155,6 @@ export function ActionButtons({
160
155
  );
161
156
  }
162
157
 
163
- // For many actions, use dropdown menu
164
158
  return (
165
159
  <Select>
166
160
  <SelectTrigger asChild>
@@ -171,43 +165,30 @@ export function ActionButtons({
171
165
  </SelectTrigger>
172
166
  <SelectContent className="!bg-main-50 border border-sec-200 shadow-lg z-[9999]" style={{ backgroundColor: 'white' }}>
173
167
  {visibleActions.map((action, actionIndex) => {
174
- // Determine the appropriate icon and label based on hierarchical context
175
- let Icon = action.icon;
176
- let label = action.label;
177
-
178
- if (hierarchical) {
179
- if (isParent && action.parentIcon) {
180
- Icon = action.parentIcon;
181
- } else if (!isParent && action.childIcon) {
182
- Icon = action.childIcon;
183
- }
184
-
185
- if (isParent && action.parentLabel) {
186
- label = action.parentLabel;
187
- } else if (!isParent && action.childLabel) {
188
- label = action.childLabel;
189
- }
190
- }
191
-
168
+ const { Icon, label } = renderAction(action, actionIndex);
169
+ const isDisabled = evaluateCondition(action.disabled, rowData, false);
170
+
192
171
  return (
193
172
  <SelectItem
194
173
  key={actionIndex}
195
174
  value={`action-${actionIndex}`}
196
175
  onClick={() => {
197
- // DEBUG: Log action click
198
- if (import.meta.env.MODE === 'development') {
199
- console.log('[ActionButtons] Dropdown action clicked:', {
176
+ if (import.meta.env.MODE === 'development' && !isDisabled) {
177
+ logger.debug('Action clicked from dropdown:', {
200
178
  actionLabel: action.label,
201
- rowOriginal: row.original,
202
- actionOnClick: typeof action.onClick
179
+ rowOriginal: rowData
203
180
  });
204
181
  }
205
- action.onClick(row.original);
182
+ if (!isDisabled) {
183
+ action.onClick(rowData);
184
+ }
206
185
  }}
207
- className={action.variant === 'destructive' ? 'text-acc-600' : ''}
186
+ data-testid={action.testId}
187
+ className="flex items-center gap-2"
188
+ aria-disabled={isDisabled}
208
189
  >
209
- {Icon && <Icon className="mr-2 h-4 w-4" />}
210
- {label}
190
+ {Icon && <Icon className="h-4 w-4" />}
191
+ <span>{label}</span>
211
192
  </SelectItem>
212
193
  );
213
194
  })}
@@ -216,3 +197,6 @@ export function ActionButtons({
216
197
  );
217
198
  }
218
199
 
200
+ const ActionButtons = React.memo(ActionButtonsComponent) as typeof ActionButtonsComponent;
201
+
202
+ export { ActionButtons };