@prenta/admin 0.92.0 → 0.94.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 (425) hide show
  1. package/CHANGELOG.md +663 -0
  2. package/design-system/tokens.css +173 -71
  3. package/design-system/tokens.json +244 -63
  4. package/design-system/tokens.scss +203 -98
  5. package/design-system/tokens.tailwind.cjs +93 -38
  6. package/dist/AdminRoot.d.ts +27 -6
  7. package/dist/AdminRoot.d.ts.map +1 -1
  8. package/dist/AdminRoot.js.map +1 -1
  9. package/dist/__tests__/components/breakpoint-controls.render.test.d.ts +2 -0
  10. package/dist/__tests__/components/breakpoint-controls.render.test.d.ts.map +1 -0
  11. package/dist/__tests__/components/breakpoint-controls.render.test.js +51 -0
  12. package/dist/__tests__/components/breakpoint-controls.render.test.js.map +1 -0
  13. package/dist/__tests__/components/canvas-edit-tip.render.test.d.ts +2 -0
  14. package/dist/__tests__/components/canvas-edit-tip.render.test.d.ts.map +1 -0
  15. package/dist/__tests__/components/canvas-edit-tip.render.test.js +37 -0
  16. package/dist/__tests__/components/canvas-edit-tip.render.test.js.map +1 -0
  17. package/dist/__tests__/components/editor-rail.render.test.d.ts +2 -0
  18. package/dist/__tests__/components/editor-rail.render.test.d.ts.map +1 -0
  19. package/dist/__tests__/components/editor-rail.render.test.js +138 -0
  20. package/dist/__tests__/components/editor-rail.render.test.js.map +1 -0
  21. package/dist/__tests__/components/media-block.render.test.d.ts +2 -0
  22. package/dist/__tests__/components/media-block.render.test.d.ts.map +1 -0
  23. package/dist/__tests__/components/media-block.render.test.js +108 -0
  24. package/dist/__tests__/components/media-block.render.test.js.map +1 -0
  25. package/dist/__tests__/components/page-audit-panel.render.test.d.ts +2 -0
  26. package/dist/__tests__/components/page-audit-panel.render.test.d.ts.map +1 -0
  27. package/dist/__tests__/components/page-audit-panel.render.test.js +59 -0
  28. package/dist/__tests__/components/page-audit-panel.render.test.js.map +1 -0
  29. package/dist/__tests__/components/reflow-meter.render.test.d.ts +2 -0
  30. package/dist/__tests__/components/reflow-meter.render.test.d.ts.map +1 -0
  31. package/dist/__tests__/components/reflow-meter.render.test.js +94 -0
  32. package/dist/__tests__/components/reflow-meter.render.test.js.map +1 -0
  33. package/dist/__tests__/components/seo-char-count.test.js +24 -6
  34. package/dist/__tests__/components/seo-char-count.test.js.map +1 -1
  35. package/dist/__tests__/components/serp-preview.render.test.d.ts +2 -0
  36. package/dist/__tests__/components/serp-preview.render.test.d.ts.map +1 -0
  37. package/dist/__tests__/components/serp-preview.render.test.js +25 -0
  38. package/dist/__tests__/components/serp-preview.render.test.js.map +1 -0
  39. package/dist/__tests__/lib/breakpoints.test.d.ts +2 -0
  40. package/dist/__tests__/lib/breakpoints.test.d.ts.map +1 -0
  41. package/dist/__tests__/lib/breakpoints.test.js +67 -0
  42. package/dist/__tests__/lib/breakpoints.test.js.map +1 -0
  43. package/dist/__tests__/lib/canvas-edit-chrome.test.d.ts +2 -0
  44. package/dist/__tests__/lib/canvas-edit-chrome.test.d.ts.map +1 -0
  45. package/dist/__tests__/lib/canvas-edit-chrome.test.js +44 -0
  46. package/dist/__tests__/lib/canvas-edit-chrome.test.js.map +1 -0
  47. package/dist/__tests__/lib/media-format.test.d.ts +2 -0
  48. package/dist/__tests__/lib/media-format.test.d.ts.map +1 -0
  49. package/dist/__tests__/lib/media-format.test.js +70 -0
  50. package/dist/__tests__/lib/media-format.test.js.map +1 -0
  51. package/dist/__tests__/lib/page-audit-input.test.d.ts +2 -0
  52. package/dist/__tests__/lib/page-audit-input.test.d.ts.map +1 -0
  53. package/dist/__tests__/lib/page-audit-input.test.js +122 -0
  54. package/dist/__tests__/lib/page-audit-input.test.js.map +1 -0
  55. package/dist/__tests__/lib/page-editor-service.test.js +2 -0
  56. package/dist/__tests__/lib/page-editor-service.test.js.map +1 -1
  57. package/dist/__tests__/lib/page-redirect.test.d.ts +2 -0
  58. package/dist/__tests__/lib/page-redirect.test.d.ts.map +1 -0
  59. package/dist/__tests__/lib/page-redirect.test.js +26 -0
  60. package/dist/__tests__/lib/page-redirect.test.js.map +1 -0
  61. package/dist/__tests__/lib/rail-width.test.d.ts +2 -0
  62. package/dist/__tests__/lib/rail-width.test.d.ts.map +1 -0
  63. package/dist/__tests__/lib/rail-width.test.js +69 -0
  64. package/dist/__tests__/lib/rail-width.test.js.map +1 -0
  65. package/dist/__tests__/lib/reflow-baseline.test.d.ts +2 -0
  66. package/dist/__tests__/lib/reflow-baseline.test.d.ts.map +1 -0
  67. package/dist/__tests__/lib/reflow-baseline.test.js +56 -0
  68. package/dist/__tests__/lib/reflow-baseline.test.js.map +1 -0
  69. package/dist/__tests__/lib/reflow-probe.test.d.ts +2 -0
  70. package/dist/__tests__/lib/reflow-probe.test.d.ts.map +1 -0
  71. package/dist/__tests__/lib/reflow-probe.test.js +73 -0
  72. package/dist/__tests__/lib/reflow-probe.test.js.map +1 -0
  73. package/dist/__tests__/lib/version-diff.test.d.ts +2 -0
  74. package/dist/__tests__/lib/version-diff.test.d.ts.map +1 -0
  75. package/dist/__tests__/lib/version-diff.test.js +230 -0
  76. package/dist/__tests__/lib/version-diff.test.js.map +1 -0
  77. package/dist/__tests__/lib/version-history.test.d.ts +2 -0
  78. package/dist/__tests__/lib/version-history.test.d.ts.map +1 -0
  79. package/dist/__tests__/lib/version-history.test.js +125 -0
  80. package/dist/__tests__/lib/version-history.test.js.map +1 -0
  81. package/dist/__tests__/styles/canvas-container-queries.test.d.ts +2 -0
  82. package/dist/__tests__/styles/canvas-container-queries.test.d.ts.map +1 -0
  83. package/dist/__tests__/styles/canvas-container-queries.test.js +87 -0
  84. package/dist/__tests__/styles/canvas-container-queries.test.js.map +1 -0
  85. package/dist/__tests__/styles/packaging.test.d.ts +2 -0
  86. package/dist/__tests__/styles/packaging.test.d.ts.map +1 -0
  87. package/dist/__tests__/styles/packaging.test.js +31 -0
  88. package/dist/__tests__/styles/packaging.test.js.map +1 -0
  89. package/dist/__tests__/styles/responsive-layer.test.d.ts +2 -0
  90. package/dist/__tests__/styles/responsive-layer.test.d.ts.map +1 -0
  91. package/dist/__tests__/styles/responsive-layer.test.js +127 -0
  92. package/dist/__tests__/styles/responsive-layer.test.js.map +1 -0
  93. package/dist/__tests__/styles/tokens-drift.test.d.ts +2 -0
  94. package/dist/__tests__/styles/tokens-drift.test.d.ts.map +1 -0
  95. package/dist/__tests__/styles/tokens-drift.test.js +92 -0
  96. package/dist/__tests__/styles/tokens-drift.test.js.map +1 -0
  97. package/dist/__tests__/views/admin-renderer-compat.test.d.ts +2 -0
  98. package/dist/__tests__/views/admin-renderer-compat.test.d.ts.map +1 -0
  99. package/dist/__tests__/views/admin-renderer-compat.test.js +43 -0
  100. package/dist/__tests__/views/admin-renderer-compat.test.js.map +1 -0
  101. package/dist/__tests__/views/admin-section-renderers.test.js +52 -4
  102. package/dist/__tests__/views/admin-section-renderers.test.js.map +1 -1
  103. package/dist/__tests__/views/canvas-review-mode.render.test.d.ts +2 -0
  104. package/dist/__tests__/views/canvas-review-mode.render.test.d.ts.map +1 -0
  105. package/dist/__tests__/views/canvas-review-mode.render.test.js +70 -0
  106. package/dist/__tests__/views/canvas-review-mode.render.test.js.map +1 -0
  107. package/dist/__tests__/views/editor-top-bar.render.test.js +30 -1
  108. package/dist/__tests__/views/editor-top-bar.render.test.js.map +1 -1
  109. package/dist/__tests__/views/page-tab.render.test.d.ts +2 -0
  110. package/dist/__tests__/views/page-tab.render.test.d.ts.map +1 -0
  111. package/dist/__tests__/views/page-tab.render.test.js +151 -0
  112. package/dist/__tests__/views/page-tab.render.test.js.map +1 -0
  113. package/dist/__tests__/views/section-class-parity.test.d.ts +2 -0
  114. package/dist/__tests__/views/section-class-parity.test.d.ts.map +1 -0
  115. package/dist/__tests__/views/section-class-parity.test.js +81 -0
  116. package/dist/__tests__/views/section-class-parity.test.js.map +1 -0
  117. package/dist/__tests__/views/sections-panel-design-owned.render.test.d.ts +2 -0
  118. package/dist/__tests__/views/sections-panel-design-owned.render.test.d.ts.map +1 -0
  119. package/dist/__tests__/views/sections-panel-design-owned.render.test.js +86 -0
  120. package/dist/__tests__/views/sections-panel-design-owned.render.test.js.map +1 -0
  121. package/dist/__tests__/views/version-history-panel.render.test.d.ts +2 -0
  122. package/dist/__tests__/views/version-history-panel.render.test.d.ts.map +1 -0
  123. package/dist/__tests__/views/version-history-panel.render.test.js +153 -0
  124. package/dist/__tests__/views/version-history-panel.render.test.js.map +1 -0
  125. package/dist/components/BreakpointPicker.d.ts +18 -0
  126. package/dist/components/BreakpointPicker.d.ts.map +1 -0
  127. package/dist/components/BreakpointPicker.js +31 -0
  128. package/dist/components/BreakpointPicker.js.map +1 -0
  129. package/dist/components/CanvasEditTip.d.ts +6 -0
  130. package/dist/components/CanvasEditTip.d.ts.map +1 -0
  131. package/dist/components/CanvasEditTip.js +22 -0
  132. package/dist/components/CanvasEditTip.js.map +1 -0
  133. package/dist/components/EditorRail.d.ts +36 -0
  134. package/dist/components/EditorRail.d.ts.map +1 -0
  135. package/dist/components/EditorRail.js +104 -0
  136. package/dist/components/EditorRail.js.map +1 -0
  137. package/dist/components/EditorRailTabs.d.ts +32 -0
  138. package/dist/components/EditorRailTabs.d.ts.map +1 -0
  139. package/dist/components/EditorRailTabs.js +45 -0
  140. package/dist/components/EditorRailTabs.js.map +1 -0
  141. package/dist/components/MediaBlock.d.ts +49 -0
  142. package/dist/components/MediaBlock.d.ts.map +1 -0
  143. package/dist/components/MediaBlock.js +50 -0
  144. package/dist/components/MediaBlock.js.map +1 -0
  145. package/dist/components/ReflowMeter.d.ts +28 -0
  146. package/dist/components/ReflowMeter.d.ts.map +1 -0
  147. package/dist/components/ReflowMeter.js +42 -0
  148. package/dist/components/ReflowMeter.js.map +1 -0
  149. package/dist/components/ZoomChip.d.ts +17 -0
  150. package/dist/components/ZoomChip.d.ts.map +1 -0
  151. package/dist/components/ZoomChip.js +17 -0
  152. package/dist/components/ZoomChip.js.map +1 -0
  153. package/dist/components/breakpoint-icons.d.ts +3 -0
  154. package/dist/components/breakpoint-icons.d.ts.map +1 -0
  155. package/dist/components/breakpoint-icons.js +16 -0
  156. package/dist/components/breakpoint-icons.js.map +1 -0
  157. package/dist/components/page-tab/AuthorPicker.d.ts +31 -0
  158. package/dist/components/page-tab/AuthorPicker.d.ts.map +1 -0
  159. package/dist/components/page-tab/AuthorPicker.js +95 -0
  160. package/dist/components/page-tab/AuthorPicker.js.map +1 -0
  161. package/dist/components/page-tab/DirectiveSwitch.d.ts +38 -0
  162. package/dist/components/page-tab/DirectiveSwitch.d.ts.map +1 -0
  163. package/dist/components/page-tab/DirectiveSwitch.js +33 -0
  164. package/dist/components/page-tab/DirectiveSwitch.js.map +1 -0
  165. package/dist/components/seo/EditorSeoScoreBadge.d.ts +8 -1
  166. package/dist/components/seo/EditorSeoScoreBadge.d.ts.map +1 -1
  167. package/dist/components/seo/EditorSeoScoreBadge.js +2 -2
  168. package/dist/components/seo/EditorSeoScoreBadge.js.map +1 -1
  169. package/dist/components/seo/PageAuditPanel.d.ts +14 -0
  170. package/dist/components/seo/PageAuditPanel.d.ts.map +1 -0
  171. package/dist/components/seo/PageAuditPanel.js +59 -0
  172. package/dist/components/seo/PageAuditPanel.js.map +1 -0
  173. package/dist/components/seo/SeoCharCount.d.ts +19 -7
  174. package/dist/components/seo/SeoCharCount.d.ts.map +1 -1
  175. package/dist/components/seo/SeoCharCount.js +30 -16
  176. package/dist/components/seo/SeoCharCount.js.map +1 -1
  177. package/dist/components/seo/SeoEditorPane.d.ts +15 -1
  178. package/dist/components/seo/SeoEditorPane.d.ts.map +1 -1
  179. package/dist/components/seo/SeoEditorPane.js +118 -21
  180. package/dist/components/seo/SeoEditorPane.js.map +1 -1
  181. package/dist/components/seo/SerpPreview.d.ts +20 -0
  182. package/dist/components/seo/SerpPreview.d.ts.map +1 -0
  183. package/dist/components/seo/SerpPreview.js +6 -0
  184. package/dist/components/seo/SerpPreview.js.map +1 -0
  185. package/dist/hooks/usePageAudit.d.ts +41 -0
  186. package/dist/hooks/usePageAudit.d.ts.map +1 -0
  187. package/dist/hooks/usePageAudit.js +68 -0
  188. package/dist/hooks/usePageAudit.js.map +1 -0
  189. package/dist/hooks/useReflowGuard.d.ts +47 -0
  190. package/dist/hooks/useReflowGuard.d.ts.map +1 -0
  191. package/dist/hooks/useReflowGuard.js +52 -0
  192. package/dist/hooks/useReflowGuard.js.map +1 -0
  193. package/dist/layout/primitives/AdminShell.d.ts.map +1 -1
  194. package/dist/layout/primitives/AdminShell.js +2 -2
  195. package/dist/layout/primitives/AdminShell.js.map +1 -1
  196. package/dist/lib/breakpoints.d.ts +51 -0
  197. package/dist/lib/breakpoints.d.ts.map +1 -0
  198. package/dist/lib/breakpoints.js +84 -0
  199. package/dist/lib/breakpoints.js.map +1 -0
  200. package/dist/lib/canvas-edit-chrome.d.ts +22 -0
  201. package/dist/lib/canvas-edit-chrome.d.ts.map +1 -0
  202. package/dist/lib/canvas-edit-chrome.js +81 -0
  203. package/dist/lib/canvas-edit-chrome.js.map +1 -0
  204. package/dist/lib/media-format.d.ts +55 -0
  205. package/dist/lib/media-format.d.ts.map +1 -0
  206. package/dist/lib/media-format.js +93 -0
  207. package/dist/lib/media-format.js.map +1 -0
  208. package/dist/lib/page-audit-input.d.ts +45 -0
  209. package/dist/lib/page-audit-input.d.ts.map +1 -0
  210. package/dist/lib/page-audit-input.js +53 -0
  211. package/dist/lib/page-audit-input.js.map +1 -0
  212. package/dist/lib/page-editor-service.d.ts +12 -0
  213. package/dist/lib/page-editor-service.d.ts.map +1 -1
  214. package/dist/lib/page-editor-service.js +6 -0
  215. package/dist/lib/page-editor-service.js.map +1 -1
  216. package/dist/lib/page-redirect.d.ts +39 -0
  217. package/dist/lib/page-redirect.d.ts.map +1 -0
  218. package/dist/lib/page-redirect.js +38 -0
  219. package/dist/lib/page-redirect.js.map +1 -0
  220. package/dist/lib/rail-width.d.ts +23 -0
  221. package/dist/lib/rail-width.d.ts.map +1 -0
  222. package/dist/lib/rail-width.js +59 -0
  223. package/dist/lib/rail-width.js.map +1 -0
  224. package/dist/lib/reflow-baseline.d.ts +45 -0
  225. package/dist/lib/reflow-baseline.d.ts.map +1 -0
  226. package/dist/lib/reflow-baseline.js +38 -0
  227. package/dist/lib/reflow-baseline.js.map +1 -0
  228. package/dist/lib/reflow-probe.d.ts +159 -0
  229. package/dist/lib/reflow-probe.d.ts.map +1 -0
  230. package/dist/lib/reflow-probe.js +228 -0
  231. package/dist/lib/reflow-probe.js.map +1 -0
  232. package/dist/lib/seo-service.d.ts +10 -0
  233. package/dist/lib/seo-service.d.ts.map +1 -1
  234. package/dist/lib/seo-service.js.map +1 -1
  235. package/dist/lib/version-diff.d.ts +50 -0
  236. package/dist/lib/version-diff.d.ts.map +1 -0
  237. package/dist/lib/version-diff.js +294 -0
  238. package/dist/lib/version-diff.js.map +1 -0
  239. package/dist/lib/version-history.d.ts +73 -0
  240. package/dist/lib/version-history.d.ts.map +1 -0
  241. package/dist/lib/version-history.js +126 -0
  242. package/dist/lib/version-history.js.map +1 -0
  243. package/dist/prenta-admin.css +1 -1
  244. package/dist/views/page-editor/CanvasMetaLine.d.ts +19 -0
  245. package/dist/views/page-editor/CanvasMetaLine.d.ts.map +1 -0
  246. package/dist/views/page-editor/CanvasMetaLine.js +23 -0
  247. package/dist/views/page-editor/CanvasMetaLine.js.map +1 -0
  248. package/dist/views/page-editor/EditorCanvas.d.ts +47 -8
  249. package/dist/views/page-editor/EditorCanvas.d.ts.map +1 -1
  250. package/dist/views/page-editor/EditorCanvas.js +68 -34
  251. package/dist/views/page-editor/EditorCanvas.js.map +1 -1
  252. package/dist/views/page-editor/EditorTopBar.d.ts +21 -2
  253. package/dist/views/page-editor/EditorTopBar.d.ts.map +1 -1
  254. package/dist/views/page-editor/EditorTopBar.js +13 -20
  255. package/dist/views/page-editor/EditorTopBar.js.map +1 -1
  256. package/dist/views/page-editor/PageSectionEditor.d.ts.map +1 -1
  257. package/dist/views/page-editor/PageSectionEditor.js +383 -78
  258. package/dist/views/page-editor/PageSectionEditor.js.map +1 -1
  259. package/dist/views/page-editor/PageTab.d.ts +36 -0
  260. package/dist/views/page-editor/PageTab.d.ts.map +1 -0
  261. package/dist/views/page-editor/PageTab.js +65 -0
  262. package/dist/views/page-editor/PageTab.js.map +1 -0
  263. package/dist/views/page-editor/ReviewBar.d.ts +21 -0
  264. package/dist/views/page-editor/ReviewBar.d.ts.map +1 -0
  265. package/dist/views/page-editor/ReviewBar.js +22 -0
  266. package/dist/views/page-editor/ReviewBar.js.map +1 -0
  267. package/dist/views/page-editor/SectionInspector.d.ts.map +1 -1
  268. package/dist/views/page-editor/SectionInspector.js +64 -20
  269. package/dist/views/page-editor/SectionInspector.js.map +1 -1
  270. package/dist/views/page-editor/SectionsPanel.d.ts +30 -5
  271. package/dist/views/page-editor/SectionsPanel.d.ts.map +1 -1
  272. package/dist/views/page-editor/SectionsPanel.js +38 -7
  273. package/dist/views/page-editor/SectionsPanel.js.map +1 -1
  274. package/dist/views/page-editor/SitePreviewFrame.d.ts.map +1 -1
  275. package/dist/views/page-editor/SitePreviewFrame.js +3 -0
  276. package/dist/views/page-editor/SitePreviewFrame.js.map +1 -1
  277. package/dist/views/page-editor/VersionHistoryPanel.d.ts +29 -0
  278. package/dist/views/page-editor/VersionHistoryPanel.d.ts.map +1 -0
  279. package/dist/views/page-editor/VersionHistoryPanel.js +81 -0
  280. package/dist/views/page-editor/VersionHistoryPanel.js.map +1 -0
  281. package/dist/views/page-editor/reflow-context.d.ts +28 -0
  282. package/dist/views/page-editor/reflow-context.d.ts.map +1 -0
  283. package/dist/views/page-editor/reflow-context.js +14 -0
  284. package/dist/views/page-editor/reflow-context.js.map +1 -0
  285. package/dist/views/page-editor/sections/ByTheNumbersSection.d.ts.map +1 -1
  286. package/dist/views/page-editor/sections/ByTheNumbersSection.js +1 -1
  287. package/dist/views/page-editor/sections/ByTheNumbersSection.js.map +1 -1
  288. package/dist/views/page-editor/sections/CtaBannerSection.d.ts.map +1 -1
  289. package/dist/views/page-editor/sections/CtaBannerSection.js +1 -1
  290. package/dist/views/page-editor/sections/CtaBannerSection.js.map +1 -1
  291. package/dist/views/page-editor/sections/FeatureHighlightSection.d.ts.map +1 -1
  292. package/dist/views/page-editor/sections/FeatureHighlightSection.js +2 -2
  293. package/dist/views/page-editor/sections/FeatureHighlightSection.js.map +1 -1
  294. package/dist/views/page-editor/sections/GallerySection.d.ts.map +1 -1
  295. package/dist/views/page-editor/sections/GallerySection.js +7 -3
  296. package/dist/views/page-editor/sections/GallerySection.js.map +1 -1
  297. package/dist/views/page-editor/sections/HeroBannerSection.js +1 -1
  298. package/dist/views/page-editor/sections/HeroBannerSection.js.map +1 -1
  299. package/dist/views/page-editor/sections/MissionSection.d.ts.map +1 -1
  300. package/dist/views/page-editor/sections/MissionSection.js +1 -1
  301. package/dist/views/page-editor/sections/MissionSection.js.map +1 -1
  302. package/dist/views/page-editor/sections/PullQuoteSection.d.ts.map +1 -1
  303. package/dist/views/page-editor/sections/PullQuoteSection.js +1 -1
  304. package/dist/views/page-editor/sections/PullQuoteSection.js.map +1 -1
  305. package/dist/views/page-editor/sections/RelatedPostsSection.d.ts.map +1 -1
  306. package/dist/views/page-editor/sections/RelatedPostsSection.js +1 -1
  307. package/dist/views/page-editor/sections/RelatedPostsSection.js.map +1 -1
  308. package/dist/views/page-editor/sections/SectionRenderer.d.ts +13 -1
  309. package/dist/views/page-editor/sections/SectionRenderer.d.ts.map +1 -1
  310. package/dist/views/page-editor/sections/SectionRenderer.js +66 -26
  311. package/dist/views/page-editor/sections/SectionRenderer.js.map +1 -1
  312. package/dist/views/page-editor/sections/admin-renderers.d.ts +47 -15
  313. package/dist/views/page-editor/sections/admin-renderers.d.ts.map +1 -1
  314. package/dist/views/page-editor/sections/admin-renderers.js +27 -15
  315. package/dist/views/page-editor/sections/admin-renderers.js.map +1 -1
  316. package/dist/views/page-editor/sections/parts.d.ts +0 -1
  317. package/dist/views/page-editor/sections/parts.d.ts.map +1 -1
  318. package/dist/views/page-editor/sections/parts.js +41 -16
  319. package/dist/views/page-editor/sections/parts.js.map +1 -1
  320. package/dist/views/page-editor/viewports.d.ts +7 -14
  321. package/dist/views/page-editor/viewports.d.ts.map +1 -1
  322. package/dist/views/page-editor/viewports.js +7 -12
  323. package/dist/views/page-editor/viewports.js.map +1 -1
  324. package/dist/views/post-editor/PostEditorCanvas.d.ts +2 -1
  325. package/dist/views/post-editor/PostEditorCanvas.d.ts.map +1 -1
  326. package/dist/views/post-editor/PostEditorCanvas.js +5 -2
  327. package/dist/views/post-editor/PostEditorCanvas.js.map +1 -1
  328. package/dist/views/post-editor/PostSectionEditor.d.ts.map +1 -1
  329. package/dist/views/post-editor/PostSectionEditor.js +2 -20
  330. package/dist/views/post-editor/PostSectionEditor.js.map +1 -1
  331. package/dist/views/post-editor/PostTemplateEditor.d.ts.map +1 -1
  332. package/dist/views/post-editor/PostTemplateEditor.js +4 -1
  333. package/dist/views/post-editor/PostTemplateEditor.js.map +1 -1
  334. package/package.json +5 -3
  335. package/src/AdminRoot.tsx +27 -6
  336. package/src/__tests__/components/breakpoint-controls.render.test.tsx +57 -0
  337. package/src/__tests__/components/canvas-edit-tip.render.test.tsx +41 -0
  338. package/src/__tests__/components/editor-rail.render.test.tsx +192 -0
  339. package/src/__tests__/components/media-block.render.test.tsx +126 -0
  340. package/src/__tests__/components/page-audit-panel.render.test.tsx +64 -0
  341. package/src/__tests__/components/reflow-meter.render.test.tsx +144 -0
  342. package/src/__tests__/components/seo-char-count.test.tsx +26 -6
  343. package/src/__tests__/components/serp-preview.render.test.tsx +32 -0
  344. package/src/__tests__/lib/breakpoints.test.ts +84 -0
  345. package/src/__tests__/lib/canvas-edit-chrome.test.ts +56 -0
  346. package/src/__tests__/lib/media-format.test.ts +89 -0
  347. package/src/__tests__/lib/page-audit-input.test.ts +144 -0
  348. package/src/__tests__/lib/page-editor-service.test.ts +2 -0
  349. package/src/__tests__/lib/page-redirect.test.ts +29 -0
  350. package/src/__tests__/lib/rail-width.test.ts +86 -0
  351. package/src/__tests__/lib/reflow-baseline.test.ts +64 -0
  352. package/src/__tests__/lib/reflow-probe.test.ts +95 -0
  353. package/src/__tests__/lib/version-diff.test.ts +278 -0
  354. package/src/__tests__/lib/version-history.test.ts +166 -0
  355. package/src/__tests__/styles/canvas-container-queries.test.ts +103 -0
  356. package/src/__tests__/styles/packaging.test.ts +38 -0
  357. package/src/__tests__/styles/responsive-layer.test.ts +152 -0
  358. package/src/__tests__/styles/tokens-drift.test.ts +110 -0
  359. package/src/__tests__/views/admin-renderer-compat.test.ts +48 -0
  360. package/src/__tests__/views/admin-section-renderers.test.tsx +70 -4
  361. package/src/__tests__/views/canvas-review-mode.render.test.tsx +94 -0
  362. package/src/__tests__/views/editor-top-bar.render.test.tsx +43 -1
  363. package/src/__tests__/views/page-tab.render.test.tsx +226 -0
  364. package/src/__tests__/views/section-class-parity.test.ts +98 -0
  365. package/src/__tests__/views/sections-panel-design-owned.render.test.tsx +104 -0
  366. package/src/__tests__/views/version-history-panel.render.test.tsx +195 -0
  367. package/src/components/BreakpointPicker.tsx +114 -0
  368. package/src/components/CanvasEditTip.tsx +46 -0
  369. package/src/components/EditorRail.tsx +219 -0
  370. package/src/components/EditorRailTabs.tsx +117 -0
  371. package/src/components/MediaBlock.tsx +223 -0
  372. package/src/components/ReflowMeter.tsx +125 -0
  373. package/src/components/ZoomChip.tsx +39 -0
  374. package/src/components/breakpoint-icons.ts +17 -0
  375. package/src/components/page-tab/AuthorPicker.tsx +240 -0
  376. package/src/components/page-tab/DirectiveSwitch.tsx +89 -0
  377. package/src/components/seo/EditorSeoScoreBadge.tsx +9 -1
  378. package/src/components/seo/PageAuditPanel.tsx +192 -0
  379. package/src/components/seo/SeoCharCount.tsx +55 -24
  380. package/src/components/seo/SeoEditorPane.tsx +213 -31
  381. package/src/components/seo/SerpPreview.tsx +41 -0
  382. package/src/hooks/usePageAudit.ts +98 -0
  383. package/src/hooks/useReflowGuard.ts +96 -0
  384. package/src/layout/primitives/AdminShell.tsx +13 -1
  385. package/src/lib/breakpoints.ts +107 -0
  386. package/src/lib/canvas-edit-chrome.ts +82 -0
  387. package/src/lib/media-format.ts +99 -0
  388. package/src/lib/page-audit-input.ts +104 -0
  389. package/src/lib/page-editor-service.ts +18 -0
  390. package/src/lib/page-redirect.ts +60 -0
  391. package/src/lib/rail-width.ts +58 -0
  392. package/src/lib/reflow-baseline.ts +70 -0
  393. package/src/lib/reflow-probe.ts +299 -0
  394. package/src/lib/seo-service.ts +10 -0
  395. package/src/lib/version-diff.ts +376 -0
  396. package/src/lib/version-history.ts +192 -0
  397. package/src/styles/prenta-tokens.css +120 -42
  398. package/src/styles/responsive.css +281 -0
  399. package/src/styles/theme.css +138 -0
  400. package/src/views/page-editor/CanvasMetaLine.tsx +50 -0
  401. package/src/views/page-editor/EditorCanvas.tsx +140 -54
  402. package/src/views/page-editor/EditorTopBar.tsx +55 -51
  403. package/src/views/page-editor/PageSectionEditor.tsx +575 -185
  404. package/src/views/page-editor/PageTab.tsx +433 -0
  405. package/src/views/page-editor/ReviewBar.tsx +76 -0
  406. package/src/views/page-editor/SectionInspector.tsx +84 -42
  407. package/src/views/page-editor/SectionsPanel.tsx +282 -141
  408. package/src/views/page-editor/SitePreviewFrame.tsx +3 -0
  409. package/src/views/page-editor/VersionHistoryPanel.tsx +302 -0
  410. package/src/views/page-editor/reflow-context.ts +43 -0
  411. package/src/views/page-editor/sections/ByTheNumbersSection.tsx +3 -2
  412. package/src/views/page-editor/sections/CtaBannerSection.tsx +3 -1
  413. package/src/views/page-editor/sections/FeatureHighlightSection.tsx +3 -2
  414. package/src/views/page-editor/sections/GallerySection.tsx +7 -3
  415. package/src/views/page-editor/sections/HeroBannerSection.tsx +2 -2
  416. package/src/views/page-editor/sections/MissionSection.tsx +4 -2
  417. package/src/views/page-editor/sections/PullQuoteSection.tsx +3 -1
  418. package/src/views/page-editor/sections/RelatedPostsSection.tsx +2 -1
  419. package/src/views/page-editor/sections/SectionRenderer.tsx +115 -34
  420. package/src/views/page-editor/sections/admin-renderers.tsx +54 -15
  421. package/src/views/page-editor/sections/parts.tsx +46 -18
  422. package/src/views/page-editor/viewports.ts +13 -26
  423. package/src/views/post-editor/PostEditorCanvas.tsx +18 -11
  424. package/src/views/post-editor/PostSectionEditor.tsx +1 -56
  425. package/src/views/post-editor/PostTemplateEditor.tsx +3 -0
@@ -0,0 +1,114 @@
1
+ 'use client'
2
+
3
+ import { useState } from 'react'
4
+ import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
5
+ import { Check, ChevronDown } from 'lucide-react'
6
+ import { adminPortalContainer } from '../lib/portal-container.js'
7
+ import {
8
+ BREAKPOINT_GROUPS,
9
+ breakpointsInGroup,
10
+ getBreakpoint,
11
+ type BreakpointId,
12
+ } from '../lib/breakpoints.js'
13
+ import { breakpointIcon } from './breakpoint-icons.js'
14
+
15
+ export interface BreakpointPickerProps {
16
+ value: BreakpointId
17
+ onChange: (id: BreakpointId) => void
18
+ disabled?: boolean
19
+ }
20
+
21
+ /**
22
+ * Preview-breakpoint selector for the editor header.
23
+ *
24
+ * A dropdown rather than a segmented row: six presets with names like "Tablet
25
+ * Landscape" do not fit a header strip, and the grouped menu is what makes the
26
+ * DESKTOP / TABLET / MOBILE structure legible.
27
+ *
28
+ * Radix owns the menu behaviour — outside-click, Escape, roving focus, and
29
+ * `aria-expanded` on the trigger — rather than a hand-rolled popover.
30
+ */
31
+ export function BreakpointPicker({ value, onChange, disabled }: BreakpointPickerProps) {
32
+ const [open, setOpen] = useState(false)
33
+ const active = getBreakpoint(value)
34
+ const ActiveIcon = breakpointIcon(active.icon)
35
+
36
+ return (
37
+ <DropdownMenu.Root open={open} onOpenChange={setOpen}>
38
+ <DropdownMenu.Trigger asChild>
39
+ <button
40
+ type="button"
41
+ disabled={disabled}
42
+ aria-label={`Preview breakpoint: ${active.name}, ${active.width} pixels`}
43
+ className={`border-border flex h-[34px] items-center gap-1.5 rounded-lg border px-2.5 text-[12.5px] font-medium transition-colors disabled:opacity-50 ${
44
+ open
45
+ ? 'border-brand bg-accent text-accent-foreground'
46
+ : 'bg-card text-foreground hover:bg-accent'
47
+ }`}
48
+ >
49
+ <ActiveIcon className={`h-4 w-4 shrink-0 ${open ? '' : 'text-brand'}`} aria-hidden />
50
+ {/* The name is the useful label; below `lg` the header is too tight
51
+ for it, so fall back to the two-letter form rather than truncating. */}
52
+ <span className="hidden lg:inline">{active.name}</span>
53
+ <span className="lg:hidden">{active.abbr}</span>
54
+ <span className="text-muted-foreground hidden text-[11.5px] tabular-nums xl:inline">
55
+ {active.width}
56
+ </span>
57
+ <ChevronDown
58
+ className={`h-3.5 w-3.5 shrink-0 transition-transform ${open ? 'rotate-180' : ''}`}
59
+ aria-hidden
60
+ />
61
+ </button>
62
+ </DropdownMenu.Trigger>
63
+
64
+ <DropdownMenu.Portal container={adminPortalContainer()}>
65
+ <DropdownMenu.Content
66
+ align="end"
67
+ sideOffset={6}
68
+ className="border-border bg-popover data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:slide-in-from-top-1 z-[120] w-[272px] rounded-[10px] border p-[5px] shadow-[0_12px_34px_rgba(26,24,39,0.17)] duration-[130ms]"
69
+ >
70
+ {BREAKPOINT_GROUPS.map((group) => (
71
+ <DropdownMenu.Group key={group}>
72
+ <DropdownMenu.Label className="text-muted-foreground px-2.5 pt-2 pb-1 text-[10px] font-bold tracking-[0.6px]">
73
+ {group}
74
+ </DropdownMenu.Label>
75
+ {breakpointsInGroup(group).map((bp) => {
76
+ const Icon = breakpointIcon(bp.icon)
77
+ const selected = bp.id === value
78
+ return (
79
+ <DropdownMenu.Item
80
+ key={bp.id}
81
+ onSelect={() => onChange(bp.id)}
82
+ className={`focus:bg-accent flex cursor-pointer items-center gap-2 rounded-[7px] px-2.5 py-[7px] outline-none ${
83
+ selected ? 'bg-accent' : ''
84
+ }`}
85
+ >
86
+ <Icon
87
+ className={`h-4 w-4 shrink-0 ${selected ? 'text-brand' : 'text-muted-foreground'}`}
88
+ aria-hidden
89
+ />
90
+ <span
91
+ className={`flex-1 text-[13px] ${selected ? 'text-brand font-semibold' : 'text-foreground'}`}
92
+ >
93
+ {bp.name}
94
+ </span>
95
+ <span className="text-muted-foreground text-[11.5px] tabular-nums">
96
+ {bp.width}
97
+ </span>
98
+ {/* Not aria-hidden: the check is the only thing marking the
99
+ current selection, and Radix menu items expose no
100
+ selected state of their own. */}
101
+ <Check
102
+ className={`text-brand h-3.5 w-3.5 shrink-0 ${selected ? '' : 'invisible'}`}
103
+ aria-label={selected ? 'Selected' : undefined}
104
+ />
105
+ </DropdownMenu.Item>
106
+ )
107
+ })}
108
+ </DropdownMenu.Group>
109
+ ))}
110
+ </DropdownMenu.Content>
111
+ </DropdownMenu.Portal>
112
+ </DropdownMenu.Root>
113
+ )
114
+ }
@@ -0,0 +1,46 @@
1
+ 'use client'
2
+
3
+ import { useEffect, useState } from 'react'
4
+ import { MousePointerClick, X } from 'lucide-react'
5
+ import { dismissCanvasEditTip, isCanvasEditTipDismissed } from '../lib/canvas-edit-chrome.js'
6
+
7
+ /**
8
+ * One-time tip above the page/post canvas explaining how to edit.
9
+ * Dismiss persists in localStorage (`prenta.canvasEditTip.dismissed`).
10
+ */
11
+ export function CanvasEditTip() {
12
+ const [visible, setVisible] = useState(false)
13
+
14
+ useEffect(() => {
15
+ setVisible(!isCanvasEditTipDismissed())
16
+ }, [])
17
+
18
+ if (!visible) return null
19
+
20
+ return (
21
+ <div
22
+ role="note"
23
+ className="border-border bg-card text-muted-foreground mx-auto mb-4 flex w-fit max-w-full items-start gap-2 rounded-lg border px-3 py-2 text-xs"
24
+ >
25
+ <MousePointerClick className="text-foreground mt-0.5 h-3.5 w-3.5 shrink-0" aria-hidden />
26
+ <p className="min-w-0 flex-1 leading-[1.5]">
27
+ <span className="text-foreground font-medium">Editing tip</span>
28
+ {' — '}
29
+ Hover sections for a light outline, then click to select. Edit copy and images in the
30
+ inspector. Use <span className="text-foreground font-medium">Site preview</span> to click
31
+ text and images in place.
32
+ </p>
33
+ <button
34
+ type="button"
35
+ aria-label="Dismiss editing tip"
36
+ onClick={() => {
37
+ dismissCanvasEditTip()
38
+ setVisible(false)
39
+ }}
40
+ className="text-muted-foreground hover:bg-accent hover:text-foreground focus-visible:ring-ring -mt-0.5 -mr-0.5 shrink-0 rounded-md p-1 transition-colors focus-visible:ring-2 focus-visible:outline-none"
41
+ >
42
+ <X className="h-3.5 w-3.5" aria-hidden />
43
+ </button>
44
+ </div>
45
+ )
46
+ }
@@ -0,0 +1,219 @@
1
+ 'use client'
2
+
3
+ import { useCallback, useEffect, useRef, useState } from 'react'
4
+ import { PanelLeftClose, PanelLeftOpen } from 'lucide-react'
5
+ import {
6
+ RAIL_COLLAPSED_WIDTH,
7
+ RAIL_DEFAULT_WIDTH,
8
+ RAIL_MAX_WIDTH,
9
+ RAIL_MIN_WIDTH,
10
+ clampRailWidth,
11
+ readRailWidth,
12
+ writeRailWidth,
13
+ } from '../lib/rail-width.js'
14
+
15
+ /** What the rail hands its children so they can render a header. */
16
+ export interface RailState {
17
+ width: number
18
+ dragging: boolean
19
+ collapse: () => void
20
+ }
21
+
22
+ export interface EditorRailProps {
23
+ /** Short label for the collapsed strip — normally the active tab's name. */
24
+ collapsedLabel: string
25
+ children: (state: RailState) => React.ReactNode
26
+ }
27
+
28
+ /**
29
+ * The editor's left rail: resizable, collapsible, and width-persistent.
30
+ *
31
+ * Collapsing UNMOUNTS the rail and swaps in a narrow strip rather than
32
+ * animating its width to zero. A zero-width panel leaves a visible sliver of
33
+ * border and keeps its contents in the accessibility tree, so a screen-reader
34
+ * user lands inside a panel sighted users cannot see.
35
+ *
36
+ * Children are a render prop rather than plain nodes because the panel header
37
+ * needs the live width and drag state — the numbers the rail owns — and
38
+ * threading them through context for one consumer is more machinery than the
39
+ * problem deserves.
40
+ */
41
+ export function EditorRail({ collapsedLabel, children }: EditorRailProps) {
42
+ const [width, setWidth] = useState(RAIL_DEFAULT_WIDTH)
43
+ const [open, setOpen] = useState(true)
44
+ const [dragging, setDragging] = useState(false)
45
+ const railRef = useRef<HTMLDivElement>(null)
46
+
47
+ // Read persisted width after mount, never during render: there is no
48
+ // localStorage on the server, and seeding initial state from it would
49
+ // hydrate-mismatch.
50
+ useEffect(() => {
51
+ setWidth(readRailWidth())
52
+ }, [])
53
+
54
+ const applyWidth = useCallback((next: number) => {
55
+ const clamped = clampRailWidth(next)
56
+ setWidth(clamped)
57
+ writeRailWidth(clamped)
58
+ }, [])
59
+
60
+ const collapse = useCallback(() => setOpen(false), [])
61
+
62
+ // Pointer listeners go on `window`, not the handle: the pointer routinely
63
+ // outruns a 7px target mid-drag, and a handle-local listener would drop the
64
+ // drag the moment it did.
65
+ useEffect(() => {
66
+ if (!dragging) return
67
+
68
+ const onMove = (event: PointerEvent) => {
69
+ const left = railRef.current?.getBoundingClientRect().left ?? 0
70
+ applyWidth(event.clientX - left)
71
+ }
72
+ const stop = () => setDragging(false)
73
+
74
+ window.addEventListener('pointermove', onMove)
75
+ window.addEventListener('pointerup', stop)
76
+ window.addEventListener('pointercancel', stop)
77
+
78
+ // Lock cursor and selection for the duration. Without this the cursor
79
+ // flickers between col-resize and text as it crosses elements, and the
80
+ // drag selects the panel's copy.
81
+ const { body } = document
82
+ const prevCursor = body.style.cursor
83
+ const prevSelect = body.style.userSelect
84
+ body.style.cursor = 'col-resize'
85
+ body.style.userSelect = 'none'
86
+
87
+ return () => {
88
+ window.removeEventListener('pointermove', onMove)
89
+ window.removeEventListener('pointerup', stop)
90
+ window.removeEventListener('pointercancel', stop)
91
+ body.style.cursor = prevCursor
92
+ body.style.userSelect = prevSelect
93
+ }
94
+ }, [dragging, applyWidth])
95
+
96
+ if (!open) {
97
+ return (
98
+ <div
99
+ className="prenta-editor-rail-collapsed bg-card border-border flex shrink-0 flex-col items-center gap-3 border-r py-3"
100
+ style={{ width: RAIL_COLLAPSED_WIDTH }}
101
+ >
102
+ <button
103
+ type="button"
104
+ onClick={() => setOpen(true)}
105
+ aria-label={`Expand ${collapsedLabel} panel`}
106
+ title={`Expand ${collapsedLabel} panel`}
107
+ className="prenta-touch-target text-muted-foreground hover:bg-accent hover:text-accent-foreground flex h-6.5 w-6.5 items-center justify-center rounded-md transition-colors"
108
+ >
109
+ <PanelLeftOpen className="h-4 w-4" aria-hidden />
110
+ </button>
111
+ {/* Vertical label so the strip still says which panel is hidden. The
112
+ button's accessible name already carries it, hence aria-hidden. */}
113
+ <span
114
+ className="prenta-rail-label text-muted-foreground text-[10px] font-bold tracking-[0.8px] whitespace-nowrap uppercase [writing-mode:vertical-rl]"
115
+ aria-hidden
116
+ >
117
+ {collapsedLabel}
118
+ </span>
119
+ </div>
120
+ )
121
+ }
122
+
123
+ return (
124
+ <div
125
+ ref={railRef}
126
+ className={`prenta-editor-rail bg-card border-border relative flex shrink-0 flex-col border-r ${
127
+ // A width transition during a drag fights the pointer: the panel
128
+ // chases the cursor a frame behind and the resize feels broken.
129
+ dragging ? '' : 'motion-safe:transition-[width] motion-safe:duration-(--mo-slow)'
130
+ }`}
131
+ style={{ width }}
132
+ >
133
+ <div className="flex min-h-0 flex-1 flex-col">{children({ width, dragging, collapse })}</div>
134
+
135
+ <div
136
+ // `separator` with an orientation and a value range is what makes this
137
+ // operable without a pointer. A bare div with a pointerdown handler is
138
+ // invisible to keyboard and assistive-tech users.
139
+ role="separator"
140
+ aria-orientation="vertical"
141
+ aria-label="Resize panel"
142
+ aria-valuenow={width}
143
+ aria-valuemin={RAIL_MIN_WIDTH}
144
+ aria-valuemax={RAIL_MAX_WIDTH}
145
+ tabIndex={0}
146
+ onPointerDown={(event) => {
147
+ event.preventDefault()
148
+ setDragging(true)
149
+ }}
150
+ onDoubleClick={() => applyWidth(RAIL_DEFAULT_WIDTH)}
151
+ onKeyDown={(event) => {
152
+ const step = event.shiftKey ? 48 : 16
153
+ if (event.key === 'ArrowLeft') {
154
+ event.preventDefault()
155
+ applyWidth(width - step)
156
+ } else if (event.key === 'ArrowRight') {
157
+ event.preventDefault()
158
+ applyWidth(width + step)
159
+ } else if (event.key === 'Home') {
160
+ event.preventDefault()
161
+ applyWidth(RAIL_DEFAULT_WIDTH)
162
+ }
163
+ }}
164
+ className="group absolute inset-y-0 -right-[3px] z-30 w-[7px] cursor-col-resize focus-visible:outline-none"
165
+ >
166
+ {/* The 2px line it draws sits inside a 7px hit area on purpose — a 2px
167
+ pointer target is a coin flip. */}
168
+ <span
169
+ aria-hidden
170
+ className={`pointer-events-none absolute inset-y-0 left-[2.5px] w-0.5 transition-colors ${
171
+ dragging
172
+ ? 'bg-brand'
173
+ : 'group-hover:bg-brand group-focus-visible:bg-brand bg-transparent'
174
+ }`}
175
+ />
176
+ </div>
177
+ </div>
178
+ )
179
+ }
180
+
181
+ /**
182
+ * Header strip under the rail's tabs: panel title, the live width while
183
+ * dragging, and the collapse control.
184
+ */
185
+ export function RailHeader({
186
+ title,
187
+ width,
188
+ dragging,
189
+ onCollapse,
190
+ }: {
191
+ title: string
192
+ width: number
193
+ dragging: boolean
194
+ onCollapse: () => void
195
+ }) {
196
+ return (
197
+ <div className="border-border flex items-center justify-between gap-2 border-b px-3.5 py-2.5">
198
+ <h2 className="text-foreground truncate text-[12.5px] font-semibold">{title}</h2>
199
+ <div className="flex shrink-0 items-center gap-1.5">
200
+ {/* Only while dragging: a permanent pixel readout is noise, but during
201
+ a resize it is the one number the user is actually aiming at. */}
202
+ {dragging && (
203
+ <span className="text-muted-foreground text-[11px] tabular-nums" aria-hidden>
204
+ {width}px
205
+ </span>
206
+ )}
207
+ <button
208
+ type="button"
209
+ onClick={onCollapse}
210
+ aria-label={`Collapse ${title} panel`}
211
+ title={`Collapse ${title} panel`}
212
+ className="prenta-touch-target text-muted-foreground hover:bg-accent hover:text-accent-foreground flex h-6.5 w-6.5 items-center justify-center rounded-md transition-colors"
213
+ >
214
+ <PanelLeftClose className="h-4 w-4" aria-hidden />
215
+ </button>
216
+ </div>
217
+ </div>
218
+ )
219
+ }
@@ -0,0 +1,117 @@
1
+ 'use client'
2
+
3
+ import { scoreBand } from '@prenta/core/seo/page-audit'
4
+
5
+ export type RailTabId = 'sections' | 'seo' | 'page'
6
+
7
+ export interface RailTab {
8
+ id: RailTabId
9
+ label: string
10
+ /** Panel title shown in the header below the strip. */
11
+ title: string
12
+ /** Count pill (Sections). */
13
+ count?: number
14
+ /**
15
+ * Score for the dot, banded by the page audit's own scale.
16
+ *
17
+ * `undefined` — not known yet; no dot is shown rather than a misleading one.
18
+ * `null` — deliberately not rated (a gate is off, e.g. the page is
19
+ * noindexed). Renders neutral, never as a low score.
20
+ */
21
+ score?: number | null
22
+ }
23
+
24
+ export interface EditorRailTabsProps {
25
+ tabs: RailTab[]
26
+ active: RailTabId
27
+ onChange: (id: RailTabId) => void
28
+ }
29
+
30
+ /**
31
+ * Three equal tabs across the top of the rail.
32
+ *
33
+ * Each tab carries a status glyph so the rail reads at a glance without being
34
+ * opened: a count for Sections, a band-coloured dot for SEO. The dot is
35
+ * accompanied by text in the accessible name — colour alone is not a signal
36
+ * for anyone who cannot distinguish these hues.
37
+ */
38
+ export function EditorRailTabs({ tabs, active, onChange }: EditorRailTabsProps) {
39
+ return (
40
+ <div role="tablist" aria-label="Editor panels" className="border-border flex border-b">
41
+ {tabs.map((tab) => {
42
+ const selected = tab.id === active
43
+ // Bands with `scoreBand` from the audit itself, NOT `gradeFromScore`.
44
+ // The two disagree between 55 and 59 — a page scoring 57 read "fair"
45
+ // in the rings and "poor" on the tab summarising them, which is the
46
+ // same class of disagreement this dot was rewired to eliminate.
47
+ const band = typeof tab.score === 'number' ? scoreBand(tab.score) : null
48
+ const unrated = tab.score === null
49
+
50
+ return (
51
+ <button
52
+ key={tab.id}
53
+ type="button"
54
+ role="tab"
55
+ id={`rail-tab-${tab.id}`}
56
+ aria-selected={selected}
57
+ aria-controls={`rail-panel-${tab.id}`}
58
+ tabIndex={selected ? 0 : -1}
59
+ onClick={() => onChange(tab.id)}
60
+ onKeyDown={(event) => {
61
+ // Arrow keys move between tabs — expected of a tablist, and the
62
+ // only way to reach a tab whose tabIndex is -1.
63
+ const index = tabs.findIndex((t) => t.id === active)
64
+ if (event.key === 'ArrowRight' || event.key === 'ArrowLeft') {
65
+ event.preventDefault()
66
+ const delta = event.key === 'ArrowRight' ? 1 : -1
67
+ const next = tabs[(index + delta + tabs.length) % tabs.length]
68
+ if (next) {
69
+ onChange(next.id)
70
+ document.getElementById(`rail-tab-${next.id}`)?.focus()
71
+ }
72
+ }
73
+ }}
74
+ className={`flex flex-1 items-center justify-center gap-1.5 border-b-2 pt-2.5 pb-2 text-[12.5px] transition-colors ${
75
+ selected
76
+ ? 'border-brand text-brand font-semibold'
77
+ : 'text-muted-foreground hover:text-foreground border-transparent font-medium'
78
+ }`}
79
+ >
80
+ {tab.label}
81
+
82
+ {tab.count !== undefined && (
83
+ <span
84
+ className={`rounded-full px-1.5 text-[10.5px] font-semibold tabular-nums ${
85
+ selected ? 'bg-accent text-accent-foreground' : 'bg-muted text-muted-foreground'
86
+ }`}
87
+ >
88
+ {tab.count}
89
+ </span>
90
+ )}
91
+
92
+ {(band || unrated) && (
93
+ <>
94
+ <span
95
+ className={`h-[7px] w-[7px] shrink-0 rounded-full ${
96
+ band === 'good'
97
+ ? 'bg-success'
98
+ : band === 'fair'
99
+ ? 'bg-warning'
100
+ : band === 'poor'
101
+ ? 'bg-destructive'
102
+ : 'bg-muted-foreground/40'
103
+ }`}
104
+ aria-hidden
105
+ />
106
+ {/* Colour alone cannot carry the score. */}
107
+ <span className="sr-only">
108
+ {unrated ? 'not rated' : `score ${tab.score} of 100`}
109
+ </span>
110
+ </>
111
+ )}
112
+ </button>
113
+ )
114
+ })}
115
+ </div>
116
+ )
117
+ }