@jmruthers/pace-core 0.6.10 → 0.6.11

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 (726) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/audit-tool/00-dependencies.cjs +46 -13
  3. package/audit-tool/audits/01-pace-core-compliance.cjs +96 -21
  4. package/audit-tool/audits/02-project-structure.cjs +13 -3
  5. package/audit-tool/audits/03-architecture.cjs +78 -4
  6. package/audit-tool/audits/04-code-quality.cjs +9 -2
  7. package/audit-tool/audits/05-styling.cjs +19 -7
  8. package/audit-tool/audits/06-security-rbac.cjs +105 -14
  9. package/audit-tool/audits/07-api-tech-stack.cjs +31 -15
  10. package/audit-tool/audits/08-testing-documentation.cjs +11 -3
  11. package/audit-tool/audits/09-operations.cjs +19 -7
  12. package/audit-tool/index.cjs +22 -11
  13. package/audit-tool/utils/report-utils.cjs +4 -0
  14. package/cursor-rules/01-pace-core-compliance.mdc +1 -0
  15. package/cursor-rules/02-project-structure.mdc +1 -0
  16. package/cursor-rules/03-architecture.mdc +3 -1
  17. package/cursor-rules/04-code-quality.mdc +1 -0
  18. package/cursor-rules/05-styling.mdc +41 -7
  19. package/cursor-rules/06-security-rbac.mdc +2 -1
  20. package/cursor-rules/07-api-tech-stack.mdc +1 -0
  21. package/cursor-rules/08-testing-documentation.mdc +1 -0
  22. package/cursor-rules/09-operations.mdc +1 -0
  23. package/dist/{DataTable-SAXFG4XI.js → DataTable-EFYP2QLE.js} +10 -7
  24. package/dist/{InactivityServiceProvider-DHryoh6K.d.ts → InactivityServiceProvider-BbxwwDz1.d.ts} +10 -1
  25. package/dist/{UnifiedAuthProvider-CiBAl9-s.d.ts → UnifiedAuthProvider-Bkt_tzdS.d.ts} +56 -24
  26. package/dist/{api-F47QJ7FX.js → api-BZR2CYXL.js} +3 -2
  27. package/dist/api-result-USV1Czr-.d.ts +51 -0
  28. package/dist/{audit-Z6ZZBWLU.js → audit-HI2DHUVU.js} +2 -1
  29. package/dist/{auth-BZOJqrdd.d.ts → auth-JvdRVaud.d.ts} +1 -1
  30. package/dist/{chunk-KSNLMI7N.js → chunk-2DL2WSOE.js} +1 -155
  31. package/dist/{chunk-MPY44PWB.js → chunk-2OEVOGGR.js} +4648 -3560
  32. package/dist/chunk-44CNXN4P.js +15 -0
  33. package/dist/{chunk-Y4PF6HIM.js → chunk-4R3T5ENU.js} +867 -786
  34. package/dist/{chunk-LNHFAF4X.js → chunk-7A6IMHH2.js} +289 -247
  35. package/dist/chunk-CU2BU2MQ.js +2 -0
  36. package/dist/{chunk-JJEYZ3DX.js → chunk-D6BMFMQZ.js} +37 -2
  37. package/dist/{chunk-BCTXBU6U.js → chunk-ENLXB7GP.js} +88 -71
  38. package/dist/{chunk-FBZ7U3ID.js → chunk-J2KQK6DG.js} +937 -987
  39. package/dist/{chunk-TFIPNIPE.js → chunk-KJXRL3XE.js} +3300 -2245
  40. package/dist/{chunk-3GWSPISD.js → chunk-L5LFKKLJ.js} +1 -1
  41. package/dist/{chunk-X5EAU5G7.js → chunk-PCSHBLPB.js} +132 -114
  42. package/dist/{chunk-NIU6DPQV.js → chunk-QRYSEPHB.js} +2 -0
  43. package/dist/{chunk-KYURMOQM.js → chunk-V7FTM2LU.js} +423 -320
  44. package/dist/chunk-WY6Y7KC3.js +264 -0
  45. package/dist/{chunk-FN52B75D.js → chunk-XOJME5T7.js} +176 -15
  46. package/dist/{chunk-7YDC7LMU.js → chunk-XPFVT3GN.js} +71 -66
  47. package/dist/{chunk-66R6RLUZ.js → chunk-YFTFFJIV.js} +3 -3
  48. package/dist/{chunk-W46INAVW.js → chunk-YYTWKVHO.js} +688 -570
  49. package/dist/components.d.ts +8 -7
  50. package/dist/components.js +17 -15
  51. package/dist/{database.generated-DT8JTZiP.d.ts → database.generated-qkdoiVrJ.d.ts} +45 -10
  52. package/dist/eslint-rules/index.cjs +3 -0
  53. package/dist/eslint-rules/rules/03-architecture.cjs +74 -0
  54. package/dist/eslint-rules/rules/06-security-rbac.cjs +74 -0
  55. package/dist/{event-WTAQuGcq.d.ts → event-BfCox3N2.d.ts} +36 -10
  56. package/dist/{file-reference-BavO2eQj.d.ts → file-reference-DU1hcawx.d.ts} +29 -13
  57. package/dist/hooks.d.ts +22 -9
  58. package/dist/hooks.js +34 -25
  59. package/dist/icons/index.d.ts +1 -0
  60. package/dist/icons/index.js +1 -0
  61. package/dist/index.d.ts +66 -177
  62. package/dist/index.js +316 -340
  63. package/dist/pagination-BW1mqywp.d.ts +201 -0
  64. package/dist/providers.d.ts +6 -5
  65. package/dist/providers.js +5 -3
  66. package/dist/rbac/index.d.ts +123 -138
  67. package/dist/rbac/index.js +10 -8
  68. package/dist/theming/runtime.d.ts +19 -2
  69. package/dist/theming/runtime.js +1 -1
  70. package/dist/{timezone-K-ptz3HO.d.ts → timezone-BTWWXKVY.d.ts} +1 -1
  71. package/dist/types.d.ts +17 -10
  72. package/dist/types.js +1 -0
  73. package/dist/{usePublicPageContext-vxBlEHO9.d.ts → usePublicPageContext-B91dGYW1.d.ts} +433 -356
  74. package/dist/{usePublicRouteParams-G3Ks53mk.d.ts → usePublicRouteParams-BgV6VhMi.d.ts} +73 -4
  75. package/dist/utils.d.ts +163 -145
  76. package/dist/utils.js +42 -25
  77. package/docs/api/modules.md +782 -643
  78. package/docs/api-reference/rpc-functions.md +12 -3
  79. package/docs/core-concepts/rbac-system.md +8 -0
  80. package/docs/getting-started/cursor-rules.md +17 -20
  81. package/docs/getting-started/dependencies.md +1 -1
  82. package/docs/getting-started/setup.md +235 -0
  83. package/docs/implementation-guides/authentication.md +27 -0
  84. package/docs/implementation-guides/data-tables.md +176 -3
  85. package/docs/migration/ApiResult-migration.md +25 -0
  86. package/docs/rbac/api-reference.md +33 -31
  87. package/docs/standards/0-standards-overview.md +50 -15
  88. package/docs/standards/1-pace-core-compliance-standards.md +62 -57
  89. package/docs/standards/2-project-structure-standards.md +33 -16
  90. package/docs/standards/3-architecture-standards.md +41 -1
  91. package/docs/standards/4-code-quality-standards.md +26 -6
  92. package/docs/standards/5-styling-standards.md +35 -1
  93. package/docs/standards/6-security-rbac-standards.md +66 -0
  94. package/docs/standards/7-api-tech-stack-standards.md +25 -14
  95. package/docs/standards/8-testing-documentation-standards.md +31 -0
  96. package/docs/standards/9-operations-standards.md +19 -0
  97. package/docs/standards/README.md +20 -201
  98. package/docs/testing/test-setup-for-consumers.md +2 -0
  99. package/docs/troubleshooting/common-issues.md +17 -1
  100. package/docs/troubleshooting/organisation-context-setup.md +8 -0
  101. package/docs/troubleshooting/print-event-name-css-variable-analysis.md +217 -0
  102. package/eslint-config-pace-core.cjs +20 -0
  103. package/package.json +14 -20
  104. package/scripts/{build-docs-incremental.js → build-docs.js} +3 -2
  105. package/scripts/setup.cjs +536 -0
  106. package/scripts/validate.cjs +480 -0
  107. package/src/__tests__/helpers/{__tests__/component-test-utils.test.tsx → component-test-utils.test.tsx} +3 -3
  108. package/src/__tests__/helpers/{__tests__/optimized-test-setup.test.ts → optimized-test-setup.test.ts} +2 -2
  109. package/src/__tests__/helpers/{__tests__/supabaseMock.test.ts → supabaseMock.test.ts} +2 -2
  110. package/src/__tests__/helpers/{__tests__/test-providers.test.tsx → test-providers.test.tsx} +1 -1
  111. package/src/__tests__/helpers/test-providers.tsx +37 -39
  112. package/src/__tests__/helpers/{__tests__/test-utils.test.tsx → test-utils.test.tsx} +4 -3
  113. package/src/__tests__/helpers/{__tests__/timer-utils.test.ts → timer-utils.test.ts} +2 -2
  114. package/src/assets/app-icons/index.test.ts +304 -0
  115. package/src/components/AddressField/AddressField.test.tsx +1 -1
  116. package/src/components/AddressField/AddressField.tsx +238 -212
  117. package/src/components/Button/Button.tsx +1 -1
  118. package/src/components/Card/Card.test.tsx +172 -17
  119. package/src/components/Card/Card.tsx +19 -10
  120. package/src/components/ContextSelector/ContextSelector.internals.tsx +204 -0
  121. package/src/components/ContextSelector/{__tests__/ContextSelector.test.tsx → ContextSelector.test.tsx} +6 -6
  122. package/src/components/ContextSelector/ContextSelector.tsx +66 -280
  123. package/src/components/ContextSelector/ContextSelector.types.ts +35 -0
  124. package/src/components/ContextSelector/useContextSelectorState.tsx +195 -0
  125. package/src/components/DataTable/AUDIT_REPORT.md +59 -44
  126. package/src/components/DataTable/{__tests__/DataTable.comprehensive.test.tsx → DataTable.comprehensive.test.tsx} +6 -6
  127. package/src/components/DataTable/{__tests__/DataTable.default-state.test.tsx → DataTable.default-state.test.tsx} +5 -5
  128. package/src/components/DataTable/{__tests__/DataTable.export.test.tsx → DataTable.export.test.tsx} +10 -10
  129. package/src/components/DataTable/{__tests__/DataTable.grouping-aggregation.test.tsx → DataTable.grouping-aggregation.test.tsx} +6 -6
  130. package/src/components/DataTable/{__tests__/DataTable.hooks.test.tsx → DataTable.hooks.test.tsx} +6 -6
  131. package/src/components/DataTable/{__tests__/DataTable.select-label-display.test.tsx → DataTable.select-label-display.test.tsx} +6 -6
  132. package/src/components/DataTable/DataTable.test.tsx +787 -416
  133. package/src/components/DataTable/DataTable.tsx +12 -12
  134. package/src/components/DataTable/DataTableCore.integration.test.tsx +458 -0
  135. package/src/components/DataTable/{__tests__/DataTableCore.test-setup.ts → DataTableCore.test-setup.ts} +10 -9
  136. package/src/components/DataTable/{__tests__/DataTableCore.test.tsx → DataTableCore.test.tsx} +8 -8
  137. package/src/components/DataTable/{__tests__/README.md → README.md} +17 -7
  138. package/src/components/DataTable/TESTING.md +101 -0
  139. package/src/components/DataTable/{__tests__/a11y.basic.test.tsx → a11y.basic.test.tsx} +34 -34
  140. package/src/components/DataTable/components/DataTableCore.tsx +104 -864
  141. package/src/components/DataTable/components/{__tests__/GroupingDropdown.test.tsx → GroupingDropdown.test.tsx} +17 -8
  142. package/src/components/DataTable/components/GroupingDropdown.tsx +2 -2
  143. package/src/components/DataTable/components/ImportModal.tsx +61 -559
  144. package/src/components/DataTable/components/ImportModalFileSection.tsx +148 -0
  145. package/src/components/DataTable/context/{__tests__/DataTableContext.test.tsx → DataTableContext.test.tsx} +2 -2
  146. package/src/components/DataTable/context/DataTableContext.tsx +7 -6
  147. package/src/components/DataTable/core/{__tests__/ColumnFactory.test.ts → ColumnFactory.test.ts} +2 -2
  148. package/src/components/DataTable/hooks/{__tests__/useColumnOrderPersistence.test.ts → useColumnOrderPersistence.test.ts} +2 -2
  149. package/src/components/DataTable/hooks/{__tests__/useColumnVisibilityPersistence.test.ts → useColumnVisibilityPersistence.test.ts} +2 -2
  150. package/src/components/DataTable/hooks/{__tests__/useDataTableConfiguration.test.ts → useDataTableConfiguration.test.ts} +3 -3
  151. package/src/components/DataTable/hooks/useDataTableConfiguration.ts +14 -2
  152. package/src/components/DataTable/hooks/{__tests__/useDataTableDataPipeline.test.ts → useDataTableDataPipeline.test.ts} +6 -6
  153. package/src/components/DataTable/hooks/useDataTableDeletionBatching.test.ts +127 -0
  154. package/src/components/DataTable/hooks/useDataTableDeletionBatching.ts +106 -0
  155. package/src/components/DataTable/hooks/useDataTableEffectiveActions.test.ts +461 -0
  156. package/src/components/DataTable/hooks/useDataTableEffectiveActions.ts +238 -0
  157. package/src/components/DataTable/hooks/useDataTableLayoutHandlers.test.ts +296 -0
  158. package/src/components/DataTable/hooks/useDataTableLayoutHandlers.ts +175 -0
  159. package/src/components/DataTable/hooks/useDataTablePaginationSync.test.ts +203 -0
  160. package/src/components/DataTable/hooks/useDataTablePaginationSync.ts +109 -0
  161. package/src/components/DataTable/hooks/{__tests__/useDataTablePermissions.test.ts → useDataTablePermissions.test.ts} +11 -11
  162. package/src/components/DataTable/hooks/useDataTablePermissions.ts +79 -247
  163. package/src/components/DataTable/hooks/useDataTablePipeline.test.tsx +219 -0
  164. package/src/components/DataTable/hooks/useDataTablePipeline.tsx +239 -0
  165. package/src/components/DataTable/hooks/useDataTableRenderGuard.test.tsx +316 -0
  166. package/src/components/DataTable/hooks/useDataTableRenderGuard.tsx +195 -0
  167. package/src/components/DataTable/hooks/useDataTableScope.test.ts +110 -0
  168. package/src/components/DataTable/hooks/useDataTableScope.ts +123 -0
  169. package/src/components/DataTable/hooks/{__tests__/useDataTableState.test.ts → useDataTableState.test.ts} +47 -5
  170. package/src/components/DataTable/hooks/useDataTableState.ts +145 -94
  171. package/src/components/DataTable/hooks/useDataTableStateAndPersistence.test.ts +277 -0
  172. package/src/components/DataTable/hooks/useDataTableStateAndPersistence.ts +222 -0
  173. package/src/components/DataTable/hooks/useDataTableSuperAdmin.test.ts +93 -0
  174. package/src/components/DataTable/hooks/useDataTableSuperAdmin.ts +86 -0
  175. package/src/components/DataTable/hooks/useDataTableTableInstance.test.ts +185 -0
  176. package/src/components/DataTable/hooks/useDataTableTableInstance.ts +178 -0
  177. package/src/components/DataTable/hooks/{__tests__/useEffectiveColumnOrder.test.ts → useEffectiveColumnOrder.test.ts} +2 -2
  178. package/src/components/DataTable/hooks/{__tests__/useHierarchicalState.test.ts → useHierarchicalState.test.ts} +2 -2
  179. package/src/components/DataTable/{components/hooks → hooks}/useImportModalFocus.test.ts +3 -3
  180. package/src/components/DataTable/{components/hooks → hooks}/useImportModalFocus.ts +2 -2
  181. package/src/components/DataTable/hooks/useImportModalState.test.ts +390 -0
  182. package/src/components/DataTable/hooks/useImportModalState.ts +345 -0
  183. package/src/components/DataTable/hooks/{__tests__/useKeyboardNavigation.test.ts → useKeyboardNavigation.test.ts} +3 -3
  184. package/src/components/DataTable/hooks/useKeyboardNavigation.ts +309 -269
  185. package/src/components/DataTable/{components/hooks → hooks}/usePermissionTracking.test.ts +3 -3
  186. package/src/components/DataTable/{components/hooks → hooks}/usePermissionTracking.ts +3 -3
  187. package/src/components/DataTable/hooks/{__tests__/useServerSideDataEffect.test.ts → useServerSideDataEffect.test.ts} +2 -2
  188. package/src/components/DataTable/hooks/useServerSideDataEffect.ts +14 -3
  189. package/src/components/DataTable/hooks/{__tests__/useTableColumns.test.ts → useTableColumns.test.ts} +2 -2
  190. package/src/components/DataTable/hooks/{__tests__/useTableHandlers.test.ts → useTableHandlers.test.ts} +25 -4
  191. package/src/components/DataTable/hooks/useTableHandlers.ts +5 -2
  192. package/src/components/DataTable/index.ts +18 -17
  193. package/src/components/DataTable/{__tests__/keyboard.test.tsx → keyboard.test.tsx} +41 -63
  194. package/src/components/DataTable/{__tests__/mocks → mocks}/MockRBACProvider.tsx +1 -1
  195. package/src/components/DataTable/{__tests__/pagination.modes.test.tsx → pagination.modes.test.tsx} +6 -6
  196. package/src/components/DataTable/{__tests__/ssr.strict-mode.test.tsx → ssr.strict-mode.test.tsx} +2 -2
  197. package/src/components/DataTable/{__tests__/styles.test.ts → styles.test.ts} +1 -4
  198. package/src/components/DataTable/styles.ts +0 -1
  199. package/src/components/DataTable/test-utils/MockDataTableComponents.tsx +55 -0
  200. package/src/components/DataTable/{__tests__/test-utils → test-utils}/dataFactories.ts +2 -2
  201. package/src/components/DataTable/test-utils/featureConfig.ts +10 -0
  202. package/src/components/DataTable/{__tests__/test-utils/sharedTestUtils.tsx → test-utils/sharedTestUtils.ts} +97 -66
  203. package/src/components/DataTable/{__tests__/test-utils.ts → test-utils.ts} +1 -1
  204. package/src/components/DataTable/types/actions.ts +71 -0
  205. package/src/components/DataTable/types/base.ts +39 -0
  206. package/src/components/DataTable/types/columns.ts +125 -0
  207. package/src/components/DataTable/types/export.ts +32 -0
  208. package/src/components/DataTable/types/features.ts +81 -0
  209. package/src/components/DataTable/types/hierarchical.ts +44 -0
  210. package/src/components/DataTable/types/index.ts +43 -0
  211. package/src/components/DataTable/types/pagination.ts +85 -0
  212. package/src/components/DataTable/types/performance.ts +47 -0
  213. package/src/components/DataTable/types/props.ts +62 -0
  214. package/src/components/DataTable/types/rbac.ts +45 -0
  215. package/src/components/DataTable/{components/__tests__ → ui/layout}/DataTableCore.test.tsx +430 -28
  216. package/src/components/DataTable/ui/layout/DataTableCore.tsx +345 -0
  217. package/src/components/DataTable/{components/__tests__ → ui/layout}/DataTableErrorBoundary.test.tsx +4 -4
  218. package/src/components/DataTable/{components → ui/layout}/DataTableErrorBoundary.tsx +7 -7
  219. package/src/components/DataTable/ui/layout/DataTableLayout.test.tsx +1352 -0
  220. package/src/components/DataTable/ui/layout/DataTableLayout.tsx +661 -0
  221. package/src/components/DataTable/ui/modals/BulkDeleteConfirmDialog.test.tsx +91 -0
  222. package/src/components/DataTable/ui/modals/BulkDeleteConfirmDialog.tsx +43 -0
  223. package/src/components/DataTable/ui/modals/DataTableModals.test.tsx +749 -0
  224. package/src/components/DataTable/{components → ui/modals}/DataTableModals.tsx +36 -28
  225. package/src/components/DataTable/ui/modals/ImportModal.test.tsx +1834 -0
  226. package/src/components/DataTable/ui/modals/ImportModal.tsx +197 -0
  227. package/src/components/DataTable/ui/modals/ImportModalFailedRowsSection.tsx +60 -0
  228. package/src/components/DataTable/ui/modals/ImportModalFileSection.tsx +148 -0
  229. package/src/components/DataTable/ui/modals/ImportModalPreviewSection.tsx +60 -0
  230. package/src/components/DataTable/ui/modals/ImportModalSummarySection.tsx +59 -0
  231. package/src/components/DataTable/ui/modals/importModalPersistence.ts +73 -0
  232. package/src/components/DataTable/{components/__tests__ → ui/shared}/AccessDeniedPage.test.tsx +2 -2
  233. package/src/components/DataTable/{components → ui/shared}/AccessDeniedPage.tsx +2 -2
  234. package/src/components/DataTable/{components/__tests__ → ui/shared}/ActionButtons.test.tsx +6 -4
  235. package/src/components/DataTable/{components → ui/shared}/ActionButtons.tsx +4 -4
  236. package/src/components/DataTable/{components/__tests__ → ui/shared}/ColumnFilter.test.tsx +29 -16
  237. package/src/components/DataTable/{components → ui/shared}/ColumnFilter.tsx +4 -4
  238. package/src/components/DataTable/{components/__tests__ → ui/shared}/PaginationControls.test.tsx +38 -16
  239. package/src/components/DataTable/{components → ui/shared}/PaginationControls.tsx +21 -15
  240. package/src/components/DataTable/{components/__tests__ → ui/shared}/SortIndicator.test.tsx +2 -2
  241. package/src/components/DataTable/{components → ui/shared}/SortIndicator.tsx +1 -1
  242. package/src/components/DataTable/{components/__tests__ → ui/table}/EditFields.test.tsx +3 -3
  243. package/src/components/DataTable/{components → ui/table}/EditFields.tsx +138 -69
  244. package/src/components/DataTable/{components/__tests__ → ui/table}/EditableRow.test.tsx +36 -27
  245. package/src/components/DataTable/{components → ui/table}/EditableRow.tsx +86 -104
  246. package/src/components/DataTable/{components/__tests__ → ui/table}/EmptyState.test.tsx +2 -62
  247. package/src/components/DataTable/{components → ui/table}/EmptyState.tsx +7 -15
  248. package/src/components/DataTable/{components/__tests__ → ui/table}/FilterRow.test.tsx +5 -4
  249. package/src/components/DataTable/{components → ui/table}/FilterRow.tsx +3 -3
  250. package/src/components/DataTable/{components/__tests__ → ui/table}/LoadingState.test.tsx +6 -10
  251. package/src/components/DataTable/{components → ui/table}/LoadingState.tsx +4 -4
  252. package/src/components/DataTable/{components/__tests__ → ui/table}/RowComponent.test.tsx +412 -17
  253. package/src/components/DataTable/{components → ui/table}/RowComponent.tsx +183 -177
  254. package/src/components/DataTable/{components/__tests__ → ui/table}/UnifiedTableBody.test.tsx +425 -16
  255. package/src/components/DataTable/ui/table/UnifiedTableBody.tsx +440 -0
  256. package/src/components/DataTable/{components/__tests__ → ui/table}/cellValueUtils.test.ts +2 -2
  257. package/src/components/DataTable/{components → ui/table}/cellValueUtils.ts +1 -1
  258. package/src/components/DataTable/{components/__tests__ → ui/toolbar}/BulkOperationsDropdown.test.tsx +12 -5
  259. package/src/components/DataTable/{components → ui/toolbar}/BulkOperationsDropdown.tsx +3 -3
  260. package/src/components/DataTable/{components/__tests__ → ui/toolbar}/ColumnVisibilityDropdown.test.tsx +7 -4
  261. package/src/components/DataTable/{components → ui/toolbar}/ColumnVisibilityDropdown.tsx +7 -7
  262. package/src/components/DataTable/{components/__tests__ → ui/toolbar}/DataTableToolbar.test.tsx +4 -4
  263. package/src/components/DataTable/{components → ui/toolbar}/DataTableToolbar.tsx +4 -4
  264. package/src/components/DataTable/ui/toolbar/GroupingDropdown.test.tsx +621 -0
  265. package/src/components/DataTable/ui/toolbar/GroupingDropdown.tsx +107 -0
  266. package/src/components/DataTable/utils/{__tests__/a11yUtils.test.ts → a11yUtils.test.ts} +2 -2
  267. package/src/components/DataTable/utils/{__tests__/aggregationUtils.test.ts → aggregationUtils.test.ts} +3 -3
  268. package/src/components/DataTable/utils/{__tests__/columnUtils.test.ts → columnUtils.test.ts} +2 -2
  269. package/src/components/DataTable/utils/csvParse.test.ts +74 -0
  270. package/src/components/DataTable/utils/csvParse.ts +65 -0
  271. package/src/components/DataTable/utils/{__tests__/errorHandling.test.ts → errorHandling.test.ts} +2 -2
  272. package/src/components/DataTable/utils/{__tests__/exportUtils.test.ts → exportUtils.test.ts} +3 -3
  273. package/src/components/DataTable/utils/{__tests__/flexibleImport.test.ts → flexibleImport.test.ts} +2 -2
  274. package/src/components/DataTable/utils/flexibleImport.ts +3 -186
  275. package/src/components/DataTable/utils/{__tests__/hierarchicalSorting.test.ts → hierarchicalSorting.test.ts} +3 -3
  276. package/src/components/DataTable/utils/{__tests__/hierarchicalUtils.test.ts → hierarchicalUtils.test.ts} +3 -3
  277. package/src/components/DataTable/utils/importDateParser.test.ts +162 -0
  278. package/src/components/DataTable/utils/importDateParser.ts +114 -0
  279. package/src/components/DataTable/utils/importValueParser.test.ts +138 -0
  280. package/src/components/DataTable/utils/importValueParser.ts +91 -0
  281. package/src/components/DataTable/utils/{__tests__/paginationUtils.test.ts → paginationUtils.test.ts} +2 -2
  282. package/src/components/DataTable/utils/paginationUtils.ts +6 -3
  283. package/src/components/DataTable/utils/{__tests__/performanceUtils.test.ts → performanceUtils.test.ts} +3 -3
  284. package/src/components/DataTable/utils/{__tests__/rowUtils.test.ts → rowUtils.test.ts} +3 -3
  285. package/src/components/DataTable/utils/{__tests__/selectFieldUtils.test.ts → selectFieldUtils.test.ts} +66 -3
  286. package/src/components/DataTable/utils/selectFieldUtils.ts +97 -60
  287. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +1 -1
  288. package/src/components/DateTimeField/DateTimeField.test.tsx +1 -1
  289. package/src/components/Dialog/Dialog.test-utils.ts +49 -0
  290. package/src/components/Dialog/Dialog.test.tsx +896 -89
  291. package/src/components/Dialog/Dialog.tsx +174 -882
  292. package/src/components/Dialog/dialogLock.test.ts +238 -0
  293. package/src/components/Dialog/dialogLock.ts +98 -0
  294. package/src/components/Dialog/index.ts +2 -0
  295. package/src/components/Dialog/useDialogDimensions.test.ts +163 -0
  296. package/src/components/Dialog/useDialogDimensions.ts +140 -0
  297. package/src/components/Dialog/useDialogLifecycle.test.ts +358 -0
  298. package/src/components/Dialog/useDialogLifecycle.ts +135 -0
  299. package/src/components/Dialog/useDialogPersistence.test.ts +381 -0
  300. package/src/components/Dialog/useDialogPersistence.ts +357 -0
  301. package/src/components/FileDisplay/FileDisplay.test.tsx +40 -40
  302. package/src/components/FileDisplay/FileDisplay.tsx +24 -656
  303. package/src/components/FileDisplay/FileDisplayContent.test.tsx +395 -0
  304. package/src/components/FileDisplay/FileDisplayContent.tsx +242 -0
  305. package/src/components/FileDisplay/FileDisplayDeleteConfirmDialog.test.tsx +74 -0
  306. package/src/components/FileDisplay/FileDisplayDeleteConfirmDialog.tsx +38 -0
  307. package/src/components/FileDisplay/FileDisplayEmptyView.test.tsx +33 -0
  308. package/src/components/FileDisplay/FileDisplayEmptyView.tsx +33 -0
  309. package/src/components/FileDisplay/FileDisplayErrorView.test.tsx +71 -0
  310. package/src/components/FileDisplay/FileDisplayErrorView.tsx +50 -0
  311. package/src/components/FileDisplay/FileDisplayLoadingFallbackView.test.tsx +22 -0
  312. package/src/components/FileDisplay/FileDisplayLoadingFallbackView.tsx +22 -0
  313. package/src/components/FileDisplay/FileDisplayLoadingView.test.tsx +21 -0
  314. package/src/components/FileDisplay/FileDisplayLoadingView.tsx +23 -0
  315. package/src/components/FileDisplay/FileDisplayMultipleFilesView.test.tsx +101 -0
  316. package/src/components/FileDisplay/FileDisplayMultipleFilesView.tsx +109 -0
  317. package/src/components/FileDisplay/FileDisplaySingleDocumentLinkView.test.tsx +58 -0
  318. package/src/components/FileDisplay/FileDisplaySingleDocumentLinkView.tsx +48 -0
  319. package/src/components/FileDisplay/FileDisplaySingleFileWithActionsView.test.tsx +111 -0
  320. package/src/components/FileDisplay/FileDisplaySingleFileWithActionsView.tsx +270 -0
  321. package/src/components/FileDisplay/FileDisplaySingleImageView.test.tsx +78 -0
  322. package/src/components/FileDisplay/FileDisplaySingleImageView.tsx +67 -0
  323. package/src/components/FileDisplay/fallbackUtils.test.ts +50 -0
  324. package/src/components/FileDisplay/fallbackUtils.ts +44 -0
  325. package/src/components/FileDisplay/fetchFileDisplayData.ts +24 -0
  326. package/src/components/FileDisplay/fetchFileDisplayData.unit.test.ts +183 -0
  327. package/src/components/FileDisplay/fileDisplayUtils.test.ts +58 -0
  328. package/src/components/FileDisplay/fileDisplayUtils.ts +24 -0
  329. package/src/{hooks/__tests__ → components/FileDisplay}/useFileDisplay.test.ts +40 -42
  330. package/src/components/FileDisplay/useFileDisplay.ts +515 -0
  331. package/src/{hooks/__tests__ → components/FileDisplay}/useFileDisplay.unit.test.ts +406 -77
  332. package/src/components/FileDisplay/useFileDisplayData.ts +126 -0
  333. package/src/{hooks/public → components/FileDisplay}/usePublicFileDisplay.test.ts +94 -88
  334. package/src/components/FileDisplay/usePublicFileDisplay.ts +579 -0
  335. package/src/components/FileUpload/FileUpload.test.tsx +16 -10
  336. package/src/components/FileUpload/FileUpload.tsx +107 -525
  337. package/src/components/FileUpload/FileUploadDropZone.tsx +112 -0
  338. package/src/components/FileUpload/FileUploadProgressItem.tsx +86 -0
  339. package/src/components/FileUpload/FileUploadProgressList.tsx +40 -0
  340. package/src/components/FileUpload/useFileUploadManager.test.ts +308 -0
  341. package/src/components/FileUpload/useFileUploadManager.ts +454 -0
  342. package/src/components/FileUpload/useResolvedAppId.test.ts +102 -0
  343. package/src/components/FileUpload/useResolvedAppId.ts +77 -0
  344. package/src/components/Footer/Footer.test.tsx +6 -292
  345. package/src/components/Footer/Footer.tsx +8 -125
  346. package/src/components/Form/Form.test.tsx +44 -27
  347. package/src/components/Form/Form.tsx +64 -287
  348. package/src/components/Form/useFormPersistence.ts +257 -0
  349. package/src/components/Header/Header.test.tsx +17 -18
  350. package/src/components/Header/Header.tsx +10 -1
  351. package/src/components/Input/Input.tsx +1 -1
  352. package/src/components/Label/Label.test.tsx +1 -1
  353. package/src/components/LoadingSpinner/LoadingSpinner.test.tsx +1 -1
  354. package/src/components/NavigationMenu/HierarchicalNavItem.tsx +104 -0
  355. package/src/components/NavigationMenu/NavigationMenu.test.tsx +1029 -26
  356. package/src/components/NavigationMenu/NavigationMenu.tsx +61 -361
  357. package/src/components/NavigationMenu/index.ts +6 -1
  358. package/src/components/NavigationMenu/navigationPermissionHelper.ts +188 -0
  359. package/src/components/NavigationMenu/{__tests__/useNavigationFiltering.test.ts → useNavigationFiltering.test.ts} +68 -53
  360. package/src/components/NavigationMenu/useNavigationFiltering.ts +197 -296
  361. package/src/components/NavigationMenu/useNavigationScope.ts +125 -0
  362. package/src/components/PaceAppLayout/PaceAppLayout.edge-cases.test.tsx +77 -62
  363. package/src/components/PaceAppLayout/PaceAppLayout.integration.test.tsx +3 -3
  364. package/src/components/PaceAppLayout/PaceAppLayout.security.test.tsx +16 -19
  365. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +529 -5
  366. package/src/components/PaceAppLayout/PaceAppLayout.tsx +280 -756
  367. package/src/components/PaceAppLayout/useFilteredNavItems.ts +304 -0
  368. package/src/components/PaceAppLayout/usePaceAppLayoutConfig.ts +142 -0
  369. package/src/components/PaceAppLayout/usePaceAppLayoutGate.tsx +150 -0
  370. package/src/components/PaceAppLayout/usePaceAppLayoutPermissions.ts +162 -0
  371. package/src/components/PaceAppLayout/usePaceAppLayoutScope.ts +79 -0
  372. package/src/components/PaceAppLayout/useRoleBasedRouteAccess.ts +157 -0
  373. package/src/components/PaceAppLayout/useSuperAdminFallback.ts +58 -0
  374. package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +31 -25
  375. package/src/components/PaceLoginPage/PaceLoginPage.tsx +31 -122
  376. package/src/components/PaceLoginPage/useLoginAppAccess.ts +153 -0
  377. package/src/components/Progress/Progress.tsx +1 -2
  378. package/src/components/ProtectedRoute/ProtectedRoute.tsx +29 -235
  379. package/src/components/ProtectedRoute/useProtectedRouteState.ts +128 -0
  380. package/src/components/ProtectedRoute/useVisibilityRedirectGrace.ts +89 -0
  381. package/src/components/PublicLayout/PublicLayout.test.tsx +217 -36
  382. package/src/components/PublicLayout/PublicPageLayout.tsx +132 -73
  383. package/src/components/PublicLayout/PublicPageProvider.tsx +5 -1
  384. package/src/components/Select/Select.test.tsx +1 -1
  385. package/src/components/Select/Select.tsx +28 -18
  386. package/src/components/Select/{__tests__/context.test.tsx → context.test.tsx} +3 -3
  387. package/src/components/Select/{utils/__tests__/text.test.tsx → text.test.tsx} +2 -2
  388. package/src/components/Select/{utils/text.ts → text.ts} +1 -1
  389. package/src/components/Select/{hooks/__tests__/useSelectEvents.test.ts → useSelectEvents.test.ts} +5 -5
  390. package/src/components/Select/{hooks/useSelectEvents.ts → useSelectEvents.ts} +2 -2
  391. package/src/components/Select/{hooks/__tests__/useSelectSearch.test.tsx → useSelectSearch.test.tsx} +7 -7
  392. package/src/components/Select/{hooks/useSelectSearch.ts → useSelectSearch.ts} +2 -2
  393. package/src/components/Select/{hooks/__tests__/useSelectState.test.ts → useSelectState.test.ts} +16 -2
  394. package/src/components/Select/{hooks/useSelectState.ts → useSelectState.ts} +3 -3
  395. package/src/components/Table/Table.test.tsx +348 -0
  396. package/src/components/Tabs/Tabs.test.tsx +270 -0
  397. package/src/components/Tabs/Tabs.tsx +1 -1
  398. package/src/components/Toast/Toast.test.tsx +420 -0
  399. package/src/components/{__tests__/index.test.ts → index.test.ts} +2 -2
  400. package/src/constants/{__tests__/performance.test.ts → performance.test.ts} +2 -2
  401. package/src/hooks/{__tests__/ServiceHooks.test.tsx → ServiceHooks.test.tsx} +8 -8
  402. package/src/hooks/{__tests__/hooks.integration.test.tsx → hooks.integration.test.tsx} +11 -11
  403. package/src/hooks/index.ts +7 -4
  404. package/src/hooks/{__tests__/index.unit.test.ts → index.unit.test.ts} +2 -2
  405. package/src/hooks/public/usePublicEvent.test.ts +1 -1
  406. package/src/hooks/public/usePublicEventLogo.test.ts +1 -1
  407. package/src/hooks/public/usePublicRouteParams.test.ts +1 -1
  408. package/src/hooks/services/useAuth.ts +9 -7
  409. package/src/hooks/useAddressAutocomplete.test.ts +22 -22
  410. package/src/hooks/useAddressAutocomplete.ts +90 -75
  411. package/src/hooks/{__tests__/useAppConfig.unit.test.ts → useAppConfig.unit.test.ts} +328 -22
  412. package/src/hooks/{__tests__/useComponentPerformance.unit.test.tsx → useComponentPerformance.unit.test.tsx} +27 -41
  413. package/src/hooks/useDataTablePerformance.ts +100 -120
  414. package/src/hooks/{__tests__/useDataTablePerformance.unit.test.ts → useDataTablePerformance.unit.test.ts} +5 -5
  415. package/src/hooks/{__tests__/useDataTableState.test.ts → useDataTableState.test.ts} +2 -2
  416. package/src/hooks/{__tests__/useDebounce.unit.test.ts → useDebounce.unit.test.ts} +2 -2
  417. package/src/hooks/useEventTheme.test.ts +4 -1
  418. package/src/hooks/useEventTheme.ts +49 -21
  419. package/src/hooks/useEvents.ts +41 -1
  420. package/src/hooks/{__tests__/useEvents.unit.test.ts → useEvents.unit.test.ts} +5 -5
  421. package/src/hooks/useFileReference.test.ts +44 -41
  422. package/src/hooks/useFileReference.ts +182 -173
  423. package/src/hooks/useFileUrl.ts +1 -1
  424. package/src/hooks/{__tests__/useFileUrl.unit.test.ts → useFileUrl.unit.test.ts} +26 -36
  425. package/src/hooks/{__tests__/useFileUrlCache.test.ts → useFileUrlCache.test.ts} +8 -8
  426. package/src/hooks/useFileUrlCache.ts +1 -1
  427. package/src/hooks/{__tests__/useFocusManagement.unit.test.ts → useFocusManagement.unit.test.ts} +2 -2
  428. package/src/hooks/{__tests__/useFocusTrap.unit.test.tsx → useFocusTrap.unit.test.tsx} +2 -2
  429. package/src/hooks/{__tests__/useFormDialog.test.ts → useFormDialog.test.ts} +2 -2
  430. package/src/hooks/useInactivityTracker.ts +138 -131
  431. package/src/hooks/{__tests__/useInactivityTracker.unit.test.ts → useInactivityTracker.unit.test.ts} +3 -3
  432. package/src/hooks/{__tests__/useIsMobile.unit.test.ts → useIsMobile.unit.test.ts} +2 -2
  433. package/src/hooks/useIsPrint.ts +62 -0
  434. package/src/hooks/useIsPrint.unit.test.ts +545 -0
  435. package/src/hooks/{__tests__/useKeyboardShortcuts.unit.test.ts → useKeyboardShortcuts.unit.test.ts} +2 -2
  436. package/src/hooks/{__tests__/useOrganisationPermissions.unit.test.tsx → useOrganisationPermissions.unit.test.tsx} +4 -4
  437. package/src/hooks/useOrganisationSecurity.test.ts +3 -3
  438. package/src/hooks/useOrganisationSecurity.ts +190 -201
  439. package/src/hooks/{__tests__/useOrganisationSecurity.unit.test.tsx → useOrganisationSecurity.unit.test.tsx} +61 -63
  440. package/src/hooks/{__tests__/useOrganisations.unit.test.ts → useOrganisations.unit.test.ts} +5 -5
  441. package/src/hooks/{__tests__/usePerformanceMonitor.unit.test.ts → usePerformanceMonitor.unit.test.ts} +13 -14
  442. package/src/hooks/{__tests__/usePermissionCache.test.ts → usePermissionCache.test.ts} +26 -27
  443. package/src/hooks/usePermissionCache.ts +276 -271
  444. package/src/hooks/{__tests__/usePreventTabReload.test.ts → usePreventTabReload.test.ts} +2 -2
  445. package/src/hooks/{__tests__/usePublicEvent.simple.test.ts → usePublicEvent.simple.test.ts} +4 -4
  446. package/src/hooks/{__tests__/usePublicEvent.test.ts → usePublicEvent.test.ts} +4 -4
  447. package/src/hooks/{__tests__/usePublicEvent.unit.test.ts → usePublicEvent.unit.test.ts} +4 -4
  448. package/src/hooks/{__tests__/usePublicFileDisplay.test.ts → usePublicFileDisplay.test.ts} +12 -12
  449. package/src/hooks/{__tests__/usePublicRouteParams.unit.test.ts → usePublicRouteParams.unit.test.ts} +3 -3
  450. package/src/hooks/{__tests__/useQueryCache.test.ts → useQueryCache.test.ts} +2 -2
  451. package/src/hooks/useQueryCache.ts +0 -2
  452. package/src/hooks/{__tests__/useRBAC.unit.test.ts → useRBAC.unit.test.ts} +55 -38
  453. package/src/hooks/{__tests__/useSessionDraft.test.ts → useSessionDraft.test.ts} +2 -2
  454. package/src/hooks/{__tests__/useSessionRestoration.unit.test.tsx → useSessionRestoration.unit.test.tsx} +10 -19
  455. package/src/hooks/useStorage.ts +21 -16
  456. package/src/hooks/{__tests__/useStorage.unit.test.ts → useStorage.unit.test.ts} +38 -75
  457. package/src/hooks/{__tests__/useToast.test.ts → useToast.test.ts} +2 -2
  458. package/src/hooks/{__tests__/useToast.unit.test.tsx → useToast.unit.test.tsx} +2 -2
  459. package/src/hooks/{__tests__/useZodForm.unit.test.tsx → useZodForm.unit.test.tsx} +2 -2
  460. package/src/icons/{__tests__/index.test.ts → index.test.ts} +2 -2
  461. package/src/icons/index.ts +2 -0
  462. package/src/{__tests__/index.test.ts → index.test.ts} +3 -7
  463. package/src/index.ts +15 -7
  464. package/src/providers/{__tests__/AuthProvider.test.tsx → AuthProvider.test.tsx} +3 -3
  465. package/src/providers/{__tests__/EventProvider.test.tsx → EventProvider.test.tsx} +3 -3
  466. package/src/providers/InactivityProvider.test-helper.tsx +40 -0
  467. package/src/providers/{__tests__/InactivityProvider.test.tsx → InactivityProvider.test.tsx} +14 -21
  468. package/src/providers/{__tests__/ProviderLifecycle.test.tsx → ProviderLifecycle.test.tsx} +4 -4
  469. package/src/providers/{__tests__/UnifiedAuthProvider.test.tsx → UnifiedAuthProvider.test.tsx} +1 -1
  470. package/src/providers/{__tests__/index.test.ts → index.test.ts} +2 -2
  471. package/src/providers/services/{__tests__/AuthServiceProvider.integration.test.tsx → AuthServiceProvider.integration.test.tsx} +4 -4
  472. package/src/providers/services/{__tests__/AuthServiceProvider.test.tsx → AuthServiceProvider.test.tsx} +7 -7
  473. package/src/providers/services/{__tests__/EventServiceProvider.test.tsx → EventServiceProvider.test.tsx} +7 -7
  474. package/src/providers/services/{__tests__/InactivityServiceProvider.test.tsx → InactivityServiceProvider.test.tsx} +5 -5
  475. package/src/providers/services/{__tests__/OrganisationServiceProvider.test.tsx → OrganisationServiceProvider.test.tsx} +6 -6
  476. package/src/providers/services/UnifiedAuthContext.ts +30 -27
  477. package/src/providers/services/{__tests__/UnifiedAuthProvider.advanced.test.tsx → UnifiedAuthProvider.advanced.test.tsx} +8 -9
  478. package/src/providers/services/{__tests__/UnifiedAuthProvider.appId.test.tsx → UnifiedAuthProvider.appId.test.tsx} +25 -25
  479. package/src/providers/services/{__tests__/UnifiedAuthProvider.integration.test.tsx → UnifiedAuthProvider.integration.test.tsx} +14 -11
  480. package/src/providers/services/UnifiedAuthProvider.tsx +115 -360
  481. package/src/providers/services/{__tests__/contexts.test.tsx → contexts.test.tsx} +6 -6
  482. package/src/providers/services/{__tests__/useUnifiedAuth.test.tsx → useUnifiedAuth.test.tsx} +6 -6
  483. package/src/providers/services/useUnifiedAuthContextValue.ts +279 -0
  484. package/src/providers/useInactivity.test-helper.ts +27 -0
  485. package/src/rbac/{__tests__/adapters.comprehensive.test.tsx → adapters.comprehensive.test.tsx} +24 -24
  486. package/src/rbac/adapters.test.tsx +22 -22
  487. package/src/rbac/adapters.tsx +29 -29
  488. package/src/rbac/api.test.ts +973 -42
  489. package/src/rbac/api.ts +228 -253
  490. package/src/rbac/{__tests__/audit-batched.test.ts → audit-batched.test.ts} +6 -6
  491. package/src/rbac/audit.ts +4 -1
  492. package/src/rbac/{__tests__/auth-rbac-security.integration.test.tsx → auth-rbac-security.integration.test.tsx} +1 -1
  493. package/src/rbac/{__tests__/auth-rbac.e2e.test.tsx → auth-rbac.e2e.test.tsx} +27 -34
  494. package/src/rbac/cache-invalidation.test.ts +715 -0
  495. package/src/rbac/components/{__tests__/AccessDenied.test.tsx → AccessDenied.test.tsx} +3 -3
  496. package/src/rbac/components/{__tests__/NavigationGuard.test.tsx → NavigationGuard.test.tsx} +13 -11
  497. package/src/{__tests__/rbac/PagePermissionGuard.test.tsx → rbac/components/PagePermissionGuard.guard.test.tsx} +33 -19
  498. package/src/rbac/components/{__tests__/PagePermissionGuard.performance.test.tsx → PagePermissionGuard.performance.test.tsx} +30 -9
  499. package/src/rbac/components/{__tests__/PagePermissionGuard.race-condition.test.tsx → PagePermissionGuard.race-condition.test.tsx} +7 -7
  500. package/src/rbac/components/{__tests__/PagePermissionGuard.test.tsx → PagePermissionGuard.test.tsx} +10 -10
  501. package/src/rbac/components/PagePermissionGuard.tsx +177 -372
  502. package/src/rbac/components/{__tests__/PagePermissionGuard.verification.test.tsx → PagePermissionGuard.verification.test.tsx} +7 -7
  503. package/src/rbac/config.ts +58 -18
  504. package/src/rbac/{__tests__/engine.comprehensive.test.ts → engine.comprehensive.test.ts} +3 -3
  505. package/src/rbac/engine.test.ts +494 -0
  506. package/src/rbac/errors.ts +89 -55
  507. package/src/rbac/hooks/permissions/runPermissionCheck.ts +77 -0
  508. package/src/rbac/hooks/permissions/{__tests__/useAccessLevel.test.ts → useAccessLevel.test.ts} +40 -40
  509. package/src/rbac/hooks/permissions/useAccessLevel.ts +16 -6
  510. package/src/rbac/hooks/permissions/{__tests__/useCan.test.ts → useCan.test.ts} +41 -41
  511. package/src/rbac/hooks/permissions/useCan.ts +170 -252
  512. package/src/rbac/hooks/permissions/{__tests__/useMultiplePermissions.test.ts → useMultiplePermissions.test.ts} +49 -49
  513. package/src/rbac/hooks/permissions/useMultiplePermissions.ts +6 -2
  514. package/src/rbac/hooks/permissions/{__tests__/usePermissions.test.ts → usePermissions.test.ts} +10 -12
  515. package/src/rbac/hooks/permissions/usePermissions.ts +36 -65
  516. package/src/rbac/hooks/useCan.test.ts +42 -42
  517. package/src/rbac/hooks/usePageAccessLogging.ts +160 -0
  518. package/src/rbac/hooks/usePageGuardScope.ts +117 -0
  519. package/src/rbac/hooks/usePagePermissionCheck.ts +67 -0
  520. package/src/rbac/hooks/{__tests__/usePermissions.integration.test.ts → usePermissions.integration.test.ts} +9 -9
  521. package/src/{__tests__/hooks/usePermissions.test.ts → rbac/hooks/usePermissions.stability.test.ts} +18 -18
  522. package/src/rbac/hooks/usePermissions.test.ts +54 -54
  523. package/src/rbac/hooks/useRBAC.test.ts +313 -217
  524. package/src/rbac/hooks/useRBAC.ts +145 -81
  525. package/src/rbac/hooks/useResourcePermissions.test.ts +25 -25
  526. package/src/rbac/hooks/useResourcePermissions.ts +68 -134
  527. package/src/rbac/hooks/useResourcePermissionsSuperAdmin.ts +67 -0
  528. package/src/rbac/hooks/useRoleManagement.test.ts +27 -112
  529. package/src/rbac/hooks/useRoleManagement.ts +153 -585
  530. package/src/rbac/hooks/{__tests__/useSecureSupabase.test.ts → useSecureSupabase.test.ts} +17 -17
  531. package/src/rbac/hooks/useSecureSupabase.ts +10 -2
  532. package/src/rbac/hooks/useSuperAdminCheck.ts +80 -0
  533. package/src/rbac/{__tests__/performance.test.ts → performance.test.ts} +1 -1
  534. package/src/rbac/{__tests__/rbac-core.test.tsx → rbac-core.test.tsx} +3 -3
  535. package/src/rbac/{__tests__/rbac-engine-core-logic.test.ts → rbac-engine-core-logic.test.ts} +2 -2
  536. package/src/rbac/{__tests__/rbac-engine-simplified.test.ts → rbac-engine-simplified.test.ts} +3 -3
  537. package/src/rbac/{__tests__/rbac-functions.test.ts → rbac-functions.test.ts} +57 -0
  538. package/src/rbac/{__tests__/rbac-role-isolation.test.ts → rbac-role-isolation.test.ts} +2 -2
  539. package/src/rbac/request-deduplication.test.ts +14 -9
  540. package/src/rbac/request-deduplication.ts +5 -4
  541. package/src/rbac/{__tests__/scenarios.user-role.test.tsx → scenarios.user-role.test.tsx} +23 -23
  542. package/src/rbac/secureClient.test.ts +514 -83
  543. package/src/rbac/secureClient.ts +8 -2
  544. package/src/rbac/security.test.ts +323 -0
  545. package/src/rbac/types/roleManagement.ts +66 -0
  546. package/src/rbac/utils/{__tests__/clientSecurity.test.ts → clientSecurity.test.ts} +4 -4
  547. package/src/rbac/utils/{__tests__/contextValidator.test.ts → contextValidator.test.ts} +4 -4
  548. package/src/rbac/utils/contextValidator.ts +5 -1
  549. package/src/rbac/utils/{__tests__/deep-equal.test.ts → deep-equal.test.ts} +1 -1
  550. package/src/rbac/utils/{__tests__/eventContext.test.ts → eventContext.test.ts} +36 -21
  551. package/src/rbac/utils/eventContext.ts +37 -33
  552. package/src/rbac/utils/fetchPermissionMap.ts +13 -0
  553. package/src/rbac/utils/permissionMapHelpers.ts +34 -0
  554. package/src/rbac/utils/roleManagementRpc.ts +303 -0
  555. package/src/services/{__tests__/AuthService.edge-cases.test.ts → AuthService.edge-cases.test.ts} +19 -19
  556. package/src/services/{__tests__/AuthService.restoreSession.test.ts → AuthService.restoreSession.test.ts} +2 -2
  557. package/src/services/{__tests__/AuthService.test.ts → AuthService.test.ts} +89 -55
  558. package/src/services/AuthService.ts +184 -205
  559. package/src/services/{__tests__/BaseService.edge-cases.test.ts → BaseService.edge-cases.test.ts} +3 -3
  560. package/src/services/{__tests__/BaseService.test.ts → BaseService.test.ts} +2 -2
  561. package/src/services/{__tests__/EventService.edge-cases.test.ts → EventService.edge-cases.test.ts} +27 -24
  562. package/src/services/{__tests__/EventService.eventColours.test.ts → EventService.eventColours.test.ts} +1 -1
  563. package/src/services/{__tests__/EventService.test.ts → EventService.test.ts} +256 -24
  564. package/src/services/EventService.ts +242 -312
  565. package/src/services/{__tests__/InactivityService.edge-cases.test.ts → InactivityService.edge-cases.test.ts} +3 -3
  566. package/src/services/{__tests__/InactivityService.lifecycle.test.ts → InactivityService.lifecycle.test.ts} +2 -2
  567. package/src/services/{__tests__/InactivityService.test.ts → InactivityService.test.ts} +179 -4
  568. package/src/services/InactivityService.ts +172 -213
  569. package/src/services/{__tests__/OrganisationService.edge-cases.test.ts → OrganisationService.edge-cases.test.ts} +5 -5
  570. package/src/services/{__tests__/OrganisationService.pagination.test.ts → OrganisationService.pagination.test.ts} +4 -4
  571. package/src/services/{__tests__/OrganisationService.test.ts → OrganisationService.test.ts} +410 -7
  572. package/src/services/OrganisationService.ts +184 -238
  573. package/src/services/base/BaseService.test.ts +1 -1
  574. package/src/services/interfaces/{__tests__/IAuthService.test.ts → IAuthService.test.ts} +21 -27
  575. package/src/services/interfaces/IAuthService.ts +10 -9
  576. package/src/services/interfaces/{__tests__/IEventService.test.ts → IEventService.test.ts} +4 -4
  577. package/src/services/interfaces/{__tests__/IInactivityService.test.ts → IInactivityService.test.ts} +3 -3
  578. package/src/services/interfaces/{__tests__/IOrganisationService.test.ts → IOrganisationService.test.ts} +3 -3
  579. package/src/styles/core.css +243 -12
  580. package/src/theming/{__tests__/parseEventColours.test.ts → parseEventColours.test.ts} +1 -1
  581. package/src/theming/{__tests__/runtime.test.ts → runtime.test.ts} +8 -17
  582. package/src/theming/runtime.ts +71 -2
  583. package/src/types/api-result.ts +53 -0
  584. package/src/types/{__tests__/core.test.ts → core.test.ts} +2 -2
  585. package/src/types/{__tests__/database-generated.test.ts → database-generated.test.ts} +3 -3
  586. package/src/types/database.generated.ts +45 -10
  587. package/src/types/event.ts +38 -18
  588. package/src/types/{__tests__/file-reference.test.ts → file-reference.test.ts} +13 -13
  589. package/src/types/file-reference.ts +37 -12
  590. package/src/types/{__tests__/guards.test.ts → guards.test.ts} +2 -2
  591. package/src/types/{__tests__/index.test.ts → index.test.ts} +2 -2
  592. package/src/types/index.ts +3 -0
  593. package/src/types/{__tests__/organisation.roles.test.ts → organisation.roles.test.ts} +1 -1
  594. package/src/types/{__tests__/organisation.test.ts → organisation.test.ts} +3 -31
  595. package/src/types/organisation.ts +15 -15
  596. package/src/types/supabase.ts +13 -4
  597. package/src/types/{__tests__/theme.test.ts → theme.test.ts} +1 -1
  598. package/src/types/{__tests__/type-validation.test.ts → type-validation.test.ts} +1 -1
  599. package/src/types/{__tests__/validation.test.ts → validation.test.ts} +2 -2
  600. package/src/utils/app/appIdResolver.test.ts +98 -71
  601. package/src/utils/app/appIdResolver.ts +31 -20
  602. package/src/utils/{__tests__/appConfig.unit.test.ts → appConfig.unit.test.ts} +1 -1
  603. package/src/utils/{__tests__/audit.unit.test.ts → audit.unit.test.ts} +1 -1
  604. package/src/utils/{__tests__/auth-utils.unit.test.ts → auth-utils.unit.test.ts} +16 -17
  605. package/src/utils/{__tests__/bundleAnalysis.unit.test.ts → bundleAnalysis.unit.test.ts} +35 -35
  606. package/src/utils/{__tests__/cn.unit.test.ts → cn.unit.test.ts} +1 -1
  607. package/src/utils/context/organisationContext.test.ts +105 -91
  608. package/src/utils/context/organisationContext.ts +29 -40
  609. package/src/utils/core/{__tests__/cn.test.ts → cn.test.ts} +3 -3
  610. package/src/utils/core/{__tests__/debugLogger.test.ts → debugLogger.test.ts} +2 -2
  611. package/src/utils/core/{__tests__/logger.test.ts → logger.test.ts} +2 -2
  612. package/src/utils/core/mergeRefs.ts +24 -0
  613. package/src/utils/{__tests__/debugLogger.test.ts → debugLogger.test.ts} +1 -1
  614. package/src/utils/{__tests__/deviceFingerprint.unit.test.ts → deviceFingerprint.unit.test.ts} +1 -1
  615. package/src/utils/dynamic/createLazyComponent.tsx +9 -1
  616. package/src/utils/dynamic/{__tests__/dynamicUtils.test.ts → dynamicUtils.test.ts} +2 -2
  617. package/src/utils/dynamic/{__tests__/lazyLoad.test.tsx → lazyLoad.test.tsx} +2 -2
  618. package/src/utils/{__tests__/dynamicUtils.unit.test.ts → dynamicUtils.unit.test.ts} +1 -1
  619. package/src/utils/file-reference/{__tests__/file-reference.test.ts → file-reference.test.ts} +214 -289
  620. package/src/utils/file-reference/index.ts +330 -347
  621. package/src/utils/{__tests__/formatDate.unit.test.ts → formatDate.unit.test.ts} +2 -2
  622. package/src/utils/formatting/formatDateTimeTimezone.test.ts +1 -1
  623. package/src/utils/formatting/formatNumber.test.ts +1 -1
  624. package/src/utils/{__tests__/formatting.unit.test.ts → formatting.unit.test.ts} +1 -1
  625. package/src/utils/google-places/googlePlacesUtils.test.ts +70 -48
  626. package/src/utils/google-places/googlePlacesUtils.ts +67 -99
  627. package/src/utils/google-places/loadGoogleMapsScript.test.ts +25 -22
  628. package/src/utils/google-places/loadGoogleMapsScript.ts +138 -117
  629. package/src/utils/{__tests__/index.unit.test.ts → index.unit.test.ts} +1 -1
  630. package/src/utils/{__tests__/lazyLoad.unit.test.tsx → lazyLoad.unit.test.tsx} +13 -14
  631. package/src/utils/location/location.test.ts +1 -1
  632. package/src/utils/{__tests__/logger.unit.test.ts → logger.unit.test.ts} +1 -1
  633. package/src/utils/{__tests__/organisationContext.unit.test.ts → organisationContext.unit.test.ts} +37 -48
  634. package/src/utils/performance/{__tests__/bundleAnalysis.test.ts → bundleAnalysis.test.ts} +2 -2
  635. package/src/utils/performance/{__tests__/performanceBenchmark.test.ts → performanceBenchmark.test.ts} +2 -2
  636. package/src/utils/performance/{__tests__/performanceBudgets.test.ts → performanceBudgets.test.ts} +2 -2
  637. package/src/utils/{__tests__/performanceBenchmark.test.ts → performanceBenchmark.test.ts} +2 -2
  638. package/src/utils/{__tests__/performanceBudgets.unit.test.ts → performanceBudgets.unit.test.ts} +2 -2
  639. package/src/utils/{__tests__/permissionTypes.unit.test.ts → permissionTypes.unit.test.ts} +1 -1
  640. package/src/utils/{__tests__/permissionUtils.unit.test.ts → permissionUtils.unit.test.ts} +1 -1
  641. package/src/utils/permissions/{__tests__/permissionTypes.test.ts → permissionTypes.test.ts} +2 -2
  642. package/src/utils/persistence/{__tests__/keyDerivation.test.ts → keyDerivation.test.ts} +2 -2
  643. package/src/utils/persistence/{__tests__/sensitiveFieldDetection.test.ts → sensitiveFieldDetection.test.ts} +2 -2
  644. package/src/utils/{__tests__/request-deduplication.test.ts → request-deduplication.test.ts} +2 -2
  645. package/src/utils/{__tests__/sanitization.unit.test.ts → sanitization.unit.test.ts} +1 -1
  646. package/src/utils/{__tests__/schemaUtils.unit.test.ts → schemaUtils.unit.test.ts} +1 -1
  647. package/src/utils/{__tests__/secureDataAccess.unit.test.ts → secureDataAccess.unit.test.ts} +2 -2
  648. package/src/utils/{__tests__/secureErrors.unit.test.ts → secureErrors.unit.test.ts} +4 -4
  649. package/src/utils/{__tests__/secureStorage.unit.test.ts → secureStorage.unit.test.ts} +1 -1
  650. package/src/utils/security/auth-utils.ts +34 -23
  651. package/src/utils/security/secureDataAccess.ts +241 -281
  652. package/src/utils/security/secureErrors.test.ts +1 -1
  653. package/src/utils/security/secureStorage.test.ts +1 -1
  654. package/src/utils/security/security.test.ts +25 -17
  655. package/src/utils/security/security.ts +15 -18
  656. package/src/utils/security/securityMonitor.test.ts +1 -1
  657. package/src/utils/{__tests__/security.unit.test.ts → security.unit.test.ts} +21 -15
  658. package/src/utils/{__tests__/securityMonitor.unit.test.ts → securityMonitor.unit.test.ts} +1 -1
  659. package/src/utils/{__tests__/sessionTracking.unit.test.ts → sessionTracking.unit.test.ts} +12 -12
  660. package/src/utils/storage/{__tests__/config.unit.test.ts → config.unit.test.ts} +2 -2
  661. package/src/utils/storage/helpers.test.ts +88 -102
  662. package/src/utils/storage/helpers.ts +173 -251
  663. package/src/utils/storage/{__tests__/index.unit.test.ts → index.unit.test.ts} +3 -3
  664. package/src/utils/storage/types.ts +7 -0
  665. package/src/utils/supabase/createBaseClient.test.ts +1 -1
  666. package/src/utils/timezone/timezone.test.ts +1 -1
  667. package/src/utils/{__tests__/timezone.test.ts → timezone.test.ts} +2 -2
  668. package/src/utils/validation/{__tests__/common.test.ts → common.test.ts} +2 -2
  669. package/src/utils/validation/{__tests__/csrf.test.ts → csrf.test.ts} +56 -28
  670. package/src/utils/validation/csrf.ts +42 -41
  671. package/src/utils/validation/{__tests__/htmlSanitization.unit.test.ts → htmlSanitization.unit.test.ts} +2 -2
  672. package/src/utils/validation/{__tests__/passwordSchema.test.ts → passwordSchema.test.ts} +2 -2
  673. package/src/utils/validation/{__tests__/schema.test.ts → schema.test.ts} +2 -2
  674. package/src/utils/validation/{__tests__/sqlInjectionProtection.test.ts → sqlInjectionProtection.test.ts} +2 -2
  675. package/src/utils/validation/{__tests__/user.test.ts → user.test.ts} +2 -2
  676. package/src/utils/validation/{__tests__/validation.test.ts → validation.test.ts} +2 -2
  677. package/src/utils/validation/{__tests__/validationUtils.test.ts → validationUtils.test.ts} +2 -2
  678. package/src/utils/{__tests__/validation.unit.test.ts → validation.unit.test.ts} +1 -1
  679. package/src/utils/{__tests__/validationUtils.unit.test.ts → validationUtils.unit.test.ts} +5 -2
  680. package/dist/UnifiedAuthProvider-BBD2PS3Q.js +0 -7
  681. package/dist/chunk-KPYQWGFQ.js +0 -183
  682. package/dist/types-D05dCGma.d.ts +0 -521
  683. package/scripts/eslint-audit.cjs +0 -222
  684. package/scripts/generate-docs.js +0 -157
  685. package/scripts/install-cursor-rules.cjs +0 -255
  686. package/scripts/install-eslint-config.cjs +0 -349
  687. package/scripts/setup-build-cache.js +0 -73
  688. package/scripts/validate-pre-publish.js +0 -145
  689. package/src/__tests__/integration/UserProfile.test.tsx +0 -124
  690. package/src/__tests__/public-recipe-view.test.ts +0 -228
  691. package/src/__tests__/rls-policies.test.ts +0 -472
  692. package/src/components/DataTable/__tests__/DataTable.test.tsx +0 -876
  693. package/src/components/DataTable/components/DataTableLayout.tsx +0 -584
  694. package/src/components/DataTable/components/UnifiedTableBody.tsx +0 -395
  695. package/src/components/DataTable/components/__tests__/DataTableLayout.test.tsx +0 -467
  696. package/src/components/DataTable/components/__tests__/DataTableModals.test.tsx +0 -358
  697. package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +0 -957
  698. package/src/components/DataTable/core/ActionManager.ts +0 -235
  699. package/src/components/DataTable/core/ColumnManager.ts +0 -204
  700. package/src/components/DataTable/core/DataManager.ts +0 -190
  701. package/src/components/DataTable/core/LocalDataAdapter.ts +0 -274
  702. package/src/components/DataTable/core/PluginRegistry.ts +0 -229
  703. package/src/components/DataTable/core/StateManager.ts +0 -312
  704. package/src/components/DataTable/core/__tests__/ActionManager.test.ts +0 -235
  705. package/src/components/DataTable/core/__tests__/ColumnManager.test.ts +0 -141
  706. package/src/components/DataTable/core/__tests__/DataManager.test.ts +0 -178
  707. package/src/components/DataTable/core/__tests__/LocalDataAdapter.test.ts +0 -133
  708. package/src/components/DataTable/core/__tests__/PluginRegistry.test.ts +0 -142
  709. package/src/components/DataTable/core/__tests__/StateManager.test.ts +0 -158
  710. package/src/components/DataTable/core/interfaces.ts +0 -338
  711. package/src/components/DataTable/types.ts +0 -764
  712. package/src/hooks/public/usePublicFileDisplay.ts +0 -534
  713. package/src/hooks/useFileDisplay.ts +0 -748
  714. package/src/providers/OrganisationProvider.test.tsx +0 -40
  715. package/src/providers/OrganisationProvider.tsx +0 -92
  716. package/src/providers/__tests__/InactivityProvider.test-helper.tsx +0 -65
  717. package/src/providers/__tests__/OrganisationProvider.test.tsx +0 -616
  718. package/src/providers/__tests__/OrganisationProvider.wrapper.test.tsx +0 -591
  719. package/src/rbac/__tests__/cache-invalidation.test.ts +0 -393
  720. /package/src/components/DataTable/{components/__tests__ → ui}/COVERAGE_NOTE.md +0 -0
  721. /package/src/components/DataTable/utils/{__tests__/COVERAGE_NOTE.md → COVERAGE_NOTE.md} +0 -0
  722. /package/src/hooks/{__tests__/useApiFetch.unit.test.ts → useApiFetch.unit.test.ts} +0 -0
  723. /package/src/providers/{__tests__/README.md → README.md} +0 -0
  724. /package/src/rbac/{__tests__/index.test.ts → index.test.ts} +0 -0
  725. /package/src/rbac/{__tests__/rbac-integration.test.ts → rbac-integration.test.ts} +0 -0
  726. /package/src/types/{__tests__/README.md → README.md} +0 -0
@@ -42,7 +42,7 @@
42
42
  * <DialogBody>
43
43
  * <section className="space-y-4">
44
44
  * <fieldset className="grid grid-cols-4 items-center gap-4">
45
- * <Label htmlFor="name" className="text-right">Name</Label>
45
+ * <Label htmlFor="name">Name</Label>
46
46
  * <Input id="name" defaultValue="John Doe" className="col-span-3" />
47
47
  * </fieldset>
48
48
  * </section>
@@ -89,16 +89,20 @@
89
89
 
90
90
  import * as React from 'react';
91
91
  import { createPortal } from 'react-dom';
92
- import { X } from 'lucide-react';
93
92
  import { useLocation } from 'react-router-dom';
93
+ import { Button, IconButton } from '../Button';
94
+ import { X } from '../../icons';
94
95
  import { useUnifiedAuth } from '../../providers/services/UnifiedAuthProvider';
95
96
  import { cn } from '../../utils/core/cn';
97
+ import { mergeRefs } from '../../utils/core/mergeRefs';
96
98
  import { renderSafeHtml } from '../../utils/validation/htmlSanitization';
97
99
  import { useState, useEffect, useRef, useCallback, useId, useMemo } from 'react';
98
100
  import { useFocusTrap } from '../../hooks/useFocusTrap';
99
- import { useSessionDraft } from '../../hooks/useSessionDraft';
100
101
  import { deriveDialogKey } from '../../utils/persistence/keyDerivation';
101
102
  import { createLogger } from '../../utils/core/logger';
103
+ import { useDialogPersistence } from './useDialogPersistence';
104
+ import { useDialogDimensions } from './useDialogDimensions';
105
+ import { useDialogLifecycle } from './useDialogLifecycle';
102
106
 
103
107
  /**
104
108
  * Dialog size variants
@@ -115,7 +119,6 @@ interface DialogContextValue {
115
119
  dialogRef: React.RefObject<HTMLDialogElement | null>;
116
120
  titleId: string;
117
121
  descriptionId: string;
118
- dialogTitle?: string; // For persistence key derivation
119
122
  markClosedByUser?: () => void; // Callback to mark dialog as closed by user (for Cancel buttons, etc.)
120
123
  }
121
124
 
@@ -190,12 +193,6 @@ export interface DialogContentProps extends React.HTMLAttributes<HTMLDialogEleme
190
193
  persistOpenState?: boolean;
191
194
  }
192
195
 
193
- /**
194
- * Props for the DialogOverlay component
195
- * @public
196
- */
197
- export interface DialogOverlayProps extends React.HTMLAttributes<HTMLDivElement> {}
198
-
199
196
  /**
200
197
  * Props for the DialogPortal component
201
198
  * @public
@@ -259,16 +256,6 @@ export interface DialogDescriptionProps extends React.HTMLAttributes<HTMLParagra
259
256
  allowHtml?: boolean;
260
257
  }
261
258
 
262
- // Size mapping for dialog variants
263
- const sizeClasses = {
264
- sm: 'max-w-sm',
265
- md: 'max-w-md',
266
- lg: 'max-w-lg',
267
- xl: 'max-w-xl',
268
- full: 'max-w-full size-full',
269
- auto: 'max-w-none w-auto min-w-0'
270
- };
271
-
272
259
  /**
273
260
  * Dialog root component
274
261
  * Provides context for dialog state management
@@ -283,7 +270,6 @@ const Dialog = React.memo<DialogProps>(function Dialog({
283
270
  const dialogRef = useRef<HTMLDialogElement | null>(null);
284
271
  const titleId = useId();
285
272
  const descriptionId = useId();
286
- const dialogTitleRef = useRef<string | undefined>(undefined);
287
273
  const [markClosedByUser, setMarkClosedByUserState] = useState<(() => void) | undefined>(undefined);
288
274
 
289
275
  const isControlled = controlledOpen !== undefined;
@@ -302,15 +288,9 @@ const Dialog = React.memo<DialogProps>(function Dialog({
302
288
  dialogRef,
303
289
  titleId,
304
290
  descriptionId,
305
- dialogTitle: dialogTitleRef.current,
306
291
  markClosedByUser, // Set by DialogContent
307
292
  }), [open, handleOpenChange, titleId, descriptionId, markClosedByUser]);
308
293
 
309
- // Expose function to set dialog title (called by DialogContent)
310
- const setDialogTitle = useCallback((title: string | undefined) => {
311
- dialogTitleRef.current = title;
312
- }, []);
313
-
314
294
  // Expose function to set markClosedByUser callback (called by DialogContent)
315
295
  const setMarkClosedByUser = useCallback((callback: (() => void) | undefined) => {
316
296
  setMarkClosedByUserState(callback);
@@ -318,22 +298,17 @@ const Dialog = React.memo<DialogProps>(function Dialog({
318
298
 
319
299
  return (
320
300
  <DialogContext.Provider value={contextValue}>
321
- <DialogTitleContext.Provider value={setDialogTitle}>
322
- <DialogMarkClosedContext.Provider value={setMarkClosedByUser}>
323
- {children}
324
- </DialogMarkClosedContext.Provider>
325
- </DialogTitleContext.Provider>
301
+ <DialogMarkClosedContext.Provider value={setMarkClosedByUser}>
302
+ {children}
303
+ </DialogMarkClosedContext.Provider>
326
304
  </DialogContext.Provider>
327
305
  );
328
306
  });
329
307
 
330
- // Context for setting dialog title from DialogContent
331
- const DialogTitleContext = React.createContext<((title: string | undefined) => void) | null>(null);
332
-
333
308
  // Context for setting markClosedByUser callback from DialogContent
334
309
  const DialogMarkClosedContext = React.createContext<((callback: (() => void) | undefined) => void) | null>(null);
335
310
 
336
- // Context for marking dialog as closed by user (from DialogContent to DialogClose)
311
+ // Context for passing markClosedByUser to DialogClose in the same render (DialogContext.markClosedByUser is set async)
337
312
  const DialogCloseContext = React.createContext<(() => void) | null>(null);
338
313
  Dialog.displayName = 'Dialog';
339
314
 
@@ -341,7 +316,7 @@ Dialog.displayName = 'Dialog';
341
316
  * DialogTrigger component
342
317
  * Opens the dialog when clicked
343
318
  */
344
- const DialogTrigger = React.forwardRef<HTMLElement, DialogTriggerProps>(
319
+ const DialogTrigger = React.forwardRef<HTMLButtonElement, DialogTriggerProps>(
345
320
  ({ children, asChild = false, className, onClick, ...props }, ref) => {
346
321
  const { onOpenChange } = useDialogContext();
347
322
 
@@ -357,19 +332,19 @@ const DialogTrigger = React.forwardRef<HTMLElement, DialogTriggerProps>(
357
332
  onClick: handleClick,
358
333
  className: cn(className, childElement.props?.className),
359
334
  ...props,
360
- } as Partial<React.HTMLAttributes<HTMLElement>>) as React.ReactElement;
335
+ } as React.HTMLAttributes<HTMLElement> & React.RefAttributes<HTMLElement>);
361
336
  }
362
337
 
363
338
  return (
364
- <button
365
- ref={ref as React.RefObject<HTMLButtonElement>}
339
+ <Button
340
+ ref={ref}
366
341
  type="button"
367
342
  onClick={handleClick}
368
343
  className={className}
369
344
  {...props}
370
345
  >
371
346
  {children}
372
- </button>
347
+ </Button>
373
348
  );
374
349
  }
375
350
  );
@@ -393,172 +368,28 @@ const DialogPortal: React.FC<DialogPortalProps> = ({ children }) => {
393
368
  };
394
369
  DialogPortal.displayName = 'DialogPortal';
395
370
 
396
- /**
397
- * DialogOverlay component
398
- * Backdrop overlay for the dialog (optional, native dialog provides ::backdrop)
399
- * This component is kept for backward compatibility but may not be needed
400
- * when using native dialog element which provides ::backdrop automatically
401
- */
402
- const DialogOverlay = React.forwardRef<HTMLDivElement, DialogOverlayProps>(
403
- ({ className: _className, ..._props }, _ref) => {
404
- // Note: Native dialog element provides ::backdrop automatically
405
- // This component is kept for API compatibility but may not render
406
- // The native dialog's ::backdrop is styled via CSS
407
- return null;
408
- }
409
- );
410
- DialogOverlay.displayName = 'DialogOverlay';
411
-
412
- /**
413
- * Custom hook for managing smart dialog dimensions
414
- * Handles responsive sizing and viewport-based constraints
415
- * Dimensions are calculated synchronously based on props - no resize listener needed
416
- */
417
- const useSmartDimensions = ({
418
- maxHeightPercent,
419
- maxWidthPercent,
420
- maxHeight,
421
- maxWidth,
422
- minHeight,
423
- minWidth,
424
- }: {
425
- maxHeightPercent?: number;
426
- maxWidthPercent?: number;
427
- maxHeight?: string;
428
- maxWidth?: string;
429
- minHeight?: string;
430
- minWidth?: string;
431
- }) => {
432
- // Calculate dimensions synchronously - no need for resize listener
433
- // CSS viewport units (vh/vw) handle responsiveness automatically
434
- return React.useMemo<React.CSSProperties>(() => {
435
- const result: React.CSSProperties = {};
436
-
437
- // Handle height constraints
438
- if (maxHeightPercent && typeof maxHeightPercent === 'number') {
439
- const constrainedHeight = Math.min(maxHeightPercent, 95);
440
- result.maxHeight = `${constrainedHeight}vh`;
441
- } else if (maxHeight) {
442
- result.maxHeight = maxHeight;
443
- }
444
-
445
- // Handle width constraints
446
- if (maxWidthPercent && typeof maxWidthPercent === 'number') {
447
- const constrainedWidth = Math.min(maxWidthPercent, 95);
448
- result.maxWidth = `${constrainedWidth}vw`;
449
- } else if (maxWidth) {
450
- result.maxWidth = maxWidth;
451
- }
452
-
453
- // Include minHeight/minWidth if provided
454
- if (minHeight) {
455
- result.minHeight = minHeight;
456
- }
457
- if (minWidth) {
458
- result.minWidth = minWidth;
459
- }
460
-
461
- return result;
462
- }, [maxHeightPercent, maxWidthPercent, maxHeight, maxWidth, minHeight, minWidth]);
463
- };
464
-
465
- /**
466
- * Global lock to ensure only one dialog is open at a time
467
- * Uses sessionStorage for persistence across page reloads
468
- */
469
- const DIALOG_LOCK_KEY = 'pace-core:dialog:lock';
470
-
471
- function acquireDialogLock(persistenceKey: string | null): boolean {
472
- if (!persistenceKey) {
473
- return true; // Non-persisted dialogs can always open
474
- }
475
-
476
- try {
477
- const lock = sessionStorage.getItem(DIALOG_LOCK_KEY);
478
- if (lock) {
479
- const lockData = JSON.parse(lock);
480
- // If lock is held by this dialog, allow it
481
- if (lockData.key === persistenceKey) {
482
- return true;
483
- }
484
- // If lock is held by another dialog, check if that dialog is still open
485
- const lockDialog = document.querySelector(`dialog[data-persistence-key="${lockData.key}"]`) as HTMLDialogElement;
486
- if (lockDialog && lockDialog.open) {
487
- return false; // Another dialog is still open
488
- }
489
- // Lock is stale, clear it
490
- sessionStorage.removeItem(DIALOG_LOCK_KEY);
491
- }
492
- // Acquire the lock
493
- sessionStorage.setItem(DIALOG_LOCK_KEY, JSON.stringify({
494
- key: persistenceKey,
495
- timestamp: Date.now(),
496
- }));
497
- return true;
498
- } catch {
499
- // If sessionStorage fails, allow opening (graceful degradation)
500
- return true;
501
- }
502
- }
503
-
504
- function releaseDialogLock(persistenceKey: string | null): void {
505
- if (!persistenceKey) {
506
- return;
507
- }
508
-
509
- try {
510
- const lock = sessionStorage.getItem(DIALOG_LOCK_KEY);
511
- if (lock) {
512
- const lockData = JSON.parse(lock);
513
- if (lockData.key === persistenceKey) {
514
- sessionStorage.removeItem(DIALOG_LOCK_KEY);
515
- }
516
- }
517
- } catch {
518
- // Ignore errors
519
- }
520
- }
521
-
522
- /**
523
- * Check if any other dialog (besides the current one) has persisted open state
524
- * This helps determine if another dialog should be allowed to auto-open
525
- */
526
- function checkOtherDialogsHavePersistedState(currentPersistenceKey: string | null): boolean {
527
- if (!currentPersistenceKey) {
528
- return false;
529
- }
530
-
531
- try {
532
- const lock = sessionStorage.getItem(DIALOG_LOCK_KEY);
533
- if (lock) {
534
- const lockData = JSON.parse(lock);
535
- if (lockData.key !== currentPersistenceKey) {
536
- // Another dialog holds the lock
537
- return true;
538
- }
539
- }
540
- } catch {
541
- // Error accessing sessionStorage - assume no other dialogs
542
- return false;
543
- }
544
-
545
- return false;
371
+ export interface UseDialogContentStateReturn {
372
+ mergedRef: (node: HTMLDialogElement | null) => void;
373
+ canRender: boolean;
374
+ markClosedByUser: () => void;
375
+ sizeClass: string;
376
+ mergedStyle: React.CSSProperties;
377
+ hasHeightConstraint: boolean;
378
+ persistenceKey: string | null;
379
+ persistOpenState: boolean;
380
+ titleId: string;
381
+ descriptionId: string;
382
+ open: boolean;
383
+ title: string | undefined;
384
+ description: string | undefined;
546
385
  }
547
386
 
548
- /**
549
- * DialogContent component
550
- * The main content container using semantic HTML <dialog> element with enhanced features
551
- *
552
- * @param props - Content configuration and styling
553
- * @param ref - Forwarded ref to the dialog element
554
- * @returns JSX.Element - The semantic dialog content with overlay and optional close button
555
- */
556
- const DialogContent = React.forwardRef<HTMLDialogElement, DialogContentProps>(
557
- ({
558
- className,
559
- children,
387
+ function useDialogContentState(
388
+ props: DialogContentProps,
389
+ ref: React.ForwardedRef<HTMLDialogElement>
390
+ ): UseDialogContentStateReturn {
391
+ const {
560
392
  size = 'md',
561
- showCloseButton = true,
562
393
  preventCloseOnEscape = false,
563
394
  preventCloseOnOutsideClick = false,
564
395
  maxHeightPercent,
@@ -572,427 +403,47 @@ const DialogContent = React.forwardRef<HTMLDialogElement, DialogContentProps>(
572
403
  description,
573
404
  style,
574
405
  persistOpenState = true,
575
- ...props
576
- }, ref) => {
577
- // Call all hooks unconditionally at the top level
578
- // Hooks must be called in the same order on every render
579
- const { open, onOpenChange, dialogRef, titleId, descriptionId } = useDialogContext();
580
- const setDialogTitle = React.useContext(DialogTitleContext);
581
- const setMarkClosedByUser = React.useContext(DialogMarkClosedContext);
582
-
583
- // Component mount/unmount tracking removed for performance
584
-
585
- // Call hooks unconditionally - if providers are missing, they will throw
586
- // Errors should be handled by error boundaries at a higher level
587
- const location = useLocation();
588
- const auth = useUnifiedAuth();
589
- const userId = auth.user?.id || null;
590
-
591
- // Create logger after all hooks are called (must be before any useEffects that use it)
592
- const logger = createLogger('Dialog');
593
-
594
- // Set dialog title in context for persistence
595
- useEffect(() => {
596
- if (setDialogTitle) {
597
- setDialogTitle(title);
598
- }
599
- }, [title, setDialogTitle]);
600
-
601
- // Derive persistence key (scoped by user ID)
602
- // CRITICAL: Only enable persistence if we have a valid userId to prevent data leakage
603
- const persistenceKey = useMemo(() => {
604
- if (!persistOpenState) {
605
- return null;
606
- }
607
- // Don't create persistence key if userId is not available
608
- // This prevents unscoped persistence that could leak between users
609
- if (!userId) {
610
- return null;
611
- }
612
- return deriveDialogKey(
613
- {
614
- title,
615
- description,
616
- },
617
- location,
618
- userId
619
- );
620
- }, [title, description, location, userId, persistOpenState]);
621
-
622
- // Use session draft for open state persistence
623
- // Only enabled when we have a valid persistenceKey (which requires userId)
624
- const { state: persistedOpen, setState: setPersistedOpen, clearDraft, wasRestored } = useSessionDraft<boolean>(
625
- persistenceKey ? `${persistenceKey}:open` : 'dialog:no-key:open',
626
- false,
627
- {
628
- enabled: Boolean(persistenceKey && persistOpenState && userId),
629
- debounceMs: 300,
630
- }
631
- );
632
-
633
- // Track if we've attempted auto-open to prevent multiple attempts
634
- const hasAutoOpenedRef = useRef(false);
635
- const hasInitializedRef = useRef(false);
636
- // Track if dialog was closed by user action (to clear persistence)
637
- const wasClosedByUserRef = useRef(false);
638
- // Track if dialog was manually opened (to prevent auto-open from interfering)
639
- const wasManuallyOpenedRef = useRef(false);
640
-
641
- // Callback to mark dialog as closed by user (exposed via context for DialogClose and Cancel buttons)
642
- const markClosedByUser = useCallback(() => {
643
- if (hasInitializedRef.current) {
644
- wasClosedByUserRef.current = true;
645
- }
646
- }, []);
647
-
648
- // Register markClosedByUser with parent Dialog component so it's available via DialogContext
649
- useEffect(() => {
650
- if (setMarkClosedByUser) {
651
- setMarkClosedByUser(markClosedByUser);
652
- }
653
- return () => {
654
- // Cleanup: unregister when DialogContent unmounts
655
- if (setMarkClosedByUser) {
656
- setMarkClosedByUser(undefined);
657
- }
658
- };
659
- }, [setMarkClosedByUser, markClosedByUser]);
660
- // Track if we've cleaned up other dialog states (to prevent multiple cleanup runs)
661
- const hasCleanedUpOtherDialogsRef = useRef(false);
662
-
663
- // Auto-open on mount if dialog was open when tab closed
664
- useEffect(() => {
665
- if (!persistenceKey || !persistOpenState) {
666
- hasInitializedRef.current = true;
667
- return;
668
- }
669
-
670
- // Only attempt auto-open once
671
- // This prevents auto-open from running after manual opens
672
- if (hasAutoOpenedRef.current) {
673
- return;
674
- }
675
-
676
- // CRITICAL: Don't auto-open if dialog was manually opened
677
- // This prevents auto-open from interfering with manual opens
678
- if (wasManuallyOpenedRef.current) {
679
- logger.debug('[Dialog Persistence] ⏭️ Skipping auto-open - dialog was manually opened', {
680
- persistenceKey,
681
- currentOpen: open,
682
- });
683
- return;
684
- }
685
-
686
- // Mark as initialized after first check
687
- if (!hasInitializedRef.current) {
688
- hasInitializedRef.current = true;
689
- }
690
-
691
- // Auto-open check (logging removed for performance)
692
-
693
- // CRITICAL: Don't auto-open if dialog is already open (user-initiated)
694
- if (open === true) {
695
- hasAutoOpenedRef.current = true;
696
- return;
697
- }
698
-
699
- // CRITICAL: Don't auto-open if userId is not available (prevents unscoped persistence from being restored)
700
- if (!userId) {
701
- hasAutoOpenedRef.current = true;
702
- return;
703
- }
704
-
705
- // Only auto-open if conditions are met
706
- if (persistedOpen === true && open === false && wasRestored && !hasAutoOpenedRef.current && !wasManuallyOpenedRef.current) {
707
- const AUTO_OPEN_LOCK_KEY = 'pace-core:dialog:auto-open-lock';
708
- const lockTimestamp = sessionStorage.getItem(AUTO_OPEN_LOCK_KEY);
709
- const now = Date.now();
710
-
711
- // Check if another dialog is already auto-opening (lock exists and is recent)
712
- if (lockTimestamp) {
713
- const lockAge = now - parseInt(lockTimestamp, 10);
714
- if (lockAge < 1000) {
715
- // Lock is recent - another dialog is auto-opening
716
- // Check if there's already an open dialog with this persistence key
717
- const existingOpenDialog = document.querySelector(`dialog[data-persistence-key="${persistenceKey}"][open]`);
718
- if (existingOpenDialog) {
719
- // Another instance is already open - skip
720
- hasAutoOpenedRef.current = true;
721
- return;
722
- }
723
- // Check if other dialog has persisted state
724
- const otherDialogHasPersistedState = checkOtherDialogsHavePersistedState(persistenceKey);
725
- if (otherDialogHasPersistedState) {
726
- // Another dialog with persisted state is auto-opening - skip this one
727
- clearDraft();
728
- hasAutoOpenedRef.current = true;
729
- return;
730
- }
731
- }
732
- // Clear stale locks
733
- if (lockAge > 2000) {
734
- sessionStorage.removeItem(AUTO_OPEN_LOCK_KEY);
735
- }
736
- }
737
-
738
- // Check if dialog with same key is already open in DOM (synchronous check)
739
- const existingDialog = document.querySelector(`dialog[data-persistence-key="${persistenceKey}"][open]`);
740
- if (existingDialog && existingDialog !== dialogRef.current && existingDialog !== internalRef.current) {
741
- hasAutoOpenedRef.current = true;
742
- return;
743
- }
744
-
745
- // Set lock and mark as auto-opened BEFORE calling onOpenChange
746
- sessionStorage.setItem(AUTO_OPEN_LOCK_KEY, String(now));
747
- hasAutoOpenedRef.current = true;
748
- wasManuallyOpenedRef.current = false;
749
-
750
- logger.debug('[Dialog] 🔄 AUTO-OPEN', { persistenceKey });
751
-
752
- // Use small delay to prevent visual flash
753
- const timeoutId = setTimeout(() => {
754
- // Double-check: if dialog is still closed and no other instance opened it
755
- const stillClosed = !open;
756
- const noOtherInstance = !document.querySelector(`dialog[data-persistence-key="${persistenceKey}"][open]`);
757
-
758
- if (stillClosed && noOtherInstance) {
759
- sessionStorage.removeItem(AUTO_OPEN_LOCK_KEY);
760
- onOpenChange(true);
761
- } else {
762
- sessionStorage.removeItem(AUTO_OPEN_LOCK_KEY);
763
- }
764
- }, 75);
765
-
766
- return () => {
767
- clearTimeout(timeoutId);
768
- sessionStorage.removeItem(AUTO_OPEN_LOCK_KEY);
769
- };
770
- }
771
- }, [persistenceKey, persistOpenState, persistedOpen, open, onOpenChange, wasRestored, clearDraft, dialogRef, userId, logger]);
772
-
773
- // When this dialog auto-opens, clear persisted state of all other dialogs
774
- // This prevents multiple dialogs from being restored simultaneously
775
- useEffect(() => {
776
- if (!persistenceKey || !persistOpenState || !open || !hasAutoOpenedRef.current) {
777
- return;
778
- }
779
-
780
- // Only run once when dialog first auto-opens
781
- if (hasCleanedUpOtherDialogsRef.current) {
782
- return;
783
- }
784
-
785
- // Clear all other dialog persisted states from sessionStorage
786
- // AND close any other dialogs that are currently open in the DOM
787
- // This ensures only the first auto-opened dialog remains open
788
- try {
789
- const keysToRemove: string[] = [];
790
- for (let i = 0; i < sessionStorage.length; i++) {
791
- const key = sessionStorage.key(i);
792
- if (key && key.startsWith('pace-core:draft:dialog:') && key.endsWith(':open')) {
793
- // Don't clear this dialog's own state
794
- if (key !== `pace-core:draft:${persistenceKey}:open`) {
795
- keysToRemove.push(key);
796
- }
797
- }
798
- }
799
-
800
- if (keysToRemove.length > 0) {
801
- logger.debug('[Dialog Persistence] Clearing other dialog persisted states:', keysToRemove);
802
- keysToRemove.forEach(key => sessionStorage.removeItem(key));
803
- }
804
-
805
- // Also close any other dialogs that are currently open in the DOM AND have persisted state
806
- // This prevents dialogs with persisted state from being hidden behind this one
807
- // We only close dialogs that have persisted state, not dialogs opened by app code
808
- // We identify dialogs with persistence using a data attribute
809
- const timeoutId = setTimeout(() => {
810
- // Guard against test environment teardown
811
- if (typeof document === 'undefined' || typeof sessionStorage === 'undefined') {
812
- return;
813
- }
814
-
815
- const otherOpenDialogs = document.querySelectorAll('dialog[open][role="dialog"]');
816
- const currentDialog = dialogRef.current || internalRef.current;
817
- if (otherOpenDialogs.length > 0 && currentDialog) {
818
- let closedCount = 0;
819
- otherOpenDialogs.forEach((dialog) => {
820
- // Don't close this dialog
821
- const dialogElement = dialog as HTMLDialogElement;
822
- if (dialogElement !== currentDialog) {
823
- // Check if this dialog has a data-persistence-key attribute
824
- // This indicates it was auto-opened from persistence
825
- const dialogPersistenceKey = dialogElement.getAttribute('data-persistence-key');
826
- if (dialogPersistenceKey && dialogPersistenceKey !== persistenceKey) {
827
- // Check if this dialog's persisted state is true
828
- let hasPersistedState = false;
829
- try {
830
- const key = `pace-core:draft:${dialogPersistenceKey}:open`;
831
- const stored = sessionStorage.getItem(key);
832
- if (stored) {
833
- const parsed = JSON.parse(stored);
834
- if (parsed && parsed.data === true) {
835
- hasPersistedState = true;
836
- }
837
- }
838
- } catch {
839
- // Invalid data - skip
840
- }
841
-
842
- // Only close if this dialog has persisted state (was auto-opened)
843
- if (hasPersistedState) {
844
- logger.debug('[Dialog Persistence] Closing other dialog with persisted state:', dialogPersistenceKey);
845
- dialogElement.close();
846
- closedCount++;
847
- }
848
- }
849
- // If dialog doesn't have data-persistence-key, it was opened by app code - don't close it
850
- }
851
- });
852
- if (closedCount > 0) {
853
- logger.debug('[Dialog Persistence] Closed', closedCount, 'other dialog(s) with persisted state');
854
- }
855
- }
856
- }, 100);
857
-
858
- hasCleanedUpOtherDialogsRef.current = true;
859
-
860
- // Also clear the auto-open lock
861
- if (typeof sessionStorage !== 'undefined') {
862
- sessionStorage.removeItem('pace-core:dialog:auto-open-lock');
863
- }
864
-
865
- // Cleanup timeout on unmount or dependency change
866
- return () => {
867
- clearTimeout(timeoutId);
868
- };
869
- } catch (error) {
870
- logger.warn('[Dialog Persistence] Failed to clear other dialog states:', error);
871
- }
872
- }, [open, persistenceKey, persistOpenState, dialogRef, logger]);
873
-
874
- // When dialog closes (user action), immediately clear persisted state
875
- // This prevents the dialog from auto-opening again after user explicitly closed it
876
- useEffect(() => {
877
- if (!persistenceKey || !persistOpenState) {
878
- return;
879
- }
880
-
881
- if (!hasInitializedRef.current) {
882
- return;
883
- }
884
-
885
- // If dialog is closed and user closed it, clear persisted state immediately
886
- if (!open && wasClosedByUserRef.current) {
887
- clearDraft();
888
- wasClosedByUserRef.current = false;
889
- }
890
- }, [open, persistenceKey, persistOpenState, clearDraft]);
891
-
892
- // Check lock BEFORE allowing dialog to open (synchronous check)
893
- // This prevents React from even trying to open if another dialog is open
894
- // Track lock acquisition state for rendering decision
895
- const [lockAcquired, setLockAcquired] = React.useState(false);
896
-
897
- useEffect(() => {
898
- if (!open) {
899
- setLockAcquired(false);
900
- return;
901
- }
902
-
903
- // Synchronously check if we can acquire the lock
904
- const acquired = acquireDialogLock(persistenceKey);
905
- setLockAcquired(acquired);
906
-
907
- if (!acquired) {
908
- // Another dialog is open - prevent this one from opening
909
- logger.warn('[Dialog] ⚠️ Cannot open - another dialog holds the lock', {
910
- persistenceKey,
911
- });
912
- // Immediately close this dialog's React state
913
- onOpenChange?.(false);
914
- return;
915
- }
916
-
917
- // Lock acquired successfully - dialog can proceed to open
918
- }, [open, persistenceKey, onOpenChange, logger]);
919
-
920
- // Track when dialog closes via onOpenChange to mark as closed by user
921
- // This handles Cancel buttons and other programmatic closes
922
- const previousOpenRef = useRef(open);
923
- useEffect(() => {
924
- // If dialog was open and is now closed, and it wasn't auto-opened, mark as closed by user
925
- if (previousOpenRef.current === true && open === false && hasInitializedRef.current) {
926
- // Only mark as closed by user if it wasn't an auto-open scenario
927
- // Auto-open sets hasAutoOpenedRef before calling onOpenChange, so we can detect it
928
- if (!hasAutoOpenedRef.current || wasManuallyOpenedRef.current) {
929
- // Dialog was manually opened and then closed - mark as closed by user
930
- wasClosedByUserRef.current = true;
931
- }
932
- }
933
- previousOpenRef.current = open;
934
- }, [open]);
935
-
936
- // Persist open state changes
937
- useEffect(() => {
938
- if (!persistenceKey || !persistOpenState) {
939
- return;
940
- }
941
-
942
- // Only persist after initial mount check is complete
943
- // This prevents overwriting the persisted state before auto-open can read it
944
- if (!hasInitializedRef.current) {
945
- return;
946
- }
406
+ } = props;
407
+
408
+ const { open, onOpenChange, dialogRef, titleId, descriptionId } = useDialogContext();
409
+ const setMarkClosedByUser = React.useContext(DialogMarkClosedContext);
410
+
411
+ const location = useLocation();
412
+ const auth = useUnifiedAuth();
413
+ const userId = auth.user?.id || null;
414
+
415
+ const logger = createLogger('Dialog');
416
+
417
+ const persistenceKey = useMemo(() => {
418
+ if (!persistOpenState) return null;
419
+ if (!userId) return null;
420
+ return deriveDialogKey({ title, description }, location, userId);
421
+ }, [title, description, location, userId, persistOpenState]);
422
+
423
+ const internalRef = useRef<HTMLDialogElement>(null);
424
+
425
+ const { lockAcquired, markClosedByUser, clearDraft } = useDialogPersistence({
426
+ open,
427
+ onOpenChange,
428
+ persistenceKey,
429
+ persistOpenState,
430
+ userId,
431
+ dialogRef,
432
+ internalRef,
433
+ setMarkClosedByUser,
434
+ logger,
435
+ });
947
436
 
948
- // Only persist when dialog is open
949
- if (open) {
950
- // Reset the flag when opening
951
- wasClosedByUserRef.current = false;
952
- // If dialog is manually opened (not via auto-open), mark it so auto-open doesn't interfere
953
- // This prevents auto-open from trying to open an already-open dialog
954
- // We check if hasAutoOpenedRef is false to determine if this is a manual open
955
- // (auto-open sets hasAutoOpenedRef to true before calling onOpenChange)
956
- if (!hasAutoOpenedRef.current) {
957
- // Mark as manually opened to prevent auto-open from interfering
958
- wasManuallyOpenedRef.current = true;
959
- // Also mark as "opened" to prevent auto-open from trying to open it again
960
- hasAutoOpenedRef.current = true;
961
- }
962
- setPersistedOpen(true);
963
- } else {
964
- // Only clear draft if user explicitly closed (not if it was never opened or auto-opened then closed)
965
- if (wasClosedByUserRef.current) {
966
- clearDraft();
967
- wasClosedByUserRef.current = false;
968
- // Reset manual open flag when dialog is closed
969
- wasManuallyOpenedRef.current = false;
970
- }
971
- }
972
- }, [open, persistenceKey, persistOpenState, setPersistedOpen, clearDraft]);
973
-
974
- // Note: We do NOT automatically clear the draft when dialog closes
975
- // The draft should only be cleared on explicit user actions (e.g., form submit success)
976
- // This allows the dialog to restore its state after tab switches
977
- const internalRef = useRef<HTMLDialogElement>(null);
978
-
979
- // Use the dialogRef from context, or fall back to internal ref or forwarded ref
980
- const actualDialogRef = dialogRef.current ? dialogRef : (ref ? (ref as React.RefObject<HTMLDialogElement>) : internalRef);
981
-
982
- // Default to 80% viewport height if no height constraint is provided
983
- // This allows the dialog to grow to 80% before enabling scrolling
984
- const effectiveMaxHeightPercent = maxHeightPercent ?? (maxHeight ? undefined : 80);
985
-
986
- // Determine if we have a height constraint that requires flex layout
987
- const hasHeightConstraint = Boolean(effectiveMaxHeightPercent || maxHeight);
988
-
989
- const smartDimensions = useSmartDimensions({
990
- maxHeightPercent: effectiveMaxHeightPercent,
437
+ const { sizeClass, mergedStyle, hasHeightConstraint } = useDialogDimensions({
438
+ size,
439
+ maxHeightPercent,
991
440
  maxWidthPercent,
992
- maxHeight,
441
+ maxHeight,
993
442
  maxWidth,
994
443
  minHeight,
995
- minWidth
444
+ minWidth,
445
+ enableScrolling,
446
+ style,
996
447
  });
997
448
 
998
449
  // Focus trap
@@ -1003,255 +454,114 @@ const DialogContent = React.forwardRef<HTMLDialogElement, DialogContentProps>(
1003
454
  onEscape: preventCloseOnEscape ? undefined : () => onOpenChange(false),
1004
455
  });
1005
456
 
1006
- // Merge refs
1007
- const mergedRef = useCallback((node: HTMLDialogElement | null) => {
1008
- // Set context dialog ref
1009
- if (dialogRef && 'current' in dialogRef) {
1010
- (dialogRef as React.MutableRefObject<HTMLDialogElement | null>).current = node;
1011
- }
1012
- // Set internal ref
1013
- if (internalRef && 'current' in internalRef) {
1014
- internalRef.current = node;
1015
- }
1016
- // Set focus trap container ref
1017
- if (containerRef && 'current' in containerRef) {
1018
- (containerRef as React.MutableRefObject<HTMLElement | null>).current = node;
1019
- }
1020
- // Handle forwarded ref
1021
- if (typeof ref === 'function') {
1022
- ref(node);
1023
- } else if (ref && 'current' in ref) {
1024
- (ref as React.MutableRefObject<HTMLDialogElement | null>).current = node;
1025
- }
1026
- }, [dialogRef, containerRef, ref]);
1027
-
1028
- // Handle dialog open/close
1029
- useEffect(() => {
1030
- const dialog = dialogRef.current || internalRef.current;
1031
- if (!dialog) return;
1032
-
1033
- if (open) {
1034
- // Log all dialogs in DOM before opening
1035
- const allDialogsBefore = document.querySelectorAll('dialog[role="dialog"]');
1036
- const dialogsBefore = Array.from(allDialogsBefore).map((d) => {
1037
- const dialogEl = d as HTMLDialogElement;
1038
- return {
1039
- persistenceKey: dialogEl.getAttribute('data-persistence-key') || 'NO-KEY',
1040
- open: dialogEl.open,
1041
- isCurrent: d === dialog,
1042
- };
1043
- });
1044
- logger.debug('[Dialog] 🟢 OPENING', {
1045
- persistenceKey,
1046
- dialogsInDOM: dialogsBefore,
1047
- totalDialogs: allDialogsBefore.length,
1048
- });
1049
-
1050
- // Use requestAnimationFrame to ensure DOM is ready
1051
- // Lock was already checked in the earlier useEffect, so we can proceed
1052
- requestAnimationFrame(() => {
1053
- if (dialog && open) {
1054
- // Check if dialog is connected to DOM before attempting to open
1055
- if (!dialog.isConnected) {
1056
- logger.warn('[Dialog] ⚠️ Dialog not connected to DOM, skipping showModal()', { persistenceKey });
1057
- return;
1058
- }
1059
-
1060
- // Check if dialog is already open - if so, skip showModal() call
1061
- // This prevents "Cannot call showModal() on an open non-modal dialog" error
1062
- if (dialog.open) {
1063
- logger.debug('[Dialog] ⏭️ Dialog already open, skipping showModal()', { persistenceKey });
1064
- return;
1065
- }
1066
-
1067
- // Before opening, close any other open dialogs (safety check)
1068
- const allDialogs = document.querySelectorAll('dialog[role="dialog"]');
1069
- allDialogs.forEach((d) => {
1070
- const dialogEl = d as HTMLDialogElement;
1071
- if (dialogEl !== dialog && dialogEl.open) {
1072
- dialogEl.setAttribute('data-duplicate-cleanup', 'true');
1073
- dialogEl.close();
1074
- }
1075
- });
1076
-
1077
- logger.debug('[Dialog] ✅ showModal() called', { persistenceKey });
1078
- dialog.showModal();
1079
- }
1080
- });
1081
- } else {
1082
- // Close dialog before it's removed from DOM
1083
- if (dialog.open) {
1084
- logger.debug('[Dialog] 🔴 CLOSING', { persistenceKey });
1085
- dialog.close();
1086
- // Release the lock
1087
- releaseDialogLock(persistenceKey);
1088
-
1089
- // After closing, check if any other dialogs with persistence are trying to open
1090
- // Only close dialogs that have persistence (data-persistence-key attribute)
1091
- // Non-persistent dialogs should be left alone
1092
- setTimeout(() => {
1093
- const allDialogs = document.querySelectorAll('dialog[role="dialog"]');
1094
- allDialogs.forEach((d) => {
1095
- const dialogEl = d as HTMLDialogElement;
1096
- if (dialogEl !== dialog && dialogEl.open) {
1097
- const otherPersistenceKey = dialogEl.getAttribute('data-persistence-key');
1098
- // Only close dialogs that have persistence (they might auto-open)
1099
- // Non-persistent dialogs are user-controlled and shouldn't be closed
1100
- if (otherPersistenceKey) {
1101
- logger.warn('[Dialog] 🗑️ Closing other persisted dialog after lock release:', {
1102
- persistenceKey,
1103
- otherPersistenceKey,
1104
- });
1105
- dialogEl.setAttribute('data-duplicate-cleanup', 'true');
1106
- dialogEl.close();
1107
- }
1108
- }
1109
- });
1110
- }, 50);
1111
- }
1112
- }
1113
- }, [open, persistenceKey, dialogRef, logger]);
1114
-
1115
- // Handle close event - sync state when dialog is closed externally
1116
- // Also track when dialog is closed by user action (for persistence clearing)
1117
- useEffect(() => {
1118
- const dialog = dialogRef.current || internalRef.current;
1119
- if (!dialog) return;
1120
-
1121
- const handleClose = () => {
1122
- // Check if this close was initiated by the user (via close button)
1123
- const wasUserClosed = dialog.hasAttribute('data-user-closed');
1124
- if (wasUserClosed) {
1125
- dialog.removeAttribute('data-user-closed');
1126
- if (hasInitializedRef.current) {
1127
- wasClosedByUserRef.current = true;
1128
- }
1129
- }
1130
-
1131
- // Ignore duplicate cleanup closes
1132
- const isDuplicateCleanup = dialog.hasAttribute('data-duplicate-cleanup');
1133
- if (isDuplicateCleanup) {
1134
- dialog.removeAttribute('data-duplicate-cleanup');
1135
- return;
1136
- }
1137
-
1138
- if (!dialog.open && open) {
1139
- // Mark as closed by user if this wasn't an auto-open scenario
1140
- if (hasInitializedRef.current && !wasUserClosed) {
1141
- wasClosedByUserRef.current = true;
1142
- }
1143
- onOpenChange(false);
1144
- } else if (!dialog.open && !open && hasInitializedRef.current && wasUserClosed) {
1145
- wasClosedByUserRef.current = true;
1146
- }
1147
- };
1148
-
1149
- dialog.addEventListener('close', handleClose);
1150
- return () => {
1151
- dialog.removeEventListener('close', handleClose);
1152
- };
1153
- }, [open, onOpenChange, dialogRef]);
1154
-
1155
- // Handle cancel event (Escape or backdrop click)
1156
- useEffect(() => {
1157
- const dialog = dialogRef.current || internalRef.current;
1158
- if (!dialog) return;
1159
-
1160
- const handleCancel = (e: Event) => {
1161
- if (preventCloseOnEscape || preventCloseOnOutsideClick) {
1162
- e.preventDefault();
1163
- return;
1164
- }
1165
- // Mark as closed by user and clear persisted state
1166
- wasClosedByUserRef.current = true;
1167
- if (persistenceKey && persistOpenState && clearDraft) {
1168
- clearDraft();
1169
- }
1170
- onOpenChange(false);
1171
- };
1172
-
1173
- dialog.addEventListener('cancel', handleCancel);
1174
- return () => {
1175
- dialog.removeEventListener('cancel', handleCancel);
1176
- };
1177
- }, [preventCloseOnEscape, preventCloseOnOutsideClick, onOpenChange, dialogRef, persistenceKey, persistOpenState, clearDraft]);
1178
-
1179
- // Merge smart dimensions with provided style
1180
- const mergedStyle = React.useMemo(() => {
1181
- if (Object.keys(smartDimensions).length === 0) {
1182
- return style;
1183
- }
457
+ // Merge refs (avoids type assertions; mergeRefs handles RefObject and callback refs)
458
+ const mergedRef = useMemo(
459
+ () => mergeRefs(dialogRef, internalRef, containerRef, ref),
460
+ [dialogRef, containerRef, ref]
461
+ );
1184
462
 
1185
- const finalStyle: React.CSSProperties = { ...smartDimensions, ...style };
1186
-
1187
- // When maxHeightPercent is set and enableScrolling is true, set height to match
1188
- // so the dialog always uses that space (not just as a max constraint)
1189
- if (enableScrolling && maxHeightPercent && typeof maxHeightPercent === 'number' && !maxHeight) {
1190
- const constrainedHeight = Math.min(maxHeightPercent, 95);
1191
- finalStyle.height = `${constrainedHeight}vh`;
1192
- }
1193
-
1194
- if (!maxWidth && !maxWidthPercent) {
1195
- const { maxWidth: _, ...styleWithoutMaxWidth } = finalStyle;
1196
- return styleWithoutMaxWidth;
1197
- }
1198
-
1199
- return finalStyle;
1200
- }, [smartDimensions, style, maxWidth, maxWidthPercent, enableScrolling, maxHeight, maxHeightPercent]);
463
+ useDialogLifecycle({
464
+ dialogRef,
465
+ internalRef,
466
+ open,
467
+ persistenceKey,
468
+ logger,
469
+ onOpenChange,
470
+ markClosedByUser,
471
+ preventCloseOnEscape,
472
+ preventCloseOnOutsideClick,
473
+ persistOpenState,
474
+ clearDraft,
475
+ });
1201
476
 
1202
- // Synchronously check if we can render (must hold lock if open)
1203
477
  const canRender = React.useMemo(() => {
1204
- if (!open) {
1205
- return true; // Can always render when closed
1206
- }
1207
- if (!persistenceKey) {
1208
- return true; // Non-persisted dialogs can always render
1209
- }
1210
- // Use the lockAcquired state which is set by the effect
478
+ if (!open) return true;
479
+ if (!persistenceKey) return true;
1211
480
  return lockAcquired;
1212
481
  }, [open, persistenceKey, lockAcquired]);
1213
482
 
483
+ return {
484
+ mergedRef,
485
+ canRender,
486
+ markClosedByUser,
487
+ sizeClass,
488
+ mergedStyle: mergedStyle ?? {},
489
+ hasHeightConstraint,
490
+ persistenceKey,
491
+ persistOpenState,
492
+ titleId,
493
+ descriptionId,
494
+ open,
495
+ title,
496
+ description,
497
+ };
498
+ }
499
+
500
+ /**
501
+ * DialogContent component
502
+ * The main content container using semantic HTML <dialog> element with enhanced features.
503
+ * Inline style exception (5-styling): viewport-based max/min dimensions from props are applied
504
+ * via the style prop; they cannot be expressed with static Tailwind and are merged with optional consumer style.
505
+ *
506
+ * @param props - Content configuration and styling
507
+ * @param ref - Forwarded ref to the dialog element
508
+ * @returns JSX.Element - The semantic dialog content with overlay and optional close button
509
+ */
510
+ const DialogContent = (React.forwardRef<HTMLDialogElement, DialogContentProps>)(
511
+ function DialogContentInner(props, ref) {
512
+ const {
513
+ className,
514
+ children,
515
+ size = 'md',
516
+ showCloseButton = true,
517
+ maxHeightPercent,
518
+ maxWidthPercent,
519
+ enableScrolling = false,
520
+ maxHeight,
521
+ maxWidth,
522
+ minHeight,
523
+ minWidth,
524
+ style: _styleProp,
525
+ title: _titleProp,
526
+ description: _descriptionProp,
527
+ persistOpenState: _persistOpenStateProp,
528
+ preventCloseOnEscape: _preventCloseOnEscape,
529
+ preventCloseOnOutsideClick: _preventCloseOnOutsideClick,
530
+ ...restProps
531
+ } = props;
532
+
533
+ const state = useDialogContentState(props, ref);
534
+
1214
535
  return (
1215
536
  <DialogPortal>
1216
- {open && canRender && (
537
+ {state.open && state.canRender && (
1217
538
  <dialog
1218
- ref={mergedRef}
539
+ ref={state.mergedRef}
1219
540
  className={cn(
1220
541
  'fixed left-[50%] top-[50%] z-[51] w-full translate-x-[-50%] translate-y-[-50%] border bg-background shadow-lg duration-200',
1221
542
  'animate-in fade-in-0 zoom-in-95 slide-in-from-left-1/2 slide-in-from-top-[48%]',
1222
543
  'sm:rounded-lg',
1223
- // Reset native dialog styles
1224
544
  'm-0 p-0 max-w-none max-h-none border-0 bg-transparent outline-none',
1225
- // When maxWidthPercent is set, use w-full to expand to the constraint (not w-auto)
1226
- // When maxWidthPercent is not set, use w-auto to size to content
1227
545
  (maxWidth || maxWidthPercent) ? 'w-full' : 'w-auto h-auto',
1228
- // Apply our custom styling
1229
546
  'border bg-background shadow-lg',
1230
- // Backdrop styling is handled via core.css only
1231
- // Only apply size classes if not using smart width
1232
- !maxWidth && !maxWidthPercent && sizeClasses[size],
1233
- // Auto size gets special handling
547
+ !maxWidth && !maxWidthPercent && state.sizeClass,
1234
548
  size === 'auto' && 'w-fit max-w-[90vw] sm:max-w-[80vw]',
1235
- // Layout classes: use flex when we have height constraints or enableScrolling is true
1236
- // Flex layout is needed for proper scrolling when height is constrained
1237
- (enableScrolling || hasHeightConstraint) ? 'flex flex-col p-6' : 'grid p-6',
1238
- // Full screen handling
549
+ (enableScrolling || state.hasHeightConstraint) ? 'flex flex-col p-6' : 'grid p-6',
1239
550
  size === 'full' && 'sm:left-[50%] sm:top-[50%] sm:translate-x-[-50%] sm:translate-y-[-50%] left-0 top-0 translate-x-0 translate-y-0 h-full rounded-none sm:h-auto sm:rounded-lg',
1240
- // Overflow handling for scrolling mode or when height is constrained
1241
- (enableScrolling || hasHeightConstraint) && 'overflow-hidden',
551
+ (enableScrolling || state.hasHeightConstraint) && 'overflow-hidden',
1242
552
  className
1243
553
  )}
1244
- style={mergedStyle}
554
+ style={state.mergedStyle}
1245
555
  role="dialog"
1246
556
  aria-modal="true"
1247
- aria-labelledby={titleId}
1248
- aria-describedby={descriptionId}
1249
- title={title}
1250
- aria-description={description}
1251
- data-persistence-key={persistenceKey && persistOpenState ? persistenceKey : undefined}
1252
- {...props}
557
+ aria-labelledby={state.titleId}
558
+ aria-describedby={state.descriptionId}
559
+ title={state.title}
560
+ aria-description={state.description}
561
+ data-persistence-key={state.persistenceKey && state.persistOpenState ? state.persistenceKey : undefined}
562
+ {...restProps}
1253
563
  >
1254
- <DialogCloseContext.Provider value={markClosedByUser}>
564
+ <DialogCloseContext.Provider value={state.markClosedByUser}>
1255
565
  {children}
1256
566
  {showCloseButton && (
1257
567
  <DialogClose />
@@ -1280,48 +590,38 @@ export interface DialogCloseProps extends React.ButtonHTMLAttributes<HTMLButtonE
1280
590
  */
1281
591
  const DialogClose = React.forwardRef<HTMLButtonElement, DialogCloseProps>(
1282
592
  ({ className, asChild = false, children, onClick, ...props }, ref) => {
1283
- // Call all hooks unconditionally at the top level
1284
- // Hooks must be called in the same order on every render
1285
593
  const { onOpenChange, markClosedByUser: contextMarkClosedByUser } = useDialogContext();
1286
- // Prefer DialogContext markClosedByUser (available to all components), fallback to DialogCloseContext (for backwards compatibility)
1287
594
  const dialogCloseContextValue = React.useContext(DialogCloseContext);
1288
- const markClosedByUser = contextMarkClosedByUser || dialogCloseContextValue;
1289
-
1290
- const handleClick = useCallback((e: React.MouseEvent<HTMLElement>) => {
1291
- // Mark dialog as closed by user before calling onOpenChange
1292
- // This ensures the persisted state is cleared when user clicks close button
595
+ const markClosedByUser = contextMarkClosedByUser ?? dialogCloseContextValue;
596
+
597
+ const handleClick = useCallback((e: React.MouseEvent<HTMLButtonElement>) => {
1293
598
  if (markClosedByUser) {
1294
599
  markClosedByUser();
1295
600
  }
1296
-
1297
- onClick?.(e as React.MouseEvent<HTMLButtonElement>);
601
+ onClick?.(e);
1298
602
  onOpenChange(false);
1299
603
  }, [onOpenChange, markClosedByUser, onClick]);
1300
604
 
1301
605
  if (asChild && React.isValidElement(children)) {
1302
- const childElement = children as React.ReactElement<React.HTMLAttributes<HTMLElement>>;
606
+ const childElement = children as React.ReactElement<React.HTMLAttributes<HTMLButtonElement>>;
1303
607
  return React.cloneElement(childElement, {
1304
608
  ref,
1305
609
  onClick: handleClick,
1306
610
  className: cn(className, childElement.props?.className),
1307
611
  ...props,
1308
- } as Partial<React.HTMLAttributes<HTMLElement>>) as React.ReactElement;
612
+ } as React.HTMLAttributes<HTMLButtonElement> & React.RefAttributes<HTMLButtonElement>);
1309
613
  }
1310
614
 
1311
615
  return (
1312
- <button
616
+ <IconButton
1313
617
  ref={ref}
618
+ icon={<X className="size-4" />}
619
+ aria-label="Close"
1314
620
  type="button"
1315
621
  onClick={handleClick}
1316
- className={cn(
1317
- 'absolute right-4 top-4 z-10 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none',
1318
- className
1319
- )}
622
+ className={cn('absolute right-4 top-4 z-10', className)}
1320
623
  {...props}
1321
- >
1322
- <X className="size-4" />
1323
- <span className="sr-only">Close</span>
1324
- </button>
624
+ />
1325
625
  );
1326
626
  }
1327
627
  );
@@ -1337,11 +637,7 @@ const DialogHeader = ({
1337
637
  ...props
1338
638
  }: DialogHeaderProps) => (
1339
639
  <header
1340
- className={cn(
1341
- 'flex flex-col space-y-1.5 text-center sm:text-left mb-4',
1342
- sticky && 'sticky top-0 z-10 bg-background',
1343
- className
1344
- )}
640
+ className={cn('dialog-header', sticky && 'sticky top-0 z-10 bg-background', className)}
1345
641
  {...props}
1346
642
  />
1347
643
  );
@@ -1361,6 +657,7 @@ const DialogBody = ({
1361
657
  children,
1362
658
  ...props
1363
659
  }: DialogBodyProps) => {
660
+ // Inline style exception (5-styling): maxHeight and style from props; no fixed token set.
1364
661
  const mergedStyle = React.useMemo(() => {
1365
662
  return {
1366
663
  ...(maxHeight && { maxHeight }),
@@ -1428,14 +725,14 @@ const DialogBody = ({
1428
725
  {...props}
1429
726
  >
1430
727
  {processedHtmlContent ? (
1431
- <p
728
+ <p
1432
729
  dangerouslySetInnerHTML={{ __html: processedHtmlContent }}
1433
- className="prose prose-sm max-w-none"
730
+ className="dialog-body-html-content"
1434
731
  />
1435
732
  ) : (
1436
733
  <>
1437
734
  {hasHtmlContent && !processedHtmlContent && (
1438
- <p className="text-acc-500 mb-2">
735
+ <p className="dialog-body-fallback">
1439
736
  No HTML content processed. Showing children instead.
1440
737
  </p>
1441
738
  )}
@@ -1457,12 +754,7 @@ const DialogFooter = ({
1457
754
  ...props
1458
755
  }: DialogFooterProps) => (
1459
756
  <footer
1460
- className={cn(
1461
- !className && 'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
1462
- 'mt-4',
1463
- sticky && 'sticky bottom-0 z-10 bg-background',
1464
- className
1465
- )}
757
+ className={cn('dialog-footer', sticky && 'sticky bottom-0 z-10 bg-background', className)}
1466
758
  {...props}
1467
759
  />
1468
760
  );