@prenta/admin 2.6.0 → 2.7.0

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 (801) hide show
  1. package/CHANGELOG.md +104 -0
  2. package/design-system/tokens.css +179 -85
  3. package/design-system/tokens.json +276 -85
  4. package/design-system/tokens.scss +148 -44
  5. package/design-system/tokens.tailwind.cjs +50 -1
  6. package/dist/AdminRoot.d.ts.map +1 -1
  7. package/dist/AdminRoot.js +42 -20
  8. package/dist/AdminRoot.js.map +1 -1
  9. package/dist/LoginRoot.d.ts.map +1 -1
  10. package/dist/LoginRoot.js +2 -1
  11. package/dist/LoginRoot.js.map +1 -1
  12. package/dist/__tests__/components/author-avatar.test.js +3 -17
  13. package/dist/__tests__/components/author-avatar.test.js.map +1 -1
  14. package/dist/__tests__/components/notification-bell.test.js +86 -15
  15. package/dist/__tests__/components/notification-bell.test.js.map +1 -1
  16. package/dist/__tests__/components/responsive-tiers.render.test.d.ts +2 -0
  17. package/dist/__tests__/components/responsive-tiers.render.test.d.ts.map +1 -0
  18. package/dist/__tests__/components/responsive-tiers.render.test.js +313 -0
  19. package/dist/__tests__/components/responsive-tiers.render.test.js.map +1 -0
  20. package/dist/__tests__/components/serp-preview.render.test.js +6 -3
  21. package/dist/__tests__/components/serp-preview.render.test.js.map +1 -1
  22. package/dist/__tests__/components/shell-primitives.render.test.d.ts +2 -0
  23. package/dist/__tests__/components/shell-primitives.render.test.d.ts.map +1 -0
  24. package/dist/__tests__/components/shell-primitives.render.test.js +716 -0
  25. package/dist/__tests__/components/shell-primitives.render.test.js.map +1 -0
  26. package/dist/__tests__/components/theme-provider-accent.test.js +49 -7
  27. package/dist/__tests__/components/theme-provider-accent.test.js.map +1 -1
  28. package/dist/__tests__/components/toggle.test.js +10 -3
  29. package/dist/__tests__/components/toggle.test.js.map +1 -1
  30. package/dist/__tests__/components/ui-primitives.test.js +3 -7
  31. package/dist/__tests__/components/ui-primitives.test.js.map +1 -1
  32. package/dist/__tests__/layout/build-nav-items.test.js +1 -1
  33. package/dist/__tests__/layout/build-nav-items.test.js.map +1 -1
  34. package/dist/__tests__/layout/shell-nav.render.test.d.ts +2 -0
  35. package/dist/__tests__/layout/shell-nav.render.test.d.ts.map +1 -0
  36. package/dist/__tests__/layout/shell-nav.render.test.js +736 -0
  37. package/dist/__tests__/layout/shell-nav.render.test.js.map +1 -0
  38. package/dist/__tests__/layout/sidebar-forms-submenu.render.test.js +8 -4
  39. package/dist/__tests__/layout/sidebar-forms-submenu.render.test.js.map +1 -1
  40. package/dist/__tests__/layout/sidebar-groups.render.test.d.ts +2 -0
  41. package/dist/__tests__/layout/sidebar-groups.render.test.d.ts.map +1 -0
  42. package/dist/__tests__/layout/sidebar-groups.render.test.js +56 -0
  43. package/dist/__tests__/layout/sidebar-groups.render.test.js.map +1 -0
  44. package/dist/__tests__/layout/sidebar-menus.render.test.d.ts +2 -0
  45. package/dist/__tests__/layout/sidebar-menus.render.test.d.ts.map +1 -0
  46. package/dist/__tests__/layout/sidebar-menus.render.test.js +82 -0
  47. package/dist/__tests__/layout/sidebar-menus.render.test.js.map +1 -0
  48. package/dist/__tests__/layout/sidebar-plugin-nav.test.js +1 -1
  49. package/dist/__tests__/layout/sidebar-plugin-nav.test.js.map +1 -1
  50. package/dist/__tests__/layout/sidebar-posts.test.js +48 -38
  51. package/dist/__tests__/layout/sidebar-posts.test.js.map +1 -1
  52. package/dist/__tests__/layout/sidebar-submenu.render.test.js +26 -10
  53. package/dist/__tests__/layout/sidebar-submenu.render.test.js.map +1 -1
  54. package/dist/__tests__/layout/topbar.render.test.d.ts +2 -0
  55. package/dist/__tests__/layout/topbar.render.test.d.ts.map +1 -0
  56. package/dist/__tests__/layout/topbar.render.test.js +406 -0
  57. package/dist/__tests__/layout/topbar.render.test.js.map +1 -0
  58. package/dist/__tests__/lib/api.test.js +2 -0
  59. package/dist/__tests__/lib/api.test.js.map +1 -1
  60. package/dist/__tests__/lib/job-drawer.test.d.ts +2 -0
  61. package/dist/__tests__/lib/job-drawer.test.d.ts.map +1 -0
  62. package/dist/__tests__/lib/job-drawer.test.js +393 -0
  63. package/dist/__tests__/lib/job-drawer.test.js.map +1 -0
  64. package/dist/__tests__/lib/job-view.test.d.ts +2 -0
  65. package/dist/__tests__/lib/job-view.test.d.ts.map +1 -0
  66. package/dist/__tests__/lib/job-view.test.js +387 -0
  67. package/dist/__tests__/lib/job-view.test.js.map +1 -0
  68. package/dist/__tests__/lib/jobs-service.test.d.ts +2 -0
  69. package/dist/__tests__/lib/jobs-service.test.d.ts.map +1 -0
  70. package/dist/__tests__/lib/jobs-service.test.js +88 -0
  71. package/dist/__tests__/lib/jobs-service.test.js.map +1 -0
  72. package/dist/__tests__/router/admin-route-order.test.d.ts +2 -0
  73. package/dist/__tests__/router/admin-route-order.test.d.ts.map +1 -0
  74. package/dist/__tests__/router/admin-route-order.test.js +36 -0
  75. package/dist/__tests__/router/admin-route-order.test.js.map +1 -0
  76. package/dist/__tests__/styles/ai-gradient-contrast.test.d.ts +2 -0
  77. package/dist/__tests__/styles/ai-gradient-contrast.test.d.ts.map +1 -0
  78. package/dist/__tests__/styles/ai-gradient-contrast.test.js +110 -0
  79. package/dist/__tests__/styles/ai-gradient-contrast.test.js.map +1 -0
  80. package/dist/__tests__/styles/breakpoint-order.test.d.ts +2 -0
  81. package/dist/__tests__/styles/breakpoint-order.test.d.ts.map +1 -0
  82. package/dist/__tests__/styles/breakpoint-order.test.js +52 -0
  83. package/dist/__tests__/styles/breakpoint-order.test.js.map +1 -0
  84. package/dist/__tests__/styles/responsive-layer.test.js +303 -12
  85. package/dist/__tests__/styles/responsive-layer.test.js.map +1 -1
  86. package/dist/__tests__/styles/tailwind-preset.test.d.ts +2 -0
  87. package/dist/__tests__/styles/tailwind-preset.test.d.ts.map +1 -0
  88. package/dist/__tests__/styles/tailwind-preset.test.js +147 -0
  89. package/dist/__tests__/styles/tailwind-preset.test.js.map +1 -0
  90. package/dist/__tests__/styles/theme-token-scope.test.js +9 -3
  91. package/dist/__tests__/styles/theme-token-scope.test.js.map +1 -1
  92. package/dist/__tests__/styles/tiers.test.d.ts +2 -0
  93. package/dist/__tests__/styles/tiers.test.d.ts.map +1 -0
  94. package/dist/__tests__/styles/tiers.test.js +121 -0
  95. package/dist/__tests__/styles/tiers.test.js.map +1 -0
  96. package/dist/__tests__/styles/tokens-drift.test.js +57 -6
  97. package/dist/__tests__/styles/tokens-drift.test.js.map +1 -1
  98. package/dist/__tests__/views/admin-lazy-route.render.test.js +27 -1
  99. package/dist/__tests__/views/admin-lazy-route.render.test.js.map +1 -1
  100. package/dist/__tests__/views/ai-settings.render.test.js +4 -4
  101. package/dist/__tests__/views/appearance-menus-route.render.test.d.ts +2 -0
  102. package/dist/__tests__/views/appearance-menus-route.render.test.d.ts.map +1 -0
  103. package/dist/__tests__/views/appearance-menus-route.render.test.js +50 -0
  104. package/dist/__tests__/views/appearance-menus-route.render.test.js.map +1 -0
  105. package/dist/__tests__/views/appearance-settings.render.test.js +8 -6
  106. package/dist/__tests__/views/appearance-settings.render.test.js.map +1 -1
  107. package/dist/__tests__/views/appearance-theme-provider.render.test.d.ts +2 -0
  108. package/dist/__tests__/views/appearance-theme-provider.render.test.d.ts.map +1 -0
  109. package/dist/__tests__/views/appearance-theme-provider.render.test.js +286 -0
  110. package/dist/__tests__/views/appearance-theme-provider.render.test.js.map +1 -0
  111. package/dist/__tests__/views/dashboard.test.js +37 -0
  112. package/dist/__tests__/views/dashboard.test.js.map +1 -1
  113. package/dist/__tests__/views/document-edit-navigation.render.test.d.ts +2 -0
  114. package/dist/__tests__/views/document-edit-navigation.render.test.d.ts.map +1 -0
  115. package/dist/__tests__/views/document-edit-navigation.render.test.js +101 -0
  116. package/dist/__tests__/views/document-edit-navigation.render.test.js.map +1 -0
  117. package/dist/__tests__/views/document-edit-routes.render.test.d.ts +2 -0
  118. package/dist/__tests__/views/document-edit-routes.render.test.d.ts.map +1 -0
  119. package/dist/__tests__/views/document-edit-routes.render.test.js +113 -0
  120. package/dist/__tests__/views/document-edit-routes.render.test.js.map +1 -0
  121. package/dist/__tests__/views/email-settings.render.test.js +2 -2
  122. package/dist/__tests__/views/form-entries.render.test.js +15 -0
  123. package/dist/__tests__/views/form-entries.render.test.js.map +1 -1
  124. package/dist/__tests__/views/form-submissions.render.test.js +18 -0
  125. package/dist/__tests__/views/form-submissions.render.test.js.map +1 -1
  126. package/dist/__tests__/views/forms-list.render.test.js +12 -0
  127. package/dist/__tests__/views/forms-list.render.test.js.map +1 -1
  128. package/dist/__tests__/views/general-settings.render.test.js +26 -10
  129. package/dist/__tests__/views/general-settings.render.test.js.map +1 -1
  130. package/dist/__tests__/views/jobs-drawer.render.test.d.ts +2 -0
  131. package/dist/__tests__/views/jobs-drawer.render.test.d.ts.map +1 -0
  132. package/dist/__tests__/views/jobs-drawer.render.test.js +908 -0
  133. package/dist/__tests__/views/jobs-drawer.render.test.js.map +1 -0
  134. package/dist/__tests__/views/jobs-list.render.test.d.ts +2 -0
  135. package/dist/__tests__/views/jobs-list.render.test.d.ts.map +1 -0
  136. package/dist/__tests__/views/jobs-list.render.test.js +695 -0
  137. package/dist/__tests__/views/jobs-list.render.test.js.map +1 -0
  138. package/dist/__tests__/views/kpi-grid.test.d.ts +2 -0
  139. package/dist/__tests__/views/kpi-grid.test.d.ts.map +1 -0
  140. package/dist/__tests__/views/kpi-grid.test.js +26 -0
  141. package/dist/__tests__/views/kpi-grid.test.js.map +1 -0
  142. package/dist/__tests__/views/mcp-connect.render.test.js +1 -0
  143. package/dist/__tests__/views/mcp-connect.render.test.js.map +1 -1
  144. package/dist/__tests__/views/post-section-editor.test.js +3 -1
  145. package/dist/__tests__/views/post-section-editor.test.js.map +1 -1
  146. package/dist/__tests__/views/script-tags.render.test.js +25 -0
  147. package/dist/__tests__/views/script-tags.render.test.js.map +1 -1
  148. package/dist/__tests__/views/security-settings.render.test.js +9 -8
  149. package/dist/__tests__/views/security-settings.render.test.js.map +1 -1
  150. package/dist/__tests__/views/seo-settings.render.test.js +7 -7
  151. package/dist/__tests__/views/seo-settings.render.test.js.map +1 -1
  152. package/dist/__tests__/views/settings-save-bar.render.test.d.ts +2 -0
  153. package/dist/__tests__/views/settings-save-bar.render.test.d.ts.map +1 -0
  154. package/dist/__tests__/views/settings-save-bar.render.test.js +68 -0
  155. package/dist/__tests__/views/settings-save-bar.render.test.js.map +1 -0
  156. package/dist/__tests__/views/settings-save-footer.render.test.js +23 -15
  157. package/dist/__tests__/views/settings-save-footer.render.test.js.map +1 -1
  158. package/dist/__tests__/views/settings-tabs.render.test.d.ts +2 -0
  159. package/dist/__tests__/views/settings-tabs.render.test.d.ts.map +1 -0
  160. package/dist/__tests__/views/settings-tabs.render.test.js +319 -0
  161. package/dist/__tests__/views/settings-tabs.render.test.js.map +1 -0
  162. package/dist/__tests__/views/updates-tab.render.test.js +4 -3
  163. package/dist/__tests__/views/updates-tab.render.test.js.map +1 -1
  164. package/dist/components/Breadcrumbs.d.ts +8 -1
  165. package/dist/components/Breadcrumbs.d.ts.map +1 -1
  166. package/dist/components/Breadcrumbs.js +6 -0
  167. package/dist/components/Breadcrumbs.js.map +1 -1
  168. package/dist/components/CommandPalette.d.ts.map +1 -1
  169. package/dist/components/CommandPalette.js +40 -4
  170. package/dist/components/CommandPalette.js.map +1 -1
  171. package/dist/components/LocaleSwitcher.d.ts.map +1 -1
  172. package/dist/components/LocaleSwitcher.js +1 -1
  173. package/dist/components/LocaleSwitcher.js.map +1 -1
  174. package/dist/components/NotificationBell.d.ts.map +1 -1
  175. package/dist/components/NotificationBell.js +44 -3
  176. package/dist/components/NotificationBell.js.map +1 -1
  177. package/dist/components/OfflineStatus.js +1 -1
  178. package/dist/components/OfflineStatus.js.map +1 -1
  179. package/dist/components/PresenceIndicator.d.ts.map +1 -1
  180. package/dist/components/PresenceIndicator.js +2 -1
  181. package/dist/components/PresenceIndicator.js.map +1 -1
  182. package/dist/components/ReflowMeter.js +2 -2
  183. package/dist/components/ReflowMeter.js.map +1 -1
  184. package/dist/components/SEOPanel.js +2 -2
  185. package/dist/components/SEOPanel.js.map +1 -1
  186. package/dist/components/ThemeProvider.d.ts +24 -4
  187. package/dist/components/ThemeProvider.d.ts.map +1 -1
  188. package/dist/components/ThemeProvider.js +80 -22
  189. package/dist/components/ThemeProvider.js.map +1 -1
  190. package/dist/components/TipTapEditor.d.ts.map +1 -1
  191. package/dist/components/TipTapEditor.js +3 -4
  192. package/dist/components/TipTapEditor.js.map +1 -1
  193. package/dist/components/forms/EmbedPanel.js +1 -1
  194. package/dist/components/forms/EmbedPanel.js.map +1 -1
  195. package/dist/components/forms/FormEntryDetailPane.js +6 -1
  196. package/dist/components/forms/FormEntryDetailPane.js.map +1 -1
  197. package/dist/components/forms/ShortcodeGlossary.d.ts.map +1 -1
  198. package/dist/components/forms/ShortcodeGlossary.js +6 -1
  199. package/dist/components/forms/ShortcodeGlossary.js.map +1 -1
  200. package/dist/components/forms/primitives.d.ts +15 -5
  201. package/dist/components/forms/primitives.d.ts.map +1 -1
  202. package/dist/components/forms/primitives.js +11 -11
  203. package/dist/components/forms/primitives.js.map +1 -1
  204. package/dist/components/pages/tag-badges.d.ts +2 -1
  205. package/dist/components/pages/tag-badges.d.ts.map +1 -1
  206. package/dist/components/pages/tag-badges.js +4 -3
  207. package/dist/components/pages/tag-badges.js.map +1 -1
  208. package/dist/components/seo/PageAuditPanel.js +2 -2
  209. package/dist/components/seo/PageAuditPanel.js.map +1 -1
  210. package/dist/components/seo/SerpPreview.d.ts +4 -4
  211. package/dist/components/seo/SerpPreview.js +1 -1
  212. package/dist/components/seo/SerpPreview.js.map +1 -1
  213. package/dist/components/seo/primitives.js +2 -2
  214. package/dist/components/seo/primitives.js.map +1 -1
  215. package/dist/components/ui/AuthorAvatar.d.ts +4 -4
  216. package/dist/components/ui/AuthorAvatar.d.ts.map +1 -1
  217. package/dist/components/ui/AuthorAvatar.js +11 -10
  218. package/dist/components/ui/AuthorAvatar.js.map +1 -1
  219. package/dist/components/ui/Badge.d.ts +1 -1
  220. package/dist/components/ui/Badge.js +1 -1
  221. package/dist/components/ui/Badge.js.map +1 -1
  222. package/dist/components/ui/Breadcrumb.d.ts +27 -0
  223. package/dist/components/ui/Breadcrumb.d.ts.map +1 -0
  224. package/dist/components/ui/Breadcrumb.js +41 -0
  225. package/dist/components/ui/Breadcrumb.js.map +1 -0
  226. package/dist/components/ui/Button.d.ts +2 -2
  227. package/dist/components/ui/Button.d.ts.map +1 -1
  228. package/dist/components/ui/Button.js +31 -16
  229. package/dist/components/ui/Button.js.map +1 -1
  230. package/dist/components/ui/Card.d.ts +8 -4
  231. package/dist/components/ui/Card.d.ts.map +1 -1
  232. package/dist/components/ui/Card.js +21 -13
  233. package/dist/components/ui/Card.js.map +1 -1
  234. package/dist/components/ui/CardListControls.d.ts +34 -0
  235. package/dist/components/ui/CardListControls.d.ts.map +1 -0
  236. package/dist/components/ui/CardListControls.js +25 -0
  237. package/dist/components/ui/CardListControls.js.map +1 -0
  238. package/dist/components/ui/CountChip.d.ts +20 -0
  239. package/dist/components/ui/CountChip.d.ts.map +1 -0
  240. package/dist/components/ui/CountChip.js +48 -0
  241. package/dist/components/ui/CountChip.js.map +1 -0
  242. package/dist/components/ui/FilterPill.d.ts +2 -1
  243. package/dist/components/ui/FilterPill.d.ts.map +1 -1
  244. package/dist/components/ui/FilterPill.js +3 -2
  245. package/dist/components/ui/FilterPill.js.map +1 -1
  246. package/dist/components/ui/Input.d.ts +1 -1
  247. package/dist/components/ui/InspectorPane.d.ts +1 -1
  248. package/dist/components/ui/InspectorPane.d.ts.map +1 -1
  249. package/dist/components/ui/InspectorPane.js +15 -4
  250. package/dist/components/ui/InspectorPane.js.map +1 -1
  251. package/dist/components/ui/Modal.d.ts.map +1 -1
  252. package/dist/components/ui/Modal.js +10 -2
  253. package/dist/components/ui/Modal.js.map +1 -1
  254. package/dist/components/ui/PageHeader.d.ts +28 -0
  255. package/dist/components/ui/PageHeader.d.ts.map +1 -0
  256. package/dist/components/ui/PageHeader.js +18 -0
  257. package/dist/components/ui/PageHeader.js.map +1 -0
  258. package/dist/components/ui/RowActions.d.ts +5 -0
  259. package/dist/components/ui/RowActions.d.ts.map +1 -1
  260. package/dist/components/ui/RowActions.js +7 -2
  261. package/dist/components/ui/RowActions.js.map +1 -1
  262. package/dist/components/ui/Segmented.d.ts +21 -0
  263. package/dist/components/ui/Segmented.d.ts.map +1 -0
  264. package/dist/components/ui/Segmented.js +41 -0
  265. package/dist/components/ui/Segmented.js.map +1 -0
  266. package/dist/components/ui/Toaster.d.ts +12 -0
  267. package/dist/components/ui/Toaster.d.ts.map +1 -0
  268. package/dist/components/ui/Toaster.js +103 -0
  269. package/dist/components/ui/Toaster.js.map +1 -0
  270. package/dist/components/ui/Toggle.d.ts +4 -3
  271. package/dist/components/ui/Toggle.d.ts.map +1 -1
  272. package/dist/components/ui/Toggle.js +16 -12
  273. package/dist/components/ui/Toggle.js.map +1 -1
  274. package/dist/components/ui/UnderlineTabs.d.ts +56 -0
  275. package/dist/components/ui/UnderlineTabs.d.ts.map +1 -0
  276. package/dist/components/ui/UnderlineTabs.js +205 -0
  277. package/dist/components/ui/UnderlineTabs.js.map +1 -0
  278. package/dist/components/ui/UnsavedBar.d.ts +25 -0
  279. package/dist/components/ui/UnsavedBar.d.ts.map +1 -0
  280. package/dist/components/ui/UnsavedBar.js +22 -0
  281. package/dist/components/ui/UnsavedBar.js.map +1 -0
  282. package/dist/components/ui/admin-links.d.ts +11 -0
  283. package/dist/components/ui/admin-links.d.ts.map +1 -0
  284. package/dist/components/ui/admin-links.js +10 -0
  285. package/dist/components/ui/admin-links.js.map +1 -0
  286. package/dist/components/ui/card-table.d.ts +43 -0
  287. package/dist/components/ui/card-table.d.ts.map +1 -0
  288. package/dist/components/ui/card-table.js +31 -0
  289. package/dist/components/ui/card-table.js.map +1 -0
  290. package/dist/components/virtual-window.d.ts +7 -0
  291. package/dist/components/virtual-window.d.ts.map +1 -1
  292. package/dist/components/virtual-window.js +5 -2
  293. package/dist/components/virtual-window.js.map +1 -1
  294. package/dist/fields/RelationshipField.js +1 -1
  295. package/dist/fields/RelationshipField.js.map +1 -1
  296. package/dist/files/dm-mono-latin-400-normal.woff +0 -0
  297. package/dist/files/dm-mono-latin-400-normal.woff2 +0 -0
  298. package/dist/files/dm-mono-latin-500-normal.woff +0 -0
  299. package/dist/files/dm-mono-latin-500-normal.woff2 +0 -0
  300. package/dist/files/dm-mono-latin-ext-400-normal.woff +0 -0
  301. package/dist/files/dm-mono-latin-ext-400-normal.woff2 +0 -0
  302. package/dist/files/dm-mono-latin-ext-500-normal.woff +0 -0
  303. package/dist/files/dm-mono-latin-ext-500-normal.woff2 +0 -0
  304. package/dist/files/dm-sans-latin-400-normal.woff +0 -0
  305. package/dist/files/dm-sans-latin-400-normal.woff2 +0 -0
  306. package/dist/files/dm-sans-latin-500-normal.woff +0 -0
  307. package/dist/files/dm-sans-latin-500-normal.woff2 +0 -0
  308. package/dist/files/dm-sans-latin-600-normal.woff +0 -0
  309. package/dist/files/dm-sans-latin-600-normal.woff2 +0 -0
  310. package/dist/files/dm-sans-latin-700-normal.woff +0 -0
  311. package/dist/files/dm-sans-latin-700-normal.woff2 +0 -0
  312. package/dist/files/dm-sans-latin-ext-400-normal.woff +0 -0
  313. package/dist/files/dm-sans-latin-ext-400-normal.woff2 +0 -0
  314. package/dist/files/dm-sans-latin-ext-500-normal.woff +0 -0
  315. package/dist/files/dm-sans-latin-ext-500-normal.woff2 +0 -0
  316. package/dist/files/dm-sans-latin-ext-600-normal.woff +0 -0
  317. package/dist/files/dm-sans-latin-ext-600-normal.woff2 +0 -0
  318. package/dist/files/dm-sans-latin-ext-700-normal.woff +0 -0
  319. package/dist/files/dm-sans-latin-ext-700-normal.woff2 +0 -0
  320. package/dist/hooks/useAppearanceBootstrap.d.ts.map +1 -1
  321. package/dist/hooks/useAppearanceBootstrap.js +9 -3
  322. package/dist/hooks/useAppearanceBootstrap.js.map +1 -1
  323. package/dist/index.d.ts +15 -0
  324. package/dist/index.d.ts.map +1 -1
  325. package/dist/index.js +8 -0
  326. package/dist/index.js.map +1 -1
  327. package/dist/layout/Header.d.ts +14 -5
  328. package/dist/layout/Header.d.ts.map +1 -1
  329. package/dist/layout/Header.js +78 -13
  330. package/dist/layout/Header.js.map +1 -1
  331. package/dist/layout/Layout.d.ts +5 -3
  332. package/dist/layout/Layout.d.ts.map +1 -1
  333. package/dist/layout/Layout.js +45 -54
  334. package/dist/layout/Layout.js.map +1 -1
  335. package/dist/layout/MobileTabBar.d.ts +20 -0
  336. package/dist/layout/MobileTabBar.d.ts.map +1 -0
  337. package/dist/layout/MobileTabBar.js +29 -0
  338. package/dist/layout/MobileTabBar.js.map +1 -0
  339. package/dist/layout/Sidebar.d.ts +8 -26
  340. package/dist/layout/Sidebar.d.ts.map +1 -1
  341. package/dist/layout/Sidebar.js +309 -345
  342. package/dist/layout/Sidebar.js.map +1 -1
  343. package/dist/layout/nav-config.d.ts +101 -0
  344. package/dist/layout/nav-config.d.ts.map +1 -0
  345. package/dist/layout/nav-config.js +297 -0
  346. package/dist/layout/nav-config.js.map +1 -0
  347. package/dist/layout/primitives/AdminShell.d.ts +10 -2
  348. package/dist/layout/primitives/AdminShell.d.ts.map +1 -1
  349. package/dist/layout/primitives/AdminShell.js +114 -6
  350. package/dist/layout/primitives/AdminShell.js.map +1 -1
  351. package/dist/layout/topbar-classes.d.ts +8 -0
  352. package/dist/layout/topbar-classes.d.ts.map +1 -0
  353. package/dist/layout/topbar-classes.js +8 -0
  354. package/dist/layout/topbar-classes.js.map +1 -0
  355. package/dist/layout/use-nav-items.d.ts +10 -0
  356. package/dist/layout/use-nav-items.d.ts.map +1 -0
  357. package/dist/layout/use-nav-items.js +39 -0
  358. package/dist/layout/use-nav-items.js.map +1 -0
  359. package/dist/lib/api.d.ts +6 -0
  360. package/dist/lib/api.d.ts.map +1 -1
  361. package/dist/lib/api.js +1 -1
  362. package/dist/lib/api.js.map +1 -1
  363. package/dist/lib/appearance-api.d.ts +3 -1
  364. package/dist/lib/appearance-api.d.ts.map +1 -1
  365. package/dist/lib/appearance-api.js +1 -0
  366. package/dist/lib/appearance-api.js.map +1 -1
  367. package/dist/lib/job-drawer.d.ts +128 -0
  368. package/dist/lib/job-drawer.d.ts.map +1 -0
  369. package/dist/lib/job-drawer.js +351 -0
  370. package/dist/lib/job-drawer.js.map +1 -0
  371. package/dist/lib/job-view.d.ts +187 -0
  372. package/dist/lib/job-view.d.ts.map +1 -0
  373. package/dist/lib/job-view.js +363 -0
  374. package/dist/lib/job-view.js.map +1 -0
  375. package/dist/lib/jobs-service.d.ts +134 -0
  376. package/dist/lib/jobs-service.d.ts.map +1 -0
  377. package/dist/lib/jobs-service.js +179 -0
  378. package/dist/lib/jobs-service.js.map +1 -0
  379. package/dist/lib/sheet-focus.d.ts +24 -0
  380. package/dist/lib/sheet-focus.d.ts.map +1 -0
  381. package/dist/lib/sheet-focus.js +61 -0
  382. package/dist/lib/sheet-focus.js.map +1 -0
  383. package/dist/lib/tiers.d.ts +30 -0
  384. package/dist/lib/tiers.d.ts.map +1 -0
  385. package/dist/lib/tiers.js +43 -0
  386. package/dist/lib/tiers.js.map +1 -0
  387. package/dist/prenta-admin.css +1 -1
  388. package/dist/router/admin-routes.d.ts.map +1 -1
  389. package/dist/router/admin-routes.js +8 -0
  390. package/dist/router/admin-routes.js.map +1 -1
  391. package/dist/router/index.d.ts +5 -1
  392. package/dist/router/index.d.ts.map +1 -1
  393. package/dist/router/index.js +7 -0
  394. package/dist/router/index.js.map +1 -1
  395. package/dist/views/ApiKeys.js +2 -2
  396. package/dist/views/ApiKeys.js.map +1 -1
  397. package/dist/views/AppearanceChrome.d.ts +6 -0
  398. package/dist/views/AppearanceChrome.d.ts.map +1 -0
  399. package/dist/views/AppearanceChrome.js +25 -0
  400. package/dist/views/AppearanceChrome.js.map +1 -0
  401. package/dist/views/CollectionList.d.ts.map +1 -1
  402. package/dist/views/CollectionList.js +73 -53
  403. package/dist/views/CollectionList.js.map +1 -1
  404. package/dist/views/Dashboard.d.ts.map +1 -1
  405. package/dist/views/Dashboard.js +14 -8
  406. package/dist/views/Dashboard.js.map +1 -1
  407. package/dist/views/DocumentEdit.d.ts +6 -1
  408. package/dist/views/DocumentEdit.d.ts.map +1 -1
  409. package/dist/views/DocumentEdit.js +44 -8
  410. package/dist/views/DocumentEdit.js.map +1 -1
  411. package/dist/views/FormDeliveries.d.ts.map +1 -1
  412. package/dist/views/FormDeliveries.js +7 -1
  413. package/dist/views/FormDeliveries.js.map +1 -1
  414. package/dist/views/FormEntries.d.ts.map +1 -1
  415. package/dist/views/FormEntries.js +11 -2
  416. package/dist/views/FormEntries.js.map +1 -1
  417. package/dist/views/FormSubmissions.d.ts +1 -1
  418. package/dist/views/FormSubmissions.d.ts.map +1 -1
  419. package/dist/views/FormSubmissions.js +15 -4
  420. package/dist/views/FormSubmissions.js.map +1 -1
  421. package/dist/views/Forms.d.ts.map +1 -1
  422. package/dist/views/Forms.js +6 -4
  423. package/dist/views/Forms.js.map +1 -1
  424. package/dist/views/FormsSettings.d.ts +2 -0
  425. package/dist/views/FormsSettings.d.ts.map +1 -0
  426. package/dist/views/FormsSettings.js +37 -0
  427. package/dist/views/FormsSettings.js.map +1 -0
  428. package/dist/views/Jobs/BusyButton.d.ts +15 -0
  429. package/dist/views/Jobs/BusyButton.d.ts.map +1 -0
  430. package/dist/views/Jobs/BusyButton.js +26 -0
  431. package/dist/views/Jobs/BusyButton.js.map +1 -0
  432. package/dist/views/Jobs/CancelJobDialog.d.ts +21 -0
  433. package/dist/views/Jobs/CancelJobDialog.d.ts.map +1 -0
  434. package/dist/views/Jobs/CancelJobDialog.js +66 -0
  435. package/dist/views/Jobs/CancelJobDialog.js.map +1 -0
  436. package/dist/views/Jobs/DrawerActivity.d.ts +16 -0
  437. package/dist/views/Jobs/DrawerActivity.d.ts.map +1 -0
  438. package/dist/views/Jobs/DrawerActivity.js +59 -0
  439. package/dist/views/Jobs/DrawerActivity.js.map +1 -0
  440. package/dist/views/Jobs/DrawerOverview.d.ts +28 -0
  441. package/dist/views/Jobs/DrawerOverview.d.ts.map +1 -0
  442. package/dist/views/Jobs/DrawerOverview.js +32 -0
  443. package/dist/views/Jobs/DrawerOverview.js.map +1 -0
  444. package/dist/views/Jobs/DrawerResults.d.ts +25 -0
  445. package/dist/views/Jobs/DrawerResults.d.ts.map +1 -0
  446. package/dist/views/Jobs/DrawerResults.js +50 -0
  447. package/dist/views/Jobs/DrawerResults.js.map +1 -0
  448. package/dist/views/Jobs/DrawerReview.d.ts +44 -0
  449. package/dist/views/Jobs/DrawerReview.d.ts.map +1 -0
  450. package/dist/views/Jobs/DrawerReview.js +110 -0
  451. package/dist/views/Jobs/DrawerReview.js.map +1 -0
  452. package/dist/views/Jobs/JobDrawer.d.ts +31 -0
  453. package/dist/views/Jobs/JobDrawer.d.ts.map +1 -0
  454. package/dist/views/Jobs/JobDrawer.js +353 -0
  455. package/dist/views/Jobs/JobDrawer.js.map +1 -0
  456. package/dist/views/Jobs/JobsTable.d.ts +19 -0
  457. package/dist/views/Jobs/JobsTable.d.ts.map +1 -0
  458. package/dist/views/Jobs/JobsTable.js +80 -0
  459. package/dist/views/Jobs/JobsTable.js.map +1 -0
  460. package/dist/views/Jobs/WorkerStrip.d.ts +22 -0
  461. package/dist/views/Jobs/WorkerStrip.d.ts.map +1 -0
  462. package/dist/views/Jobs/WorkerStrip.js +63 -0
  463. package/dist/views/Jobs/WorkerStrip.js.map +1 -0
  464. package/dist/views/Jobs/atoms.d.ts +19 -0
  465. package/dist/views/Jobs/atoms.d.ts.map +1 -0
  466. package/dist/views/Jobs/atoms.js +52 -0
  467. package/dist/views/Jobs/atoms.js.map +1 -0
  468. package/dist/views/Jobs/focus.d.ts +23 -0
  469. package/dist/views/Jobs/focus.d.ts.map +1 -0
  470. package/dist/views/Jobs/focus.js +54 -0
  471. package/dist/views/Jobs/focus.js.map +1 -0
  472. package/dist/views/Jobs/icons.d.ts +22 -0
  473. package/dist/views/Jobs/icons.d.ts.map +1 -0
  474. package/dist/views/Jobs/icons.js +23 -0
  475. package/dist/views/Jobs/icons.js.map +1 -0
  476. package/dist/views/Jobs/placeholders.d.ts +6 -0
  477. package/dist/views/Jobs/placeholders.d.ts.map +1 -0
  478. package/dist/views/Jobs/placeholders.js +20 -0
  479. package/dist/views/Jobs/placeholders.js.map +1 -0
  480. package/dist/views/Jobs/useJobDetail.d.ts +21 -0
  481. package/dist/views/Jobs/useJobDetail.d.ts.map +1 -0
  482. package/dist/views/Jobs/useJobDetail.js +39 -0
  483. package/dist/views/Jobs/useJobDetail.js.map +1 -0
  484. package/dist/views/Jobs/useJobsFeed.d.ts +35 -0
  485. package/dist/views/Jobs/useJobsFeed.d.ts.map +1 -0
  486. package/dist/views/Jobs/useJobsFeed.js +231 -0
  487. package/dist/views/Jobs/useJobsFeed.js.map +1 -0
  488. package/dist/views/Jobs/useReviewItems.d.ts +39 -0
  489. package/dist/views/Jobs/useReviewItems.d.ts.map +1 -0
  490. package/dist/views/Jobs/useReviewItems.js +140 -0
  491. package/dist/views/Jobs/useReviewItems.js.map +1 -0
  492. package/dist/views/Jobs.d.ts +10 -1
  493. package/dist/views/Jobs.d.ts.map +1 -1
  494. package/dist/views/Jobs.js +126 -35
  495. package/dist/views/Jobs.js.map +1 -1
  496. package/dist/views/MediaBrowser.d.ts.map +1 -1
  497. package/dist/views/MediaBrowser.js +37 -9
  498. package/dist/views/MediaBrowser.js.map +1 -1
  499. package/dist/views/Pages/PagesListView.d.ts +1 -1
  500. package/dist/views/Pages/PagesListView.d.ts.map +1 -1
  501. package/dist/views/Pages/PagesListView.js +34 -5
  502. package/dist/views/Pages/PagesListView.js.map +1 -1
  503. package/dist/views/Posts/NewPostTypeInstructions.d.ts.map +1 -1
  504. package/dist/views/Posts/NewPostTypeInstructions.js +12 -2
  505. package/dist/views/Posts/NewPostTypeInstructions.js.map +1 -1
  506. package/dist/views/Posts/PostTypesView.d.ts.map +1 -1
  507. package/dist/views/Posts/PostTypesView.js +10 -1
  508. package/dist/views/Posts/PostTypesView.js.map +1 -1
  509. package/dist/views/Posts/PostsListView.d.ts +1 -2
  510. package/dist/views/Posts/PostsListView.d.ts.map +1 -1
  511. package/dist/views/Posts/PostsListView.js +31 -3
  512. package/dist/views/Posts/PostsListView.js.map +1 -1
  513. package/dist/views/SEO.d.ts.map +1 -1
  514. package/dist/views/SEO.js +3 -3
  515. package/dist/views/SEO.js.map +1 -1
  516. package/dist/views/ScriptTags.d.ts.map +1 -1
  517. package/dist/views/ScriptTags.js +20 -15
  518. package/dist/views/ScriptTags.js.map +1 -1
  519. package/dist/views/Settings.d.ts.map +1 -1
  520. package/dist/views/Settings.js +115 -51
  521. package/dist/views/Settings.js.map +1 -1
  522. package/dist/views/SpamFolder.d.ts +2 -0
  523. package/dist/views/SpamFolder.d.ts.map +1 -0
  524. package/dist/views/SpamFolder.js +14 -0
  525. package/dist/views/SpamFolder.js.map +1 -0
  526. package/dist/views/Trash.d.ts +6 -1
  527. package/dist/views/Trash.d.ts.map +1 -1
  528. package/dist/views/Trash.js +7 -2
  529. package/dist/views/Trash.js.map +1 -1
  530. package/dist/views/Users.js +2 -2
  531. package/dist/views/Users.js.map +1 -1
  532. package/dist/views/WidgetsPlaceholder.d.ts +2 -0
  533. package/dist/views/WidgetsPlaceholder.d.ts.map +1 -0
  534. package/dist/views/WidgetsPlaceholder.js +15 -0
  535. package/dist/views/WidgetsPlaceholder.js.map +1 -0
  536. package/dist/views/dashboard/kpi-grid.d.ts +18 -0
  537. package/dist/views/dashboard/kpi-grid.d.ts.map +1 -0
  538. package/dist/views/dashboard/kpi-grid.js +36 -0
  539. package/dist/views/dashboard/kpi-grid.js.map +1 -0
  540. package/dist/views/lazy-views.d.ts +4 -0
  541. package/dist/views/lazy-views.d.ts.map +1 -1
  542. package/dist/views/lazy-views.js +4 -0
  543. package/dist/views/lazy-views.js.map +1 -1
  544. package/dist/views/page-editor/PageTab.js +1 -1
  545. package/dist/views/page-editor/PageTab.js.map +1 -1
  546. package/dist/views/page-editor/ReviewBar.js +1 -1
  547. package/dist/views/page-editor/ReviewBar.js.map +1 -1
  548. package/dist/views/page-editor/VersionHistoryPanel.js +1 -1
  549. package/dist/views/page-editor/VersionHistoryPanel.js.map +1 -1
  550. package/dist/views/seo/AuditTab.js +1 -1
  551. package/dist/views/seo/AuditTab.js.map +1 -1
  552. package/dist/views/seo/OverviewTab.d.ts.map +1 -1
  553. package/dist/views/seo/OverviewTab.js +2 -2
  554. package/dist/views/seo/OverviewTab.js.map +1 -1
  555. package/dist/views/seo/ProposalsTab.js +1 -1
  556. package/dist/views/seo/ProposalsTab.js.map +1 -1
  557. package/dist/views/settings/AISettingsTab.d.ts.map +1 -1
  558. package/dist/views/settings/AISettingsTab.js +4 -2
  559. package/dist/views/settings/AISettingsTab.js.map +1 -1
  560. package/dist/views/settings/AccessProtectionCard.js +1 -1
  561. package/dist/views/settings/AccessProtectionCard.js.map +1 -1
  562. package/dist/views/settings/AdminThemeCard.d.ts.map +1 -1
  563. package/dist/views/settings/AdminThemeCard.js +63 -15
  564. package/dist/views/settings/AdminThemeCard.js.map +1 -1
  565. package/dist/views/settings/AppearanceTab.d.ts.map +1 -1
  566. package/dist/views/settings/AppearanceTab.js +4 -2
  567. package/dist/views/settings/AppearanceTab.js.map +1 -1
  568. package/dist/views/settings/BrandPreviewCard.js +1 -1
  569. package/dist/views/settings/BrandPreviewCard.js.map +1 -1
  570. package/dist/views/settings/BrandVoiceCard.js +1 -1
  571. package/dist/views/settings/BrandVoiceCard.js.map +1 -1
  572. package/dist/views/settings/EmailSettingsTab.d.ts.map +1 -1
  573. package/dist/views/settings/EmailSettingsTab.js +5 -3
  574. package/dist/views/settings/EmailSettingsTab.js.map +1 -1
  575. package/dist/views/settings/GeneralSettingsTab.d.ts.map +1 -1
  576. package/dist/views/settings/GeneralSettingsTab.js +4 -2
  577. package/dist/views/settings/GeneralSettingsTab.js.map +1 -1
  578. package/dist/views/settings/McpConnectTab.d.ts.map +1 -1
  579. package/dist/views/settings/McpConnectTab.js +1 -1
  580. package/dist/views/settings/McpConnectTab.js.map +1 -1
  581. package/dist/views/settings/ProviderConnectionCard.js +1 -1
  582. package/dist/views/settings/ProviderConnectionCard.js.map +1 -1
  583. package/dist/views/settings/SearchDataCard.js +1 -1
  584. package/dist/views/settings/SearchDataCard.js.map +1 -1
  585. package/dist/views/settings/SecurityStatusCard.js +2 -2
  586. package/dist/views/settings/SecurityStatusCard.js.map +1 -1
  587. package/dist/views/settings/SecurityTab.d.ts.map +1 -1
  588. package/dist/views/settings/SecurityTab.js +4 -2
  589. package/dist/views/settings/SecurityTab.js.map +1 -1
  590. package/dist/views/settings/SeoAutopilotCard.js +2 -2
  591. package/dist/views/settings/SeoAutopilotCard.js.map +1 -1
  592. package/dist/views/settings/SeoSettingsTab.d.ts.map +1 -1
  593. package/dist/views/settings/SeoSettingsTab.js +5 -3
  594. package/dist/views/settings/SeoSettingsTab.js.map +1 -1
  595. package/dist/views/settings/SiteIndexStatusCard.js +1 -1
  596. package/dist/views/settings/SiteIndexStatusCard.js.map +1 -1
  597. package/dist/views/settings/SiteThemeCard.js +1 -1
  598. package/dist/views/settings/UpdatesTab.d.ts.map +1 -1
  599. package/dist/views/settings/UpdatesTab.js +6 -5
  600. package/dist/views/settings/UpdatesTab.js.map +1 -1
  601. package/dist/views/settings/components.d.ts +7 -11
  602. package/dist/views/settings/components.d.ts.map +1 -1
  603. package/dist/views/settings/components.js +18 -12
  604. package/dist/views/settings/components.js.map +1 -1
  605. package/dist/views/settings/useAppearanceSettings.d.ts +7 -1
  606. package/dist/views/settings/useAppearanceSettings.d.ts.map +1 -1
  607. package/dist/views/settings/useAppearanceSettings.js +89 -22
  608. package/dist/views/settings/useAppearanceSettings.js.map +1 -1
  609. package/dist/views/users/UserDetailDrawer.js +1 -1
  610. package/dist/views/users/UserDetailDrawer.js.map +1 -1
  611. package/package.json +11 -9
  612. package/src/AdminRoot.tsx +88 -30
  613. package/src/LoginRoot.tsx +2 -1
  614. package/src/__tests__/components/author-avatar.test.tsx +3 -20
  615. package/src/__tests__/components/notification-bell.test.tsx +146 -14
  616. package/src/__tests__/components/responsive-tiers.render.test.tsx +461 -0
  617. package/src/__tests__/components/serp-preview.render.test.tsx +6 -3
  618. package/src/__tests__/components/shell-primitives.render.test.tsx +951 -0
  619. package/src/__tests__/components/theme-provider-accent.test.tsx +83 -8
  620. package/src/__tests__/components/toggle.test.tsx +11 -3
  621. package/src/__tests__/components/ui-primitives.test.tsx +5 -8
  622. package/src/__tests__/layout/build-nav-items.test.ts +1 -1
  623. package/src/__tests__/layout/shell-nav.render.test.tsx +983 -0
  624. package/src/__tests__/layout/sidebar-forms-submenu.render.test.tsx +8 -4
  625. package/src/__tests__/layout/sidebar-groups.render.test.tsx +116 -0
  626. package/src/__tests__/layout/sidebar-menus.render.test.tsx +116 -0
  627. package/src/__tests__/layout/sidebar-plugin-nav.test.ts +1 -1
  628. package/src/__tests__/layout/sidebar-posts.test.ts +48 -40
  629. package/src/__tests__/layout/sidebar-submenu.render.test.tsx +35 -10
  630. package/src/__tests__/layout/topbar.render.test.tsx +464 -0
  631. package/src/__tests__/lib/api.test.ts +2 -0
  632. package/src/__tests__/lib/job-drawer.test.ts +493 -0
  633. package/src/__tests__/lib/job-view.test.ts +437 -0
  634. package/src/__tests__/lib/jobs-service.test.ts +98 -0
  635. package/src/__tests__/router/admin-route-order.test.ts +39 -0
  636. package/src/__tests__/styles/ai-gradient-contrast.test.ts +135 -0
  637. package/src/__tests__/styles/breakpoint-order.test.ts +68 -0
  638. package/src/__tests__/styles/responsive-layer.test.ts +367 -12
  639. package/src/__tests__/styles/tailwind-preset.test.ts +154 -0
  640. package/src/__tests__/styles/theme-token-scope.test.ts +10 -3
  641. package/src/__tests__/styles/tiers.test.ts +136 -0
  642. package/src/__tests__/styles/tokens-drift.test.ts +67 -6
  643. package/src/__tests__/views/admin-lazy-route.render.test.tsx +41 -1
  644. package/src/__tests__/views/ai-settings.render.test.tsx +4 -4
  645. package/src/__tests__/views/appearance-menus-route.render.test.tsx +58 -0
  646. package/src/__tests__/views/appearance-settings.render.test.tsx +10 -8
  647. package/src/__tests__/views/appearance-theme-provider.render.test.tsx +347 -0
  648. package/src/__tests__/views/dashboard.test.tsx +48 -0
  649. package/src/__tests__/views/document-edit-navigation.render.test.tsx +130 -0
  650. package/src/__tests__/views/document-edit-routes.render.test.tsx +130 -0
  651. package/src/__tests__/views/email-settings.render.test.tsx +2 -2
  652. package/src/__tests__/views/form-entries.render.test.tsx +22 -0
  653. package/src/__tests__/views/form-submissions.render.test.tsx +22 -0
  654. package/src/__tests__/views/forms-list.render.test.tsx +13 -0
  655. package/src/__tests__/views/general-settings.render.test.tsx +38 -11
  656. package/src/__tests__/views/jobs-drawer.render.test.tsx +1047 -0
  657. package/src/__tests__/views/jobs-list.render.test.tsx +850 -0
  658. package/src/__tests__/views/kpi-grid.test.ts +29 -0
  659. package/src/__tests__/views/mcp-connect.render.test.tsx +1 -0
  660. package/src/__tests__/views/post-section-editor.test.tsx +5 -1
  661. package/src/__tests__/views/script-tags.render.test.tsx +29 -0
  662. package/src/__tests__/views/security-settings.render.test.tsx +9 -10
  663. package/src/__tests__/views/seo-settings.render.test.tsx +7 -9
  664. package/src/__tests__/views/settings-save-bar.render.test.tsx +91 -0
  665. package/src/__tests__/views/settings-save-footer.render.test.tsx +25 -15
  666. package/src/__tests__/views/settings-tabs.render.test.tsx +371 -0
  667. package/src/__tests__/views/updates-tab.render.test.tsx +4 -3
  668. package/src/components/Breadcrumbs.tsx +7 -1
  669. package/src/components/CommandPalette.tsx +51 -3
  670. package/src/components/LocaleSwitcher.tsx +5 -6
  671. package/src/components/NotificationBell.tsx +74 -18
  672. package/src/components/OfflineStatus.tsx +1 -1
  673. package/src/components/PresenceIndicator.tsx +2 -1
  674. package/src/components/ReflowMeter.tsx +3 -3
  675. package/src/components/SEOPanel.tsx +2 -2
  676. package/src/components/ThemeProvider.tsx +122 -42
  677. package/src/components/TipTapEditor.tsx +3 -4
  678. package/src/components/forms/EmbedPanel.tsx +1 -1
  679. package/src/components/forms/FormEntryDetailPane.tsx +7 -1
  680. package/src/components/forms/ShortcodeGlossary.tsx +7 -1
  681. package/src/components/forms/primitives.tsx +37 -21
  682. package/src/components/pages/tag-badges.tsx +4 -3
  683. package/src/components/seo/PageAuditPanel.tsx +2 -2
  684. package/src/components/seo/SerpPreview.tsx +5 -5
  685. package/src/components/seo/primitives.tsx +2 -2
  686. package/src/components/ui/AuthorAvatar.tsx +11 -10
  687. package/src/components/ui/Badge.tsx +1 -1
  688. package/src/components/ui/Breadcrumb.tsx +109 -0
  689. package/src/components/ui/Button.tsx +40 -15
  690. package/src/components/ui/Card.tsx +35 -32
  691. package/src/components/ui/CardListControls.tsx +90 -0
  692. package/src/components/ui/CountChip.tsx +90 -0
  693. package/src/components/ui/FilterPill.tsx +5 -3
  694. package/src/components/ui/InspectorPane.tsx +21 -7
  695. package/src/components/ui/Modal.tsx +20 -6
  696. package/src/components/ui/PageHeader.tsx +63 -0
  697. package/src/components/ui/RowActions.tsx +7 -2
  698. package/src/components/ui/Segmented.tsx +97 -0
  699. package/src/components/ui/Toaster.tsx +139 -0
  700. package/src/components/ui/Toggle.tsx +18 -14
  701. package/src/components/ui/UnderlineTabs.tsx +373 -0
  702. package/src/components/ui/UnsavedBar.tsx +119 -0
  703. package/src/components/ui/admin-links.tsx +20 -0
  704. package/src/components/ui/card-table.ts +34 -0
  705. package/src/components/virtual-window.tsx +13 -2
  706. package/src/fields/RelationshipField.tsx +1 -1
  707. package/src/hooks/useAppearanceBootstrap.ts +9 -3
  708. package/src/index.ts +19 -0
  709. package/src/layout/Header.tsx +210 -135
  710. package/src/layout/Layout.tsx +61 -108
  711. package/src/layout/MobileTabBar.tsx +92 -0
  712. package/src/layout/Sidebar.tsx +555 -593
  713. package/src/layout/nav-config.tsx +425 -0
  714. package/src/layout/primitives/AdminShell.tsx +154 -6
  715. package/src/layout/topbar-classes.ts +8 -0
  716. package/src/layout/use-nav-items.ts +40 -0
  717. package/src/lib/api.ts +7 -1
  718. package/src/lib/appearance-api.ts +4 -0
  719. package/src/lib/job-drawer.ts +424 -0
  720. package/src/lib/job-view.ts +456 -0
  721. package/src/lib/jobs-service.ts +290 -0
  722. package/src/lib/sheet-focus.ts +71 -0
  723. package/src/lib/tiers.ts +43 -0
  724. package/src/router/admin-routes.ts +8 -0
  725. package/src/router/index.ts +9 -1
  726. package/src/styles/prenta-tokens.css +263 -82
  727. package/src/styles/responsive.css +460 -22
  728. package/src/styles/theme.css +177 -28
  729. package/src/views/ApiKeys.tsx +2 -2
  730. package/src/views/AppearanceChrome.tsx +74 -0
  731. package/src/views/CollectionList.tsx +264 -205
  732. package/src/views/Dashboard.tsx +26 -13
  733. package/src/views/DocumentEdit.tsx +111 -56
  734. package/src/views/FormDeliveries.tsx +30 -17
  735. package/src/views/FormEntries.tsx +28 -20
  736. package/src/views/FormSubmissions.tsx +32 -34
  737. package/src/views/Forms.tsx +55 -34
  738. package/src/views/FormsSettings.tsx +94 -0
  739. package/src/views/Jobs/BusyButton.tsx +56 -0
  740. package/src/views/Jobs/CancelJobDialog.tsx +127 -0
  741. package/src/views/Jobs/DrawerActivity.tsx +142 -0
  742. package/src/views/Jobs/DrawerOverview.tsx +139 -0
  743. package/src/views/Jobs/DrawerResults.tsx +155 -0
  744. package/src/views/Jobs/DrawerReview.tsx +464 -0
  745. package/src/views/Jobs/JobDrawer.tsx +556 -0
  746. package/src/views/Jobs/JobsTable.tsx +260 -0
  747. package/src/views/Jobs/WorkerStrip.tsx +144 -0
  748. package/src/views/Jobs/atoms.tsx +93 -0
  749. package/src/views/Jobs/focus.ts +56 -0
  750. package/src/views/Jobs/icons.tsx +111 -0
  751. package/src/views/Jobs/placeholders.tsx +66 -0
  752. package/src/views/Jobs/useJobDetail.ts +64 -0
  753. package/src/views/Jobs/useJobsFeed.ts +249 -0
  754. package/src/views/Jobs/useReviewItems.ts +187 -0
  755. package/src/views/Jobs.tsx +282 -105
  756. package/src/views/MediaBrowser.tsx +71 -21
  757. package/src/views/Pages/PagesListView.tsx +115 -39
  758. package/src/views/Posts/NewPostTypeInstructions.tsx +15 -10
  759. package/src/views/Posts/PostTypesView.tsx +28 -6
  760. package/src/views/Posts/PostsListView.tsx +130 -40
  761. package/src/views/SEO.tsx +23 -8
  762. package/src/views/ScriptTags.tsx +233 -153
  763. package/src/views/Settings.tsx +215 -256
  764. package/src/views/SpamFolder.tsx +32 -0
  765. package/src/views/Trash.tsx +12 -1
  766. package/src/views/Users.tsx +2 -2
  767. package/src/views/WidgetsPlaceholder.tsx +33 -0
  768. package/src/views/dashboard/kpi-grid.ts +38 -0
  769. package/src/views/lazy-views.tsx +12 -0
  770. package/src/views/page-editor/PageTab.tsx +2 -2
  771. package/src/views/page-editor/ReviewBar.tsx +1 -1
  772. package/src/views/page-editor/VersionHistoryPanel.tsx +1 -1
  773. package/src/views/seo/AuditTab.tsx +1 -1
  774. package/src/views/seo/OverviewTab.tsx +6 -5
  775. package/src/views/seo/ProposalsTab.tsx +1 -1
  776. package/src/views/settings/AISettingsTab.tsx +10 -6
  777. package/src/views/settings/AccessProtectionCard.tsx +1 -1
  778. package/src/views/settings/AdminThemeCard.tsx +228 -35
  779. package/src/views/settings/AppearanceTab.tsx +10 -6
  780. package/src/views/settings/BrandPreviewCard.tsx +1 -1
  781. package/src/views/settings/BrandVoiceCard.tsx +1 -1
  782. package/src/views/settings/EmailSettingsTab.tsx +11 -7
  783. package/src/views/settings/GeneralSettingsTab.tsx +10 -6
  784. package/src/views/settings/McpConnectTab.tsx +2 -1
  785. package/src/views/settings/ProviderConnectionCard.tsx +1 -1
  786. package/src/views/settings/SearchDataCard.tsx +1 -1
  787. package/src/views/settings/SecurityStatusCard.tsx +2 -2
  788. package/src/views/settings/SecurityTab.tsx +10 -6
  789. package/src/views/settings/SeoAutopilotCard.tsx +2 -2
  790. package/src/views/settings/SeoSettingsTab.tsx +13 -9
  791. package/src/views/settings/SiteIndexStatusCard.tsx +1 -1
  792. package/src/views/settings/SiteThemeCard.tsx +1 -1
  793. package/src/views/settings/UpdatesTab.tsx +107 -101
  794. package/src/views/settings/components.tsx +26 -62
  795. package/src/views/settings/useAppearanceSettings.ts +116 -25
  796. package/src/views/users/UserDetailDrawer.tsx +1 -1
  797. package/dist/components/ui/StatPill.d.ts +0 -18
  798. package/dist/components/ui/StatPill.d.ts.map +0 -1
  799. package/dist/components/ui/StatPill.js +0 -5
  800. package/dist/components/ui/StatPill.js.map +0 -1
  801. package/src/components/ui/StatPill.tsx +0 -37
@@ -0,0 +1,736 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // @vitest-environment happy-dom
3
+ import { act, cleanup, fireEvent, render, screen, waitFor, within } from '@testing-library/react';
4
+ import { createPortal } from 'react-dom';
5
+ import { hydrateRoot } from 'react-dom/client';
6
+ import { renderToString } from 'react-dom/server';
7
+ import { afterEach, describe, expect, it, vi } from 'vitest';
8
+ const fetchFormsSidebarCounts = vi.fn(async () => [
9
+ { formId: 'f1', name: 'Contact Us', slug: 'contact-us', status: 'active', unread: 4 },
10
+ { formId: 'f2', name: 'Free RFP', slug: 'free-rfp', status: 'active', unread: 2 },
11
+ ]);
12
+ vi.mock('../../lib/forms-service.js', () => ({
13
+ fetchFormsSidebarCounts: () => fetchFormsSidebarCounts(),
14
+ }));
15
+ vi.mock('../../components/ThemeProvider.js', () => ({
16
+ useTheme: () => ({
17
+ adminBrand: { primaryUrl: null, markUrl: null, darkUrl: null, initials: 'BW' },
18
+ resolvedTheme: 'light',
19
+ density: 'default',
20
+ }),
21
+ }));
22
+ // The real Header pulls in the command palette, notifications and locale
23
+ // switcher; the Layout tests only need its menu button.
24
+ vi.mock('../../layout/Header.js', () => ({
25
+ Header: ({ onToggleSidebar }) => (_jsx("button", { type: "button", onClick: onToggleSidebar, children: "Menu" })),
26
+ }));
27
+ const { Sidebar } = await import('../../layout/Sidebar.js');
28
+ const { MobileTabBar } = await import('../../layout/MobileTabBar.js');
29
+ const { Layout } = await import('../../layout/Layout.js');
30
+ const { AdminShell, DRAWER_ID } = await import('../../layout/primitives/AdminShell.js');
31
+ const { Toaster } = await import('../../components/ui/Toaster.js');
32
+ const { toast } = await import('sonner');
33
+ const { activeChildPath, applyFormsNav, buildNavItems, isPathActive, SETTINGS_TABS, withSettingsTabs, } = await import('../../layout/nav-config.js');
34
+ afterEach(() => {
35
+ cleanup();
36
+ vi.unstubAllGlobals();
37
+ });
38
+ /**
39
+ * happy-dom has no real media queries; answer them from a fixed width. Reads
40
+ * `min-width` / `max-width` and range syntax (`width < 769px`,
41
+ * `769px <= width < 1025px`), joined by `and`.
42
+ */
43
+ function matchesWidth(query, width) {
44
+ const compare = (a, op, b) => op === '<' ? a < b : op === '<=' ? a <= b : op === '>' ? a > b : a >= b;
45
+ return query.split(/\)\s*and\s*\(/).every((raw) => {
46
+ const part = raw.replace(/[()\s]/g, '');
47
+ const min = /^min-width:(\d+(?:\.\d+)?)px$/.exec(part);
48
+ if (min)
49
+ return width >= Number(min[1]);
50
+ const max = /^max-width:(\d+(?:\.\d+)?)px$/.exec(part);
51
+ if (max)
52
+ return width <= Number(max[1]);
53
+ const range = /^(?:(\d+(?:\.\d+)?)px(<=?|>=?))?width(?:(<=?|>=?)(\d+(?:\.\d+)?)px)?$/.exec(part);
54
+ if (!range)
55
+ throw new Error(`stubWidth can't read ${query}`);
56
+ const [, lo, loOp, hiOp, hi] = range;
57
+ return ((lo === undefined || compare(Number(lo), loOp, width)) &&
58
+ (hi === undefined || compare(width, hiOp, Number(hi))));
59
+ });
60
+ }
61
+ function stubWidth(width) {
62
+ vi.stubGlobal('matchMedia', (query) => {
63
+ const matches = matchesWidth(query, width);
64
+ return {
65
+ matches,
66
+ media: query,
67
+ addEventListener: () => { },
68
+ removeEventListener: () => { },
69
+ };
70
+ });
71
+ }
72
+ /**
73
+ * Whether a rail flyout is open, read from its trigger. (Radix hides the
74
+ * menu with `visibility: hidden` once happy-dom's zero-size layout puts it
75
+ * "out of view", so role queries can't tell open from closed.)
76
+ */
77
+ const isOpen = (trigger) => trigger.getAttribute('aria-expanded') === 'true';
78
+ function renderSidebar(currentPath, collapsed = false, onNavigate = vi.fn()) {
79
+ render(_jsx(Sidebar, { collapsed: collapsed, onToggleCollapse: () => { }, currentPath: currentPath, onNavigate: onNavigate }));
80
+ return onNavigate;
81
+ }
82
+ /**
83
+ * P-02 shell & nav (docs/design/prenta/TRACKING.md, specs/SHELL.md §2).
84
+ */
85
+ describe('nav config', () => {
86
+ it('lists the Forms children in the kit order, with per-form rows one level down', () => {
87
+ const forms = applyFormsNav(buildNavItems({}), [
88
+ { formId: 'f1', name: 'Contact Us', slug: 'contact-us', status: 'active', unread: 4 },
89
+ { formId: 'f2', name: 'Free RFP', slug: 'free-rfp', status: 'active', unread: 2 },
90
+ ]);
91
+ const item = forms.find((i) => i.label === 'Forms');
92
+ const label = (c) => `${c.label}${c.badge ? ` (${c.badge})` : ''}`;
93
+ expect(item?.badge).toBe(6);
94
+ expect(item?.children?.map(label)).toEqual([
95
+ 'All Entries (6)',
96
+ 'All Forms',
97
+ 'Spam',
98
+ 'SMTP Log',
99
+ 'Form Settings',
100
+ ]);
101
+ // cms-layouts.js "3rd-level nav nesting": Entries and Forms → per-form.
102
+ expect(item?.children?.[0]?.children?.map(label)).toEqual(['Contact Us (4)', 'Free RFP (2)']);
103
+ expect(item?.children?.[0]?.children?.[0]?.path).toBe('/forms/f1/submissions');
104
+ expect(item?.children?.[1]?.children?.map((c) => c.path)).toEqual([
105
+ '/forms/f1/edit',
106
+ '/forms/f2/edit',
107
+ ]);
108
+ });
109
+ it('lets the most specific sibling own the page', () => {
110
+ const pages = buildNavItems({}).find((i) => i.label === 'Pages');
111
+ expect(activeChildPath(pages.children, '/pages/trash')).toBe('/pages/trash');
112
+ expect(activeChildPath(pages.children, '/pages/abc/edit')).toBe('/pages');
113
+ });
114
+ it('keeps config and plugin entries out of the tab bar set', () => {
115
+ const items = buildNavItems({
116
+ collections: {
117
+ pages: { slug: 'pages', type: 'page' },
118
+ faqs: { slug: 'faqs', labels: { plural: 'FAQs' } },
119
+ team: { slug: 'team', labels: { plural: 'Team' }, admin: { group: 'Company' } },
120
+ },
121
+ _pluginNavItems: [{ label: 'Redirect log', path: '/redirect-log' }],
122
+ });
123
+ expect(items.filter((i) => i.extra).map((i) => `${i.group}:${i.label}`)).toEqual([
124
+ 'Content:FAQs',
125
+ 'Content:Redirect log',
126
+ 'Company:Team',
127
+ ]);
128
+ // Config groups sit after CONTENT and before SETTINGS.
129
+ const labels = items.map((i) => i.label);
130
+ expect(labels.indexOf('Team')).toBeGreaterThan(labels.indexOf('Script Tags'));
131
+ expect(labels.indexOf('Team')).toBeLessThan(labels.indexOf('Jobs'));
132
+ });
133
+ });
134
+ describe('Sidebar', () => {
135
+ it('puts Jobs above Settings under the SETTINGS label', () => {
136
+ stubWidth(1280);
137
+ renderSidebar('/');
138
+ const nav = screen.getByRole('navigation', { name: 'Primary' });
139
+ const text = nav.textContent ?? '';
140
+ expect(text.indexOf('Settings')).toBeLessThan(text.indexOf('Jobs'));
141
+ expect(text.indexOf('Jobs')).toBeLessThan(text.lastIndexOf('Settings'));
142
+ // Pages, Posts and Appearance come before the CONTENT label.
143
+ expect(text.indexOf('Appearance')).toBeLessThan(text.indexOf('Content'));
144
+ expect(text.indexOf('Content')).toBeLessThan(text.indexOf('Media'));
145
+ });
146
+ it('toggles a parent on click at ≥1025px instead of navigating', () => {
147
+ stubWidth(1280);
148
+ const onNavigate = renderSidebar('/', false);
149
+ fireEvent.click(screen.getByRole('button', { name: 'Pages' }));
150
+ expect(onNavigate).not.toHaveBeenCalled();
151
+ expect(screen.getByRole('button', { name: 'All Pages' })).toBeTruthy();
152
+ expect(screen.getByRole('button', { name: 'Collapse Pages' }).getAttribute('aria-expanded')).toBe('true');
153
+ });
154
+ it('navigates from a parent in the mobile drawer (below 1025px)', () => {
155
+ stubWidth(600);
156
+ const onNavigate = renderSidebar('/', false);
157
+ fireEvent.click(screen.getByRole('button', { name: 'Pages' }));
158
+ expect(onNavigate).toHaveBeenCalledWith('/pages');
159
+ });
160
+ it('shows the Forms unread total as a kit badge, in the rail too', async () => {
161
+ stubWidth(924);
162
+ renderSidebar('/', true);
163
+ const forms = await screen.findByRole('button', { name: /Forms/ });
164
+ await waitFor(() => expect(within(forms).getByLabelText('6 unread')).toBeTruthy());
165
+ const badge = within(forms).getByLabelText('6 unread');
166
+ expect(badge.className).toContain('bg-err');
167
+ expect(badge.className).toContain('text-badge');
168
+ // Rail labels stay in the accessibility tree.
169
+ expect(within(forms).getByText('Forms').className).toContain('sr-only');
170
+ });
171
+ it('hides section labels in the ≤1024px rail', () => {
172
+ stubWidth(924);
173
+ renderSidebar('/', true);
174
+ const label = screen.getByText('Content');
175
+ expect(label.className).toContain('tablet:hidden');
176
+ expect(label.className).toContain('opacity-0');
177
+ });
178
+ });
179
+ describe('Forms third level', () => {
180
+ it('opens the per-form rows when one is the current page, and toggles them', async () => {
181
+ stubWidth(1280);
182
+ renderSidebar('/forms/f2/submissions');
183
+ const rfp = await screen.findByRole('button', { name: /^Free RFP/ });
184
+ expect(rfp.getAttribute('aria-current')).toBe('page');
185
+ expect(rfp.className).toContain('pl-[30px]');
186
+ // All Forms' rows stay closed; All Entries' are open.
187
+ const allForms = screen.getByRole('button', { name: 'Expand All Forms' });
188
+ expect(allForms.getAttribute('aria-expanded')).toBe('false');
189
+ const entries = screen.getByRole('button', { name: 'Collapse All Entries' });
190
+ fireEvent.click(entries);
191
+ expect(screen.queryByRole('button', { name: /^Free RFP/ })).toBeNull();
192
+ });
193
+ it('leaves per-form rows out of the rail flyout', async () => {
194
+ stubWidth(924);
195
+ renderSidebar('/', true);
196
+ const forms = await screen.findByRole('button', { name: /^Forms/ });
197
+ fireEvent.mouseEnter(forms.parentElement);
198
+ await waitFor(() => expect(screen.getByText('All Entries')).toBeTruthy());
199
+ expect(screen.getByText('Form Settings')).toBeTruthy();
200
+ expect(screen.queryByText('Contact Us')).toBeNull();
201
+ });
202
+ });
203
+ describe('MobileTabBar', () => {
204
+ it('shows one tab per kit destination plus More, and leaves extras to the drawer', () => {
205
+ const items = applyFormsNav(buildNavItems({
206
+ collections: {
207
+ pages: { slug: 'pages', type: 'page' },
208
+ blog: { slug: 'blog', type: 'post' },
209
+ faqs: { slug: 'faqs', labels: { plural: 'FAQs' } },
210
+ },
211
+ }), [
212
+ { formId: 'f1', name: 'Contact Us', slug: 'contact-us', status: 'active', unread: 3 },
213
+ ]);
214
+ const onNavigate = vi.fn();
215
+ const onMore = vi.fn();
216
+ render(_jsx(MobileTabBar, { items: items, currentPath: "/forms/entries", onNavigate: onNavigate, onMore: onMore, moreOpen: false }));
217
+ const bar = screen.getByRole('navigation', { name: 'Tab bar' });
218
+ const tabs = within(bar)
219
+ .getAllByRole('button')
220
+ .map((b) => b.textContent);
221
+ expect(tabs).toEqual([
222
+ 'Dashboard',
223
+ 'Pages',
224
+ 'Posts',
225
+ 'Appearance',
226
+ 'Media',
227
+ 'Forms3',
228
+ 'SEO',
229
+ 'Script Tags',
230
+ 'Jobs',
231
+ 'Settings',
232
+ 'More',
233
+ ]);
234
+ // A child page marks its parent tab.
235
+ expect(within(bar)
236
+ .getByRole('button', { name: /^Forms/ })
237
+ .getAttribute('aria-current')).toBe('page');
238
+ fireEvent.click(within(bar).getByRole('button', { name: 'Pages' }));
239
+ expect(onNavigate).toHaveBeenCalledWith('/pages');
240
+ const more = within(bar).getByRole('button', { name: 'More' });
241
+ expect(more.getAttribute('aria-expanded')).toBe('false');
242
+ fireEvent.click(more);
243
+ expect(onMore).toHaveBeenCalledOnce();
244
+ });
245
+ });
246
+ describe('AdminShell', () => {
247
+ it('renders the tab bar on mobile only and keeps the closed drawer inert', async () => {
248
+ stubWidth(600);
249
+ const { rerender } = render(_jsx(AdminShell, { desktopBreakpoint: 769, sidebar: _jsx("button", { type: "button", children: "Drawer link" }), tabBar: _jsx("div", { children: "Tab bar" }), children: "Body" }));
250
+ expect(screen.getByText('Tab bar')).toBeTruthy();
251
+ const drawer = screen.getByText('Drawer link').parentElement;
252
+ await waitFor(() => expect(drawer.inert).toBe(true));
253
+ // (happy-dom drops the `calc(… env(safe-area-inset-bottom))` padding, so it
254
+ // isn't asserted here; the tab bar test checks the safe-area classes.)
255
+ rerender(_jsx(AdminShell, { desktopBreakpoint: 769, mobileSidebarOpen: true, sidebar: _jsx("button", { type: "button", children: "Drawer link" }), tabBar: _jsx("div", { children: "Tab bar" }), children: "Body" }));
256
+ await waitFor(() => expect(drawer.inert).toBe(false));
257
+ cleanup();
258
+ stubWidth(1280);
259
+ await act(async () => {
260
+ render(_jsx(AdminShell, { desktopBreakpoint: 769, sidebar: _jsx("button", { type: "button", children: "Drawer link" }), tabBar: _jsx("div", { children: "Tab bar" }), children: "Body" }));
261
+ });
262
+ expect(screen.queryByText('Tab bar')).toBeNull();
263
+ });
264
+ });
265
+ /** Fixes from the second review of the P-02 commit. */
266
+ describe('P-02 review fixes', () => {
267
+ it('reads every tier query the same way at fractional widths', () => {
268
+ // Layout's and Sidebar's queries: exactly one tier matches at any width.
269
+ const tiers = ['(width < 769px)', '(769px <= width < 1025px)', '(min-width: 1025px)'];
270
+ for (const width of [320, 768, 768.5, 769, 924, 1024, 1024.5, 1025, 1440]) {
271
+ expect(tiers.filter((q) => matchesWidth(q, width))).toHaveLength(1);
272
+ // AdminShell's desktop query is the mobile tier's complement.
273
+ expect(matchesWidth('(min-width: 769px)', width)).toBe(!matchesWidth(tiers[0], width));
274
+ }
275
+ });
276
+ it('leaves focus where it was when a rail flyout opens on hover', async () => {
277
+ stubWidth(924);
278
+ render(_jsxs(_Fragment, { children: [_jsx("input", { "aria-label": "Title" }), _jsx(Sidebar, { collapsed: true, onToggleCollapse: () => { }, currentPath: "/", onNavigate: vi.fn() })] }));
279
+ const input = screen.getByRole('textbox', { name: 'Title' });
280
+ input.focus();
281
+ expect(document.activeElement).toBe(input);
282
+ const pages = screen.getByRole('button', { name: 'Pages' });
283
+ fireEvent.mouseEnter(pages.parentElement);
284
+ await waitFor(() => expect(isOpen(pages)).toBe(true));
285
+ expect(document.activeElement).toBe(input);
286
+ // Esc still closes it, though focus isn't in it.
287
+ fireEvent.keyDown(input, { key: 'Escape' });
288
+ await waitFor(() => expect(isOpen(pages)).toBe(false));
289
+ });
290
+ it('reports the group state on a parent row that toggles it (≥1025px)', () => {
291
+ stubWidth(1280);
292
+ renderSidebar('/');
293
+ const row = screen.getByRole('button', { name: 'Pages' });
294
+ const chevron = screen.getByRole('button', { name: 'Expand Pages' });
295
+ expect(row.getAttribute('aria-expanded')).toBe('false');
296
+ expect(row.getAttribute('aria-controls')).toBe(chevron.getAttribute('aria-controls'));
297
+ fireEvent.click(row);
298
+ expect(row.getAttribute('aria-expanded')).toBe('true');
299
+ expect(document.getElementById(row.getAttribute('aria-controls'))).toBeTruthy();
300
+ // 24px chevrons that grow to the touch target on coarse pointers.
301
+ const collapse = screen.getByRole('button', { name: 'Collapse Pages' });
302
+ expect(collapse.className).toContain('prenta-touch-hit');
303
+ expect(collapse.className).toContain('h-6 w-6');
304
+ });
305
+ it('leaves aria-expanded off a parent row that navigates (drawer)', () => {
306
+ stubWidth(600);
307
+ renderSidebar('/');
308
+ expect(screen.getByRole('button', { name: 'Pages' }).hasAttribute('aria-expanded')).toBe(false);
309
+ });
310
+ it('outlines the current row for forced colors', () => {
311
+ stubWidth(1280);
312
+ renderSidebar('/media');
313
+ const media = screen.getByRole('button', { name: 'Media' });
314
+ expect(media.getAttribute('aria-current')).toBe('page');
315
+ expect(media.className).toContain('forced-colors:outline-solid');
316
+ });
317
+ it('names the Header & Footer row as the kit does', () => {
318
+ stubWidth(1280);
319
+ renderSidebar('/appearance/header');
320
+ expect(screen.getByRole('button', { name: 'Header & Footer' })).toBeTruthy();
321
+ });
322
+ it('shows the rail label for mouse and keyboard, dismisses it on Esc, not for touch', () => {
323
+ stubWidth(924);
324
+ renderSidebar('/', true);
325
+ const wrapper = screen.getByRole('button', { name: 'Dashboard' }).parentElement;
326
+ const labels = () => screen.queryAllByText('Dashboard').length;
327
+ expect(labels()).toBe(1); // the button's own sr-only label
328
+ fireEvent.pointerEnter(wrapper, { pointerType: 'touch' });
329
+ expect(labels()).toBe(1);
330
+ fireEvent.pointerEnter(wrapper, { pointerType: 'mouse' });
331
+ expect(labels()).toBe(2);
332
+ fireEvent.keyDown(document, { key: 'Escape' });
333
+ expect(labels()).toBe(1);
334
+ });
335
+ it('hides Expand sidebar in the 769–1024px rail', () => {
336
+ stubWidth(924);
337
+ renderSidebar('/', true);
338
+ const toggle = screen.getByRole('button', { name: /expand sidebar/i });
339
+ expect(toggle.closest('div')?.className).toContain('not-narrow:block');
340
+ });
341
+ it('lists the Settings tabs under Settings, each active on its own tab', () => {
342
+ const items = withSettingsTabs(buildNavItems({}), {});
343
+ const settings = items.find((i) => i.path === '/settings');
344
+ expect(settings.children?.map((c) => c.label)).toEqual([
345
+ 'General',
346
+ 'Admin appearance',
347
+ 'Security',
348
+ 'Users',
349
+ 'MCP',
350
+ 'API Keys',
351
+ 'SEO',
352
+ 'Email (SMTP)',
353
+ 'Webhooks',
354
+ 'AI',
355
+ 'Updates',
356
+ ]);
357
+ // The same list as the Settings strip (P-06), so each tab reads the same.
358
+ expect(settings.children?.map((c) => [c.label, c.icon])).toEqual(SETTINGS_TABS.filter((t) => t.id !== 'layout').map((t) => [t.label, t.icon]));
359
+ expect(settings.children?.every((c) => c.iconSize === 14)).toBe(true);
360
+ expect(activeChildPath(settings.children, '/settings')).toBe('/settings?tab=general');
361
+ expect(activeChildPath(settings.children, '/settings?tab=security')).toBe('/settings?tab=security');
362
+ expect(isPathActive('/settings/other', '/settings?tab=general')).toBe(false);
363
+ // Layout only exists when the site defines regions.
364
+ const withLayout = withSettingsTabs(buildNavItems({}), { layout: { regions: { header: {} } } });
365
+ expect(withLayout.find((i) => i.path === '/settings')?.children?.map((c) => c.label)).toContain('Layout');
366
+ });
367
+ it('keeps 44px tabs and offers the drawer from More', () => {
368
+ render(_jsx(MobileTabBar, { items: buildNavItems({}), currentPath: "/", onNavigate: vi.fn(), onMore: vi.fn(), moreOpen: false, drawerId: "drawer" }));
369
+ const bar = screen.getByRole('navigation', { name: 'Tab bar' });
370
+ for (const tab of within(bar).getAllByRole('button')) {
371
+ expect(tab.className).toContain('min-h-11');
372
+ expect(tab.className).toContain('min-w-[60px]');
373
+ }
374
+ expect(within(bar).getByRole('button', { name: 'More' }).getAttribute('aria-controls')).toBe('drawer');
375
+ // Below every overlay layer, and clear of the home indicator.
376
+ expect(bar.className).toContain('z-[var(--z-tabbar)]');
377
+ expect(bar.className).toContain('pb-[env(safe-area-inset-bottom)]');
378
+ });
379
+ it('makes the open drawer a modal dialog: focus in, trapped, Esc, focus back', async () => {
380
+ stubWidth(600);
381
+ const onClose = vi.fn();
382
+ const shell = (open) => (_jsx(AdminShell, { desktopBreakpoint: 769, mobileSidebarOpen: open, onMobileSidebarClose: onClose, sidebar: _jsxs(_Fragment, { children: [_jsx("button", { type: "button", children: "First link" }), _jsx("button", { type: "button", children: "Last link" })] }), tabBar: _jsx("button", { type: "button", "aria-controls": DRAWER_ID, children: "More" }), children: _jsx("button", { type: "button", children: "Body button" }) }));
383
+ const { rerender } = render(shell(false));
384
+ const more = await screen.findByRole('button', { name: 'More' });
385
+ expect(screen.queryByRole('dialog')).toBeNull();
386
+ more.focus();
387
+ rerender(shell(true));
388
+ const dialog = await screen.findByRole('dialog', { name: 'Navigation' });
389
+ expect(dialog.id).toBe(DRAWER_ID);
390
+ expect(dialog.getAttribute('aria-modal')).toBe('true');
391
+ // The drawer's own close button comes first and takes focus.
392
+ const first = screen.getByRole('button', { name: 'Close navigation' });
393
+ const last = screen.getByRole('button', { name: 'Last link' });
394
+ await waitFor(() => expect(document.activeElement).toBe(first));
395
+ const content = document.getElementById('prenta-main').parentElement;
396
+ expect(content.inert).toBe(true);
397
+ expect(more.parentElement.inert).toBe(true);
398
+ // Tab wraps at both ends.
399
+ last.focus();
400
+ fireEvent.keyDown(last, { key: 'Tab' });
401
+ expect(document.activeElement).toBe(first);
402
+ fireEvent.keyDown(first, { key: 'Tab', shiftKey: true });
403
+ expect(document.activeElement).toBe(last);
404
+ fireEvent.keyDown(last, { key: 'Escape' });
405
+ expect(onClose).toHaveBeenCalledOnce();
406
+ rerender(shell(false));
407
+ await waitFor(() => expect(document.activeElement).toBe(more));
408
+ expect(content.inert).toBe(false);
409
+ expect(screen.queryByRole('dialog')).toBeNull();
410
+ });
411
+ it('slides the drawer with a motion-safe class the Reduce motion setting can stop', () => {
412
+ stubWidth(600);
413
+ render(_jsx(AdminShell, { desktopBreakpoint: 769, sidebar: _jsx("span", { children: "Nav" }), children: "Body" }));
414
+ const drawer = document.getElementById(DRAWER_ID);
415
+ expect(drawer.style.transition).toBe('');
416
+ expect(drawer.className).toContain('motion-safe:transition-transform');
417
+ });
418
+ it('wires the drawer to More and lists Settings tabs in it on mobile', async () => {
419
+ stubWidth(600);
420
+ render(_jsx(Layout, { config: {}, session: null, currentPath: "/settings?tab=security", onNavigate: vi.fn(), children: "Body" }));
421
+ const bar = await screen.findByRole('navigation', { name: 'Tab bar' });
422
+ const more = within(bar).getByRole('button', { name: 'More' });
423
+ expect(more.getAttribute('aria-controls')).toBe(DRAWER_ID);
424
+ more.focus();
425
+ fireEvent.click(more);
426
+ const dialog = await screen.findByRole('dialog', { name: 'Navigation' });
427
+ const security = within(dialog).getByRole('button', { name: 'Security' });
428
+ expect(security.getAttribute('aria-current')).toBe('page');
429
+ fireEvent.keyDown(security, { key: 'Escape' });
430
+ await waitFor(() => expect(screen.queryByRole('dialog')).toBeNull());
431
+ await waitFor(() => expect(document.activeElement).toBe(more));
432
+ });
433
+ it('keeps Settings a single row in the desktop sidebar', async () => {
434
+ stubWidth(1280);
435
+ render(_jsx(Layout, { config: {}, session: null, currentPath: "/settings", onNavigate: vi.fn(), children: "Body" }));
436
+ await screen.findByRole('navigation', { name: 'Primary' });
437
+ expect(screen.queryByRole('button', { name: 'Security' })).toBeNull();
438
+ });
439
+ it('lifts toasts above the tab bar', async () => {
440
+ const { container } = render(_jsx(Toaster, { aboveTabBar: true }));
441
+ act(() => {
442
+ toast('Saved');
443
+ });
444
+ await screen.findByText('Saved');
445
+ const list = container.querySelector('[data-sonner-toaster]');
446
+ expect(list.style.getPropertyValue('--offset-bottom')).toContain('var(--tabbar-h)');
447
+ expect(list.style.getPropertyValue('--mobile-offset-bottom')).toContain('env(safe-area-inset-bottom)');
448
+ });
449
+ });
450
+ /**
451
+ * happy-dom applies no stylesheet, so Tailwind's `hidden` (display: none)
452
+ * leaves boxes. Give elements under it no client rects, as a browser does.
453
+ */
454
+ function hideTailwindHidden() {
455
+ return vi.spyOn(HTMLElement.prototype, 'getClientRects').mockImplementation(function () {
456
+ return (this.closest('.hidden') ? [] : [{}]);
457
+ });
458
+ }
459
+ /** `stubWidth` whose width can change, firing the queries' change listeners. */
460
+ function stubResizableWidth(initial) {
461
+ let width = initial;
462
+ const listeners = new Set();
463
+ vi.stubGlobal('matchMedia', (query) => ({
464
+ media: query,
465
+ get matches() {
466
+ return matchesWidth(query, width);
467
+ },
468
+ addEventListener: (_, listener) => listeners.add(listener),
469
+ removeEventListener: (_, listener) => listeners.delete(listener),
470
+ }));
471
+ return (next) => {
472
+ width = next;
473
+ act(() => {
474
+ for (const listener of [...listeners])
475
+ listener();
476
+ });
477
+ };
478
+ }
479
+ /** Fixes from the third review of the P-02 commits. */
480
+ describe('P-02 third review', () => {
481
+ it('traps Tab among the visible rows of the real sidebar, with a close button first', async () => {
482
+ stubWidth(600);
483
+ const rects = hideTailwindHidden();
484
+ const onClose = vi.fn();
485
+ render(_jsx(AdminShell, { desktopBreakpoint: 769, mobileSidebarOpen: true, onMobileSidebarClose: onClose, sidebar: _jsx(Sidebar, { collapsed: false, onToggleCollapse: () => { }, currentPath: "/", onNavigate: vi.fn() }), tabBar: _jsx("button", { type: "button", children: "More" }), children: "Body" }));
486
+ const dialog = await screen.findByRole('dialog', { name: 'Navigation' });
487
+ const close = within(dialog).getByRole('button', { name: 'Close navigation' });
488
+ await waitFor(() => expect(document.activeElement).toBe(close));
489
+ // The collapse footer is in the DOM but not rendered below 1025px.
490
+ const visible = within(dialog)
491
+ .getAllByRole('button')
492
+ .filter((b) => !b.closest('.hidden'));
493
+ const last = visible[visible.length - 1];
494
+ expect(last.textContent).toBe('Settings');
495
+ last.focus();
496
+ fireEvent.keyDown(last, { key: 'Tab' });
497
+ expect(document.activeElement).toBe(close);
498
+ fireEvent.keyDown(close, { key: 'Tab', shiftKey: true });
499
+ expect(document.activeElement).toBe(last);
500
+ // Nothing behind the modal takes focus, the skip link included.
501
+ const skip = document.querySelector('.prenta-skip-link');
502
+ expect(skip.inert).toBe(true);
503
+ fireEvent.click(close);
504
+ expect(onClose).toHaveBeenCalledOnce();
505
+ rects.mockRestore();
506
+ });
507
+ it('leaves Esc from a layer portaled out of the drawer to that layer', async () => {
508
+ stubWidth(600);
509
+ const onClose = vi.fn();
510
+ render(_jsx(AdminShell, { desktopBreakpoint: 769, mobileSidebarOpen: true, onMobileSidebarClose: onClose, sidebar: _jsxs(_Fragment, { children: [_jsx("button", { type: "button", children: "Row" }), createPortal(_jsx("button", { type: "button", children: "Menu item" }), document.body)] }), children: "Body" }));
511
+ await screen.findByRole('dialog', { name: 'Navigation' });
512
+ fireEvent.keyDown(screen.getByRole('button', { name: 'Menu item' }), { key: 'Escape' });
513
+ expect(onClose).not.toHaveBeenCalled();
514
+ // Handled already inside the drawer (default prevented): not ours either.
515
+ const row = screen.getByRole('button', { name: 'Row' });
516
+ const claim = (e) => e.preventDefault();
517
+ row.addEventListener('keydown', claim);
518
+ fireEvent.keyDown(row, { key: 'Escape' });
519
+ expect(onClose).not.toHaveBeenCalled();
520
+ row.removeEventListener('keydown', claim);
521
+ fireEvent.keyDown(row, { key: 'Escape' });
522
+ expect(onClose).toHaveBeenCalledOnce();
523
+ });
524
+ it('closes the drawer when the window widens past the mobile tier', async () => {
525
+ const resize = stubResizableWidth(600);
526
+ render(_jsx(Layout, { config: {}, session: null, currentPath: "/", onNavigate: vi.fn(), children: "Body" }));
527
+ const bar = await screen.findByRole('navigation', { name: 'Tab bar' });
528
+ fireEvent.click(within(bar).getByRole('button', { name: 'More' }));
529
+ await screen.findByRole('dialog', { name: 'Navigation' });
530
+ resize(1280);
531
+ expect(screen.queryByRole('dialog')).toBeNull();
532
+ resize(600);
533
+ const again = await screen.findByRole('navigation', { name: 'Tab bar' });
534
+ expect(screen.queryByRole('dialog')).toBeNull();
535
+ expect(within(again).getByRole('button', { name: 'More' }).getAttribute('aria-expanded')).toBe('false');
536
+ });
537
+ it('keeps Ctrl/Cmd+K from opening the command palette behind the open drawer', async () => {
538
+ stubWidth(600);
539
+ const palette = vi.fn();
540
+ const listen = (e) => {
541
+ if (e.key === 'k' && (e.metaKey || e.ctrlKey))
542
+ palette();
543
+ };
544
+ document.addEventListener('keydown', listen);
545
+ const shell = (open) => (_jsx(AdminShell, { desktopBreakpoint: 769, mobileSidebarOpen: open, onMobileSidebarClose: () => { }, sidebar: _jsx("button", { type: "button", children: "Row" }), children: "Body" }));
546
+ const { rerender } = render(shell(true));
547
+ await screen.findByRole('dialog', { name: 'Navigation' });
548
+ const row = screen.getByRole('button', { name: 'Row' });
549
+ // fireEvent returns false when the default action was prevented.
550
+ expect(fireEvent.keyDown(row, { key: 'k', ctrlKey: true })).toBe(false);
551
+ expect(fireEvent.keyDown(row, { key: 'k', metaKey: true })).toBe(false);
552
+ expect(fireEvent.keyDown(row, { key: 'K', ctrlKey: true, shiftKey: true })).toBe(true);
553
+ expect(palette).not.toHaveBeenCalled();
554
+ rerender(shell(false));
555
+ await waitFor(() => expect(screen.queryByRole('dialog')).toBeNull());
556
+ fireEvent.keyDown(document.body, { key: 'k', ctrlKey: true });
557
+ expect(palette).toHaveBeenCalledOnce();
558
+ document.removeEventListener('keydown', listen);
559
+ });
560
+ it('moves focus into a flyout opened from the keyboard after an earlier hover', async () => {
561
+ stubWidth(924);
562
+ renderSidebar('/', true);
563
+ const pages = screen.getByRole('button', { name: 'Pages' });
564
+ const wrapper = pages.parentElement;
565
+ fireEvent.mouseEnter(wrapper);
566
+ await waitFor(() => expect(isOpen(pages)).toBe(true));
567
+ fireEvent.mouseLeave(wrapper);
568
+ await waitFor(() => expect(isOpen(pages)).toBe(false)); // the 120ms hover close
569
+ pages.focus();
570
+ // happy-dom delivers the focusin a task later; let it land first.
571
+ await act(() => new Promise((resolve) => setTimeout(resolve, 0)));
572
+ fireEvent.keyDown(pages, { key: 'Enter' });
573
+ await waitFor(() => expect(isOpen(pages)).toBe(true));
574
+ const menu = document.querySelector('[role="menu"]');
575
+ await waitFor(() => expect(menu.contains(document.activeElement)).toBe(true));
576
+ });
577
+ it('leaves focus in the field when Esc closes a hover-opened flyout', async () => {
578
+ stubWidth(924);
579
+ render(_jsxs(_Fragment, { children: [_jsx("input", { "aria-label": "Title" }), _jsx(Sidebar, { collapsed: true, onToggleCollapse: () => { }, currentPath: "/", onNavigate: vi.fn() })] }));
580
+ const input = screen.getByRole('textbox', { name: 'Title' });
581
+ input.focus();
582
+ const pages = screen.getByRole('button', { name: 'Pages' });
583
+ fireEvent.mouseEnter(pages.parentElement);
584
+ await waitFor(() => expect(isOpen(pages)).toBe(true));
585
+ fireEvent.keyDown(input, { key: 'Escape' });
586
+ await waitFor(() => expect(isOpen(pages)).toBe(false));
587
+ expect(document.activeElement).toBe(input);
588
+ });
589
+ // These two mount inside a marked admin root, as AdminRoot does, so the
590
+ // flyout portals into a stable container. (Without one, the portal target
591
+ // falls back to the first `.prenta-admin` in the document, which is the
592
+ // open flyout panel itself, and the panel ends up detached.)
593
+ const railInAdminRoot = (extra) => render(_jsxs("div", { "data-prenta-admin-root": "", className: "prenta-admin", children: [extra, _jsx(Sidebar, { collapsed: true, onToggleCollapse: () => { }, currentPath: "/", onNavigate: vi.fn() })] }));
594
+ it('returns focus to the trigger when a hover-opened flyout closes after focus moved into it', async () => {
595
+ stubWidth(924);
596
+ railInAdminRoot(_jsx("input", { "aria-label": "Title" }));
597
+ screen.getByRole('textbox', { name: 'Title' }).focus();
598
+ const pages = screen.getByRole('button', { name: 'Pages' });
599
+ fireEvent.mouseEnter(pages.parentElement);
600
+ await waitFor(() => expect(isOpen(pages)).toBe(true));
601
+ await waitFor(() => expect(document.querySelector('[role="menuitem"]')).not.toBeNull());
602
+ // Radix focuses menu items under the pointer; stand in for that move.
603
+ const item = document.querySelector('[role="menuitem"]');
604
+ act(() => item.focus());
605
+ await act(() => new Promise((resolve) => setTimeout(resolve, 0)));
606
+ fireEvent.keyDown(item, { key: 'Escape' });
607
+ await waitFor(() => expect(isOpen(pages)).toBe(false));
608
+ await waitFor(() => expect(document.activeElement).toBe(pages));
609
+ });
610
+ it('keeps the keyboard focus return when the pointer crosses an open flyout trigger', async () => {
611
+ stubWidth(924);
612
+ railInAdminRoot();
613
+ const pages = screen.getByRole('button', { name: 'Pages' });
614
+ pages.focus();
615
+ await act(() => new Promise((resolve) => setTimeout(resolve, 0)));
616
+ fireEvent.keyDown(pages, { key: 'Enter' });
617
+ await waitFor(() => expect(isOpen(pages)).toBe(true));
618
+ const menu = document.querySelector('[role="menu"]');
619
+ await waitFor(() => expect(menu.contains(document.activeElement)).toBe(true));
620
+ // The mouse passes over the trigger while the keyboard-opened menu is up.
621
+ fireEvent.mouseEnter(pages.parentElement);
622
+ fireEvent.keyDown(document.activeElement, { key: 'Escape' });
623
+ await waitFor(() => expect(isOpen(pages)).toBe(false));
624
+ await waitFor(() => expect(document.activeElement).toBe(pages));
625
+ });
626
+ it('keeps the next hover flyout open when the pointer moves on from one that held focus', async () => {
627
+ stubWidth(924);
628
+ railInAdminRoot();
629
+ const pages = screen.getByRole('button', { name: 'Pages' });
630
+ const posts = screen.getByRole('button', { name: 'Posts' });
631
+ fireEvent.mouseEnter(pages.parentElement);
632
+ await waitFor(() => expect(document.querySelector('[role="menuitem"]')).not.toBeNull());
633
+ act(() => document.querySelector('[role="menuitem"]').focus());
634
+ await act(() => new Promise((resolve) => setTimeout(resolve, 0)));
635
+ fireEvent.mouseLeave(pages.parentElement);
636
+ fireEvent.mouseEnter(posts.parentElement);
637
+ await waitFor(() => expect(isOpen(posts)).toBe(true));
638
+ // Past the 120ms hover close of Pages, plus a tick for Radix's focus return.
639
+ await act(() => new Promise((resolve) => setTimeout(resolve, 250)));
640
+ // Documents the intended behaviour. happy-dom doesn't route Radix's
641
+ // close focus-return to the trigger on this path, so this can't fail on
642
+ // the regression by itself; the close-cause guard in RailFlyout is what
643
+ // prevents the trigger focus that dismissed the next flyout in browsers.
644
+ expect(isOpen(pages)).toBe(false);
645
+ expect(isOpen(posts)).toBe(true);
646
+ expect(document.activeElement).not.toBe(pages);
647
+ });
648
+ it('keeps a keyboard-opened flyout open while the pointer crosses the rail', async () => {
649
+ stubWidth(924);
650
+ railInAdminRoot();
651
+ const pages = screen.getByRole('button', { name: 'Pages' });
652
+ pages.focus();
653
+ await act(() => new Promise((resolve) => setTimeout(resolve, 0)));
654
+ fireEvent.keyDown(pages, { key: 'Enter' });
655
+ await waitFor(() => expect(isOpen(pages)).toBe(true));
656
+ fireEvent.mouseEnter(pages.parentElement);
657
+ fireEvent.mouseLeave(pages.parentElement);
658
+ await act(() => new Promise((resolve) => setTimeout(resolve, 250)));
659
+ expect(isOpen(pages)).toBe(true);
660
+ });
661
+ it('moves focus into a flyout opened by a touch tap, even after an earlier mouse hover', async () => {
662
+ stubWidth(924);
663
+ railInAdminRoot();
664
+ const pages = screen.getByRole('button', { name: 'Pages' });
665
+ fireEvent.mouseEnter(pages.parentElement);
666
+ await waitFor(() => expect(isOpen(pages)).toBe(true));
667
+ fireEvent.mouseLeave(pages.parentElement);
668
+ await waitFor(() => expect(isOpen(pages)).toBe(false));
669
+ fireEvent.pointerDown(pages, { pointerType: 'touch', button: 0, ctrlKey: false });
670
+ await waitFor(() => expect(isOpen(pages)).toBe(true));
671
+ const menu = document.querySelector('[role="menu"]');
672
+ await waitFor(() => expect(menu.contains(document.activeElement)).toBe(true));
673
+ });
674
+ it('takes the keyboard into a flyout the mouse already opened', async () => {
675
+ stubWidth(924);
676
+ railInAdminRoot();
677
+ const pages = screen.getByRole('button', { name: 'Pages' });
678
+ pages.focus();
679
+ await act(() => new Promise((resolve) => setTimeout(resolve, 0)));
680
+ fireEvent.mouseEnter(pages.parentElement);
681
+ await waitFor(() => expect(document.querySelector('[role="menuitem"]')).not.toBeNull());
682
+ expect(document.activeElement).toBe(pages);
683
+ fireEvent.keyDown(pages, { key: 'ArrowDown' });
684
+ expect(isOpen(pages)).toBe(true);
685
+ expect(document.activeElement).toBe(document.querySelector('[role="menuitem"]'));
686
+ });
687
+ it('returns focus to the trigger without a focus ring after a mouse pick', async () => {
688
+ stubWidth(924);
689
+ const onNavigate = vi.fn();
690
+ render(_jsx("div", { "data-prenta-admin-root": "", className: "prenta-admin", children: _jsx(Sidebar, { collapsed: true, onToggleCollapse: () => { }, currentPath: "/", onNavigate: onNavigate }) }));
691
+ const pages = screen.getByRole('button', { name: 'Pages' });
692
+ const focus = vi.spyOn(pages, 'focus');
693
+ fireEvent.mouseEnter(pages.parentElement);
694
+ await waitFor(() => expect(document.querySelector('[role="menuitem"]')).not.toBeNull());
695
+ const item = document.querySelector('[role="menuitem"]');
696
+ act(() => item.focus());
697
+ await act(() => new Promise((resolve) => setTimeout(resolve, 0)));
698
+ fireEvent.click(item, { detail: 1 });
699
+ expect(onNavigate).toHaveBeenCalled();
700
+ await waitFor(() => expect(document.activeElement).toBe(pages));
701
+ expect(focus).toHaveBeenCalledWith(expect.objectContaining({ focusVisible: false }));
702
+ });
703
+ it('gives the rail labels a positioned ancestor inside the sidebar', () => {
704
+ stubWidth(924);
705
+ renderSidebar('/', true);
706
+ expect(screen.getByRole('navigation', { name: 'Primary' }).className).toMatch(/\brelative\b/);
707
+ });
708
+ });
709
+ describe('Sidebar hydration', () => {
710
+ it('renders parent rows the same on the server and in the first client render', async () => {
711
+ // Below 1025px the row navigates and reports no group state, but only
712
+ // after mount: the server can't know the width. happy-dom gives
713
+ // renderToString a window, so take matchMedia away as a server has none.
714
+ const tree = (_jsx(Sidebar, { collapsed: false, onToggleCollapse: () => { }, currentPath: "/", onNavigate: vi.fn(), items: buildNavItems({}) }));
715
+ vi.stubGlobal('matchMedia', undefined);
716
+ const html = renderToString(tree);
717
+ stubWidth(600);
718
+ const container = document.createElement('div');
719
+ container.innerHTML = html;
720
+ document.body.appendChild(container);
721
+ const errors = vi.spyOn(console, 'error').mockImplementation(() => { });
722
+ const recoverable = vi.fn();
723
+ let root;
724
+ await act(async () => {
725
+ root = hydrateRoot(container, tree, { onRecoverableError: recoverable });
726
+ });
727
+ expect(errors).not.toHaveBeenCalled();
728
+ expect(recoverable).not.toHaveBeenCalled();
729
+ const pages = within(container).getByRole('button', { name: 'Pages' });
730
+ expect(pages.hasAttribute('aria-expanded')).toBe(false);
731
+ act(() => root.unmount());
732
+ container.remove();
733
+ errors.mockRestore();
734
+ });
735
+ });
736
+ //# sourceMappingURL=shell-nav.render.test.js.map