@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.
- package/CHANGELOG.md +663 -0
- package/design-system/tokens.css +173 -71
- package/design-system/tokens.json +244 -63
- package/design-system/tokens.scss +203 -98
- package/design-system/tokens.tailwind.cjs +93 -38
- package/dist/AdminRoot.d.ts +27 -6
- package/dist/AdminRoot.d.ts.map +1 -1
- package/dist/AdminRoot.js.map +1 -1
- package/dist/__tests__/components/breakpoint-controls.render.test.d.ts +2 -0
- package/dist/__tests__/components/breakpoint-controls.render.test.d.ts.map +1 -0
- package/dist/__tests__/components/breakpoint-controls.render.test.js +51 -0
- package/dist/__tests__/components/breakpoint-controls.render.test.js.map +1 -0
- package/dist/__tests__/components/canvas-edit-tip.render.test.d.ts +2 -0
- package/dist/__tests__/components/canvas-edit-tip.render.test.d.ts.map +1 -0
- package/dist/__tests__/components/canvas-edit-tip.render.test.js +37 -0
- package/dist/__tests__/components/canvas-edit-tip.render.test.js.map +1 -0
- package/dist/__tests__/components/editor-rail.render.test.d.ts +2 -0
- package/dist/__tests__/components/editor-rail.render.test.d.ts.map +1 -0
- package/dist/__tests__/components/editor-rail.render.test.js +138 -0
- package/dist/__tests__/components/editor-rail.render.test.js.map +1 -0
- package/dist/__tests__/components/media-block.render.test.d.ts +2 -0
- package/dist/__tests__/components/media-block.render.test.d.ts.map +1 -0
- package/dist/__tests__/components/media-block.render.test.js +108 -0
- package/dist/__tests__/components/media-block.render.test.js.map +1 -0
- package/dist/__tests__/components/page-audit-panel.render.test.d.ts +2 -0
- package/dist/__tests__/components/page-audit-panel.render.test.d.ts.map +1 -0
- package/dist/__tests__/components/page-audit-panel.render.test.js +59 -0
- package/dist/__tests__/components/page-audit-panel.render.test.js.map +1 -0
- package/dist/__tests__/components/reflow-meter.render.test.d.ts +2 -0
- package/dist/__tests__/components/reflow-meter.render.test.d.ts.map +1 -0
- package/dist/__tests__/components/reflow-meter.render.test.js +94 -0
- package/dist/__tests__/components/reflow-meter.render.test.js.map +1 -0
- package/dist/__tests__/components/seo-char-count.test.js +24 -6
- package/dist/__tests__/components/seo-char-count.test.js.map +1 -1
- package/dist/__tests__/components/serp-preview.render.test.d.ts +2 -0
- package/dist/__tests__/components/serp-preview.render.test.d.ts.map +1 -0
- package/dist/__tests__/components/serp-preview.render.test.js +25 -0
- package/dist/__tests__/components/serp-preview.render.test.js.map +1 -0
- package/dist/__tests__/lib/breakpoints.test.d.ts +2 -0
- package/dist/__tests__/lib/breakpoints.test.d.ts.map +1 -0
- package/dist/__tests__/lib/breakpoints.test.js +67 -0
- package/dist/__tests__/lib/breakpoints.test.js.map +1 -0
- package/dist/__tests__/lib/canvas-edit-chrome.test.d.ts +2 -0
- package/dist/__tests__/lib/canvas-edit-chrome.test.d.ts.map +1 -0
- package/dist/__tests__/lib/canvas-edit-chrome.test.js +44 -0
- package/dist/__tests__/lib/canvas-edit-chrome.test.js.map +1 -0
- package/dist/__tests__/lib/media-format.test.d.ts +2 -0
- package/dist/__tests__/lib/media-format.test.d.ts.map +1 -0
- package/dist/__tests__/lib/media-format.test.js +70 -0
- package/dist/__tests__/lib/media-format.test.js.map +1 -0
- package/dist/__tests__/lib/page-audit-input.test.d.ts +2 -0
- package/dist/__tests__/lib/page-audit-input.test.d.ts.map +1 -0
- package/dist/__tests__/lib/page-audit-input.test.js +122 -0
- package/dist/__tests__/lib/page-audit-input.test.js.map +1 -0
- package/dist/__tests__/lib/page-editor-service.test.js +2 -0
- package/dist/__tests__/lib/page-editor-service.test.js.map +1 -1
- package/dist/__tests__/lib/page-redirect.test.d.ts +2 -0
- package/dist/__tests__/lib/page-redirect.test.d.ts.map +1 -0
- package/dist/__tests__/lib/page-redirect.test.js +26 -0
- package/dist/__tests__/lib/page-redirect.test.js.map +1 -0
- package/dist/__tests__/lib/rail-width.test.d.ts +2 -0
- package/dist/__tests__/lib/rail-width.test.d.ts.map +1 -0
- package/dist/__tests__/lib/rail-width.test.js +69 -0
- package/dist/__tests__/lib/rail-width.test.js.map +1 -0
- package/dist/__tests__/lib/reflow-baseline.test.d.ts +2 -0
- package/dist/__tests__/lib/reflow-baseline.test.d.ts.map +1 -0
- package/dist/__tests__/lib/reflow-baseline.test.js +56 -0
- package/dist/__tests__/lib/reflow-baseline.test.js.map +1 -0
- package/dist/__tests__/lib/reflow-probe.test.d.ts +2 -0
- package/dist/__tests__/lib/reflow-probe.test.d.ts.map +1 -0
- package/dist/__tests__/lib/reflow-probe.test.js +73 -0
- package/dist/__tests__/lib/reflow-probe.test.js.map +1 -0
- package/dist/__tests__/lib/version-diff.test.d.ts +2 -0
- package/dist/__tests__/lib/version-diff.test.d.ts.map +1 -0
- package/dist/__tests__/lib/version-diff.test.js +230 -0
- package/dist/__tests__/lib/version-diff.test.js.map +1 -0
- package/dist/__tests__/lib/version-history.test.d.ts +2 -0
- package/dist/__tests__/lib/version-history.test.d.ts.map +1 -0
- package/dist/__tests__/lib/version-history.test.js +125 -0
- package/dist/__tests__/lib/version-history.test.js.map +1 -0
- package/dist/__tests__/styles/canvas-container-queries.test.d.ts +2 -0
- package/dist/__tests__/styles/canvas-container-queries.test.d.ts.map +1 -0
- package/dist/__tests__/styles/canvas-container-queries.test.js +87 -0
- package/dist/__tests__/styles/canvas-container-queries.test.js.map +1 -0
- package/dist/__tests__/styles/packaging.test.d.ts +2 -0
- package/dist/__tests__/styles/packaging.test.d.ts.map +1 -0
- package/dist/__tests__/styles/packaging.test.js +31 -0
- package/dist/__tests__/styles/packaging.test.js.map +1 -0
- package/dist/__tests__/styles/responsive-layer.test.d.ts +2 -0
- package/dist/__tests__/styles/responsive-layer.test.d.ts.map +1 -0
- package/dist/__tests__/styles/responsive-layer.test.js +127 -0
- package/dist/__tests__/styles/responsive-layer.test.js.map +1 -0
- package/dist/__tests__/styles/tokens-drift.test.d.ts +2 -0
- package/dist/__tests__/styles/tokens-drift.test.d.ts.map +1 -0
- package/dist/__tests__/styles/tokens-drift.test.js +92 -0
- package/dist/__tests__/styles/tokens-drift.test.js.map +1 -0
- package/dist/__tests__/views/admin-renderer-compat.test.d.ts +2 -0
- package/dist/__tests__/views/admin-renderer-compat.test.d.ts.map +1 -0
- package/dist/__tests__/views/admin-renderer-compat.test.js +43 -0
- package/dist/__tests__/views/admin-renderer-compat.test.js.map +1 -0
- package/dist/__tests__/views/admin-section-renderers.test.js +52 -4
- package/dist/__tests__/views/admin-section-renderers.test.js.map +1 -1
- package/dist/__tests__/views/canvas-review-mode.render.test.d.ts +2 -0
- package/dist/__tests__/views/canvas-review-mode.render.test.d.ts.map +1 -0
- package/dist/__tests__/views/canvas-review-mode.render.test.js +70 -0
- package/dist/__tests__/views/canvas-review-mode.render.test.js.map +1 -0
- package/dist/__tests__/views/editor-top-bar.render.test.js +30 -1
- package/dist/__tests__/views/editor-top-bar.render.test.js.map +1 -1
- package/dist/__tests__/views/page-tab.render.test.d.ts +2 -0
- package/dist/__tests__/views/page-tab.render.test.d.ts.map +1 -0
- package/dist/__tests__/views/page-tab.render.test.js +151 -0
- package/dist/__tests__/views/page-tab.render.test.js.map +1 -0
- package/dist/__tests__/views/section-class-parity.test.d.ts +2 -0
- package/dist/__tests__/views/section-class-parity.test.d.ts.map +1 -0
- package/dist/__tests__/views/section-class-parity.test.js +81 -0
- package/dist/__tests__/views/section-class-parity.test.js.map +1 -0
- package/dist/__tests__/views/sections-panel-design-owned.render.test.d.ts +2 -0
- package/dist/__tests__/views/sections-panel-design-owned.render.test.d.ts.map +1 -0
- package/dist/__tests__/views/sections-panel-design-owned.render.test.js +86 -0
- package/dist/__tests__/views/sections-panel-design-owned.render.test.js.map +1 -0
- package/dist/__tests__/views/version-history-panel.render.test.d.ts +2 -0
- package/dist/__tests__/views/version-history-panel.render.test.d.ts.map +1 -0
- package/dist/__tests__/views/version-history-panel.render.test.js +153 -0
- package/dist/__tests__/views/version-history-panel.render.test.js.map +1 -0
- package/dist/components/BreakpointPicker.d.ts +18 -0
- package/dist/components/BreakpointPicker.d.ts.map +1 -0
- package/dist/components/BreakpointPicker.js +31 -0
- package/dist/components/BreakpointPicker.js.map +1 -0
- package/dist/components/CanvasEditTip.d.ts +6 -0
- package/dist/components/CanvasEditTip.d.ts.map +1 -0
- package/dist/components/CanvasEditTip.js +22 -0
- package/dist/components/CanvasEditTip.js.map +1 -0
- package/dist/components/EditorRail.d.ts +36 -0
- package/dist/components/EditorRail.d.ts.map +1 -0
- package/dist/components/EditorRail.js +104 -0
- package/dist/components/EditorRail.js.map +1 -0
- package/dist/components/EditorRailTabs.d.ts +32 -0
- package/dist/components/EditorRailTabs.d.ts.map +1 -0
- package/dist/components/EditorRailTabs.js +45 -0
- package/dist/components/EditorRailTabs.js.map +1 -0
- package/dist/components/MediaBlock.d.ts +49 -0
- package/dist/components/MediaBlock.d.ts.map +1 -0
- package/dist/components/MediaBlock.js +50 -0
- package/dist/components/MediaBlock.js.map +1 -0
- package/dist/components/ReflowMeter.d.ts +28 -0
- package/dist/components/ReflowMeter.d.ts.map +1 -0
- package/dist/components/ReflowMeter.js +42 -0
- package/dist/components/ReflowMeter.js.map +1 -0
- package/dist/components/ZoomChip.d.ts +17 -0
- package/dist/components/ZoomChip.d.ts.map +1 -0
- package/dist/components/ZoomChip.js +17 -0
- package/dist/components/ZoomChip.js.map +1 -0
- package/dist/components/breakpoint-icons.d.ts +3 -0
- package/dist/components/breakpoint-icons.d.ts.map +1 -0
- package/dist/components/breakpoint-icons.js +16 -0
- package/dist/components/breakpoint-icons.js.map +1 -0
- package/dist/components/page-tab/AuthorPicker.d.ts +31 -0
- package/dist/components/page-tab/AuthorPicker.d.ts.map +1 -0
- package/dist/components/page-tab/AuthorPicker.js +95 -0
- package/dist/components/page-tab/AuthorPicker.js.map +1 -0
- package/dist/components/page-tab/DirectiveSwitch.d.ts +38 -0
- package/dist/components/page-tab/DirectiveSwitch.d.ts.map +1 -0
- package/dist/components/page-tab/DirectiveSwitch.js +33 -0
- package/dist/components/page-tab/DirectiveSwitch.js.map +1 -0
- package/dist/components/seo/EditorSeoScoreBadge.d.ts +8 -1
- package/dist/components/seo/EditorSeoScoreBadge.d.ts.map +1 -1
- package/dist/components/seo/EditorSeoScoreBadge.js +2 -2
- package/dist/components/seo/EditorSeoScoreBadge.js.map +1 -1
- package/dist/components/seo/PageAuditPanel.d.ts +14 -0
- package/dist/components/seo/PageAuditPanel.d.ts.map +1 -0
- package/dist/components/seo/PageAuditPanel.js +59 -0
- package/dist/components/seo/PageAuditPanel.js.map +1 -0
- package/dist/components/seo/SeoCharCount.d.ts +19 -7
- package/dist/components/seo/SeoCharCount.d.ts.map +1 -1
- package/dist/components/seo/SeoCharCount.js +30 -16
- package/dist/components/seo/SeoCharCount.js.map +1 -1
- package/dist/components/seo/SeoEditorPane.d.ts +15 -1
- package/dist/components/seo/SeoEditorPane.d.ts.map +1 -1
- package/dist/components/seo/SeoEditorPane.js +118 -21
- package/dist/components/seo/SeoEditorPane.js.map +1 -1
- package/dist/components/seo/SerpPreview.d.ts +20 -0
- package/dist/components/seo/SerpPreview.d.ts.map +1 -0
- package/dist/components/seo/SerpPreview.js +6 -0
- package/dist/components/seo/SerpPreview.js.map +1 -0
- package/dist/hooks/usePageAudit.d.ts +41 -0
- package/dist/hooks/usePageAudit.d.ts.map +1 -0
- package/dist/hooks/usePageAudit.js +68 -0
- package/dist/hooks/usePageAudit.js.map +1 -0
- package/dist/hooks/useReflowGuard.d.ts +47 -0
- package/dist/hooks/useReflowGuard.d.ts.map +1 -0
- package/dist/hooks/useReflowGuard.js +52 -0
- package/dist/hooks/useReflowGuard.js.map +1 -0
- package/dist/layout/primitives/AdminShell.d.ts.map +1 -1
- package/dist/layout/primitives/AdminShell.js +2 -2
- package/dist/layout/primitives/AdminShell.js.map +1 -1
- package/dist/lib/breakpoints.d.ts +51 -0
- package/dist/lib/breakpoints.d.ts.map +1 -0
- package/dist/lib/breakpoints.js +84 -0
- package/dist/lib/breakpoints.js.map +1 -0
- package/dist/lib/canvas-edit-chrome.d.ts +22 -0
- package/dist/lib/canvas-edit-chrome.d.ts.map +1 -0
- package/dist/lib/canvas-edit-chrome.js +81 -0
- package/dist/lib/canvas-edit-chrome.js.map +1 -0
- package/dist/lib/media-format.d.ts +55 -0
- package/dist/lib/media-format.d.ts.map +1 -0
- package/dist/lib/media-format.js +93 -0
- package/dist/lib/media-format.js.map +1 -0
- package/dist/lib/page-audit-input.d.ts +45 -0
- package/dist/lib/page-audit-input.d.ts.map +1 -0
- package/dist/lib/page-audit-input.js +53 -0
- package/dist/lib/page-audit-input.js.map +1 -0
- package/dist/lib/page-editor-service.d.ts +12 -0
- package/dist/lib/page-editor-service.d.ts.map +1 -1
- package/dist/lib/page-editor-service.js +6 -0
- package/dist/lib/page-editor-service.js.map +1 -1
- package/dist/lib/page-redirect.d.ts +39 -0
- package/dist/lib/page-redirect.d.ts.map +1 -0
- package/dist/lib/page-redirect.js +38 -0
- package/dist/lib/page-redirect.js.map +1 -0
- package/dist/lib/rail-width.d.ts +23 -0
- package/dist/lib/rail-width.d.ts.map +1 -0
- package/dist/lib/rail-width.js +59 -0
- package/dist/lib/rail-width.js.map +1 -0
- package/dist/lib/reflow-baseline.d.ts +45 -0
- package/dist/lib/reflow-baseline.d.ts.map +1 -0
- package/dist/lib/reflow-baseline.js +38 -0
- package/dist/lib/reflow-baseline.js.map +1 -0
- package/dist/lib/reflow-probe.d.ts +159 -0
- package/dist/lib/reflow-probe.d.ts.map +1 -0
- package/dist/lib/reflow-probe.js +228 -0
- package/dist/lib/reflow-probe.js.map +1 -0
- package/dist/lib/seo-service.d.ts +10 -0
- package/dist/lib/seo-service.d.ts.map +1 -1
- package/dist/lib/seo-service.js.map +1 -1
- package/dist/lib/version-diff.d.ts +50 -0
- package/dist/lib/version-diff.d.ts.map +1 -0
- package/dist/lib/version-diff.js +294 -0
- package/dist/lib/version-diff.js.map +1 -0
- package/dist/lib/version-history.d.ts +73 -0
- package/dist/lib/version-history.d.ts.map +1 -0
- package/dist/lib/version-history.js +126 -0
- package/dist/lib/version-history.js.map +1 -0
- package/dist/prenta-admin.css +1 -1
- package/dist/views/page-editor/CanvasMetaLine.d.ts +19 -0
- package/dist/views/page-editor/CanvasMetaLine.d.ts.map +1 -0
- package/dist/views/page-editor/CanvasMetaLine.js +23 -0
- package/dist/views/page-editor/CanvasMetaLine.js.map +1 -0
- package/dist/views/page-editor/EditorCanvas.d.ts +47 -8
- package/dist/views/page-editor/EditorCanvas.d.ts.map +1 -1
- package/dist/views/page-editor/EditorCanvas.js +68 -34
- package/dist/views/page-editor/EditorCanvas.js.map +1 -1
- package/dist/views/page-editor/EditorTopBar.d.ts +21 -2
- package/dist/views/page-editor/EditorTopBar.d.ts.map +1 -1
- package/dist/views/page-editor/EditorTopBar.js +13 -20
- package/dist/views/page-editor/EditorTopBar.js.map +1 -1
- package/dist/views/page-editor/PageSectionEditor.d.ts.map +1 -1
- package/dist/views/page-editor/PageSectionEditor.js +383 -78
- package/dist/views/page-editor/PageSectionEditor.js.map +1 -1
- package/dist/views/page-editor/PageTab.d.ts +36 -0
- package/dist/views/page-editor/PageTab.d.ts.map +1 -0
- package/dist/views/page-editor/PageTab.js +65 -0
- package/dist/views/page-editor/PageTab.js.map +1 -0
- package/dist/views/page-editor/ReviewBar.d.ts +21 -0
- package/dist/views/page-editor/ReviewBar.d.ts.map +1 -0
- package/dist/views/page-editor/ReviewBar.js +22 -0
- package/dist/views/page-editor/ReviewBar.js.map +1 -0
- package/dist/views/page-editor/SectionInspector.d.ts.map +1 -1
- package/dist/views/page-editor/SectionInspector.js +64 -20
- package/dist/views/page-editor/SectionInspector.js.map +1 -1
- package/dist/views/page-editor/SectionsPanel.d.ts +30 -5
- package/dist/views/page-editor/SectionsPanel.d.ts.map +1 -1
- package/dist/views/page-editor/SectionsPanel.js +38 -7
- package/dist/views/page-editor/SectionsPanel.js.map +1 -1
- package/dist/views/page-editor/SitePreviewFrame.d.ts.map +1 -1
- package/dist/views/page-editor/SitePreviewFrame.js +3 -0
- package/dist/views/page-editor/SitePreviewFrame.js.map +1 -1
- package/dist/views/page-editor/VersionHistoryPanel.d.ts +29 -0
- package/dist/views/page-editor/VersionHistoryPanel.d.ts.map +1 -0
- package/dist/views/page-editor/VersionHistoryPanel.js +81 -0
- package/dist/views/page-editor/VersionHistoryPanel.js.map +1 -0
- package/dist/views/page-editor/reflow-context.d.ts +28 -0
- package/dist/views/page-editor/reflow-context.d.ts.map +1 -0
- package/dist/views/page-editor/reflow-context.js +14 -0
- package/dist/views/page-editor/reflow-context.js.map +1 -0
- package/dist/views/page-editor/sections/ByTheNumbersSection.d.ts.map +1 -1
- package/dist/views/page-editor/sections/ByTheNumbersSection.js +1 -1
- package/dist/views/page-editor/sections/ByTheNumbersSection.js.map +1 -1
- package/dist/views/page-editor/sections/CtaBannerSection.d.ts.map +1 -1
- package/dist/views/page-editor/sections/CtaBannerSection.js +1 -1
- package/dist/views/page-editor/sections/CtaBannerSection.js.map +1 -1
- package/dist/views/page-editor/sections/FeatureHighlightSection.d.ts.map +1 -1
- package/dist/views/page-editor/sections/FeatureHighlightSection.js +2 -2
- package/dist/views/page-editor/sections/FeatureHighlightSection.js.map +1 -1
- package/dist/views/page-editor/sections/GallerySection.d.ts.map +1 -1
- package/dist/views/page-editor/sections/GallerySection.js +7 -3
- package/dist/views/page-editor/sections/GallerySection.js.map +1 -1
- package/dist/views/page-editor/sections/HeroBannerSection.js +1 -1
- package/dist/views/page-editor/sections/HeroBannerSection.js.map +1 -1
- package/dist/views/page-editor/sections/MissionSection.d.ts.map +1 -1
- package/dist/views/page-editor/sections/MissionSection.js +1 -1
- package/dist/views/page-editor/sections/MissionSection.js.map +1 -1
- package/dist/views/page-editor/sections/PullQuoteSection.d.ts.map +1 -1
- package/dist/views/page-editor/sections/PullQuoteSection.js +1 -1
- package/dist/views/page-editor/sections/PullQuoteSection.js.map +1 -1
- package/dist/views/page-editor/sections/RelatedPostsSection.d.ts.map +1 -1
- package/dist/views/page-editor/sections/RelatedPostsSection.js +1 -1
- package/dist/views/page-editor/sections/RelatedPostsSection.js.map +1 -1
- package/dist/views/page-editor/sections/SectionRenderer.d.ts +13 -1
- package/dist/views/page-editor/sections/SectionRenderer.d.ts.map +1 -1
- package/dist/views/page-editor/sections/SectionRenderer.js +66 -26
- package/dist/views/page-editor/sections/SectionRenderer.js.map +1 -1
- package/dist/views/page-editor/sections/admin-renderers.d.ts +47 -15
- package/dist/views/page-editor/sections/admin-renderers.d.ts.map +1 -1
- package/dist/views/page-editor/sections/admin-renderers.js +27 -15
- package/dist/views/page-editor/sections/admin-renderers.js.map +1 -1
- package/dist/views/page-editor/sections/parts.d.ts +0 -1
- package/dist/views/page-editor/sections/parts.d.ts.map +1 -1
- package/dist/views/page-editor/sections/parts.js +41 -16
- package/dist/views/page-editor/sections/parts.js.map +1 -1
- package/dist/views/page-editor/viewports.d.ts +7 -14
- package/dist/views/page-editor/viewports.d.ts.map +1 -1
- package/dist/views/page-editor/viewports.js +7 -12
- package/dist/views/page-editor/viewports.js.map +1 -1
- package/dist/views/post-editor/PostEditorCanvas.d.ts +2 -1
- package/dist/views/post-editor/PostEditorCanvas.d.ts.map +1 -1
- package/dist/views/post-editor/PostEditorCanvas.js +5 -2
- package/dist/views/post-editor/PostEditorCanvas.js.map +1 -1
- package/dist/views/post-editor/PostSectionEditor.d.ts.map +1 -1
- package/dist/views/post-editor/PostSectionEditor.js +2 -20
- package/dist/views/post-editor/PostSectionEditor.js.map +1 -1
- package/dist/views/post-editor/PostTemplateEditor.d.ts.map +1 -1
- package/dist/views/post-editor/PostTemplateEditor.js +4 -1
- package/dist/views/post-editor/PostTemplateEditor.js.map +1 -1
- package/package.json +5 -3
- package/src/AdminRoot.tsx +27 -6
- package/src/__tests__/components/breakpoint-controls.render.test.tsx +57 -0
- package/src/__tests__/components/canvas-edit-tip.render.test.tsx +41 -0
- package/src/__tests__/components/editor-rail.render.test.tsx +192 -0
- package/src/__tests__/components/media-block.render.test.tsx +126 -0
- package/src/__tests__/components/page-audit-panel.render.test.tsx +64 -0
- package/src/__tests__/components/reflow-meter.render.test.tsx +144 -0
- package/src/__tests__/components/seo-char-count.test.tsx +26 -6
- package/src/__tests__/components/serp-preview.render.test.tsx +32 -0
- package/src/__tests__/lib/breakpoints.test.ts +84 -0
- package/src/__tests__/lib/canvas-edit-chrome.test.ts +56 -0
- package/src/__tests__/lib/media-format.test.ts +89 -0
- package/src/__tests__/lib/page-audit-input.test.ts +144 -0
- package/src/__tests__/lib/page-editor-service.test.ts +2 -0
- package/src/__tests__/lib/page-redirect.test.ts +29 -0
- package/src/__tests__/lib/rail-width.test.ts +86 -0
- package/src/__tests__/lib/reflow-baseline.test.ts +64 -0
- package/src/__tests__/lib/reflow-probe.test.ts +95 -0
- package/src/__tests__/lib/version-diff.test.ts +278 -0
- package/src/__tests__/lib/version-history.test.ts +166 -0
- package/src/__tests__/styles/canvas-container-queries.test.ts +103 -0
- package/src/__tests__/styles/packaging.test.ts +38 -0
- package/src/__tests__/styles/responsive-layer.test.ts +152 -0
- package/src/__tests__/styles/tokens-drift.test.ts +110 -0
- package/src/__tests__/views/admin-renderer-compat.test.ts +48 -0
- package/src/__tests__/views/admin-section-renderers.test.tsx +70 -4
- package/src/__tests__/views/canvas-review-mode.render.test.tsx +94 -0
- package/src/__tests__/views/editor-top-bar.render.test.tsx +43 -1
- package/src/__tests__/views/page-tab.render.test.tsx +226 -0
- package/src/__tests__/views/section-class-parity.test.ts +98 -0
- package/src/__tests__/views/sections-panel-design-owned.render.test.tsx +104 -0
- package/src/__tests__/views/version-history-panel.render.test.tsx +195 -0
- package/src/components/BreakpointPicker.tsx +114 -0
- package/src/components/CanvasEditTip.tsx +46 -0
- package/src/components/EditorRail.tsx +219 -0
- package/src/components/EditorRailTabs.tsx +117 -0
- package/src/components/MediaBlock.tsx +223 -0
- package/src/components/ReflowMeter.tsx +125 -0
- package/src/components/ZoomChip.tsx +39 -0
- package/src/components/breakpoint-icons.ts +17 -0
- package/src/components/page-tab/AuthorPicker.tsx +240 -0
- package/src/components/page-tab/DirectiveSwitch.tsx +89 -0
- package/src/components/seo/EditorSeoScoreBadge.tsx +9 -1
- package/src/components/seo/PageAuditPanel.tsx +192 -0
- package/src/components/seo/SeoCharCount.tsx +55 -24
- package/src/components/seo/SeoEditorPane.tsx +213 -31
- package/src/components/seo/SerpPreview.tsx +41 -0
- package/src/hooks/usePageAudit.ts +98 -0
- package/src/hooks/useReflowGuard.ts +96 -0
- package/src/layout/primitives/AdminShell.tsx +13 -1
- package/src/lib/breakpoints.ts +107 -0
- package/src/lib/canvas-edit-chrome.ts +82 -0
- package/src/lib/media-format.ts +99 -0
- package/src/lib/page-audit-input.ts +104 -0
- package/src/lib/page-editor-service.ts +18 -0
- package/src/lib/page-redirect.ts +60 -0
- package/src/lib/rail-width.ts +58 -0
- package/src/lib/reflow-baseline.ts +70 -0
- package/src/lib/reflow-probe.ts +299 -0
- package/src/lib/seo-service.ts +10 -0
- package/src/lib/version-diff.ts +376 -0
- package/src/lib/version-history.ts +192 -0
- package/src/styles/prenta-tokens.css +120 -42
- package/src/styles/responsive.css +281 -0
- package/src/styles/theme.css +138 -0
- package/src/views/page-editor/CanvasMetaLine.tsx +50 -0
- package/src/views/page-editor/EditorCanvas.tsx +140 -54
- package/src/views/page-editor/EditorTopBar.tsx +55 -51
- package/src/views/page-editor/PageSectionEditor.tsx +575 -185
- package/src/views/page-editor/PageTab.tsx +433 -0
- package/src/views/page-editor/ReviewBar.tsx +76 -0
- package/src/views/page-editor/SectionInspector.tsx +84 -42
- package/src/views/page-editor/SectionsPanel.tsx +282 -141
- package/src/views/page-editor/SitePreviewFrame.tsx +3 -0
- package/src/views/page-editor/VersionHistoryPanel.tsx +302 -0
- package/src/views/page-editor/reflow-context.ts +43 -0
- package/src/views/page-editor/sections/ByTheNumbersSection.tsx +3 -2
- package/src/views/page-editor/sections/CtaBannerSection.tsx +3 -1
- package/src/views/page-editor/sections/FeatureHighlightSection.tsx +3 -2
- package/src/views/page-editor/sections/GallerySection.tsx +7 -3
- package/src/views/page-editor/sections/HeroBannerSection.tsx +2 -2
- package/src/views/page-editor/sections/MissionSection.tsx +4 -2
- package/src/views/page-editor/sections/PullQuoteSection.tsx +3 -1
- package/src/views/page-editor/sections/RelatedPostsSection.tsx +2 -1
- package/src/views/page-editor/sections/SectionRenderer.tsx +115 -34
- package/src/views/page-editor/sections/admin-renderers.tsx +54 -15
- package/src/views/page-editor/sections/parts.tsx +46 -18
- package/src/views/page-editor/viewports.ts +13 -26
- package/src/views/post-editor/PostEditorCanvas.tsx +18 -11
- package/src/views/post-editor/PostSectionEditor.tsx +1 -56
- package/src/views/post-editor/PostTemplateEditor.tsx +3 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useMemo, useState } from 'react'
|
|
4
|
+
import { History, Loader2, RotateCcw, X } from 'lucide-react'
|
|
5
|
+
import {
|
|
6
|
+
diffVersions,
|
|
7
|
+
groupChangesBySection,
|
|
8
|
+
type FieldChange,
|
|
9
|
+
type SectionChangeGroup,
|
|
10
|
+
} from '../../lib/version-diff.js'
|
|
11
|
+
import {
|
|
12
|
+
groupVersionsByDay,
|
|
13
|
+
initials,
|
|
14
|
+
timeLabel,
|
|
15
|
+
versionKind,
|
|
16
|
+
type PageVersion,
|
|
17
|
+
} from '../../lib/version-history.js'
|
|
18
|
+
|
|
19
|
+
export interface VersionHistoryPanelProps {
|
|
20
|
+
versions: PageVersion[]
|
|
21
|
+
loading: boolean
|
|
22
|
+
error: string | null
|
|
23
|
+
/** Index into `versions`; 0 is the newest, which is "current". */
|
|
24
|
+
selectedIndex: number
|
|
25
|
+
onSelect: (index: number) => void
|
|
26
|
+
onClose: () => void
|
|
27
|
+
onRestore: (version: PageVersion) => void
|
|
28
|
+
restoring: boolean
|
|
29
|
+
canRestore: boolean
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Version history, as a takeover of the editing rail.
|
|
34
|
+
*
|
|
35
|
+
* It is deliberately NOT a dialog. History here is about comparing *renders*:
|
|
36
|
+
* picking a version puts it on the canvas, and the changed sections light up
|
|
37
|
+
* there. A modal would cover the very thing it exists to let you look at, and
|
|
38
|
+
* a right-hand drawer would push the canvas around while you compare against
|
|
39
|
+
* it. Overlaying the rail leaves the canvas exactly where it was, at exactly
|
|
40
|
+
* the size it was, so the only thing that changes between two versions is the
|
|
41
|
+
* page itself.
|
|
42
|
+
*
|
|
43
|
+
* Absolutely positioned over the rail's own box (the rail is `relative`) so it
|
|
44
|
+
* inherits the user's chosen rail width and needs no width of its own.
|
|
45
|
+
*/
|
|
46
|
+
export function VersionHistoryPanel({
|
|
47
|
+
versions,
|
|
48
|
+
loading,
|
|
49
|
+
error,
|
|
50
|
+
selectedIndex,
|
|
51
|
+
onSelect,
|
|
52
|
+
onClose,
|
|
53
|
+
onRestore,
|
|
54
|
+
restoring,
|
|
55
|
+
canRestore,
|
|
56
|
+
}: VersionHistoryPanelProps) {
|
|
57
|
+
/**
|
|
58
|
+
* Disclosure FOLLOWS selection rather than being tracked separately.
|
|
59
|
+
*
|
|
60
|
+
* They are the same act: the selected version is what the canvas renders,
|
|
61
|
+
* and the expanded diff is the list of what to look for on it. Held apart,
|
|
62
|
+
* the two drift — the parent can move the selection (the review bar's "Back
|
|
63
|
+
* to current" does exactly that), and the diff then describes one version
|
|
64
|
+
* while the canvas shows another, with Restore stranded on a row nobody can
|
|
65
|
+
* see. Only "did the user collapse the row they are on" needs remembering.
|
|
66
|
+
*/
|
|
67
|
+
const [collapsed, setCollapsed] = useState(false)
|
|
68
|
+
const expandedId = collapsed ? null : (versions[selectedIndex]?.id ?? null)
|
|
69
|
+
|
|
70
|
+
const days = useMemo(() => groupVersionsByDay(versions), [versions])
|
|
71
|
+
|
|
72
|
+
// Diff each version against the one before it, so a row reports what that
|
|
73
|
+
// save DID rather than how it differs from the present.
|
|
74
|
+
const changesByVersion = useMemo(() => {
|
|
75
|
+
const map = new Map<string, FieldChange[]>()
|
|
76
|
+
versions.forEach((version, index) => {
|
|
77
|
+
map.set(version.id, diffVersions(version.sections, versions[index + 1]?.sections))
|
|
78
|
+
})
|
|
79
|
+
return map
|
|
80
|
+
}, [versions])
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<div
|
|
84
|
+
className="bg-card absolute inset-0 z-45 flex flex-col"
|
|
85
|
+
role="region"
|
|
86
|
+
aria-label="Version history"
|
|
87
|
+
>
|
|
88
|
+
<div className="border-border flex shrink-0 items-center gap-2 border-b py-2.5 pr-2.5 pl-3.5">
|
|
89
|
+
<History className="text-brand h-3.5 w-3.5" aria-hidden />
|
|
90
|
+
<h2 className="text-foreground flex-1 text-[12.5px] font-semibold">Version history</h2>
|
|
91
|
+
<span className="text-muted-foreground text-[11px] tabular-nums">
|
|
92
|
+
{versions.length} version{versions.length === 1 ? '' : 's'}
|
|
93
|
+
</span>
|
|
94
|
+
<button
|
|
95
|
+
type="button"
|
|
96
|
+
onClick={onClose}
|
|
97
|
+
aria-label="Close version history"
|
|
98
|
+
title="Close version history"
|
|
99
|
+
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"
|
|
100
|
+
>
|
|
101
|
+
<X className="h-3.5 w-3.5" aria-hidden />
|
|
102
|
+
</button>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<div className="min-h-0 flex-1 overflow-y-auto">
|
|
106
|
+
{loading ? (
|
|
107
|
+
<div className="flex items-center justify-center py-16">
|
|
108
|
+
<Loader2 className="text-muted-foreground h-5 w-5 animate-spin" aria-hidden />
|
|
109
|
+
<span className="sr-only">Loading version history</span>
|
|
110
|
+
</div>
|
|
111
|
+
) : error ? (
|
|
112
|
+
<p className="text-destructive px-3.5 py-10 text-center text-sm">{error}</p>
|
|
113
|
+
) : versions.length === 0 ? (
|
|
114
|
+
<p className="text-muted-foreground px-3.5 py-10 text-center text-sm">
|
|
115
|
+
No versions yet. Every save adds one.
|
|
116
|
+
</p>
|
|
117
|
+
) : (
|
|
118
|
+
days.map((day) => (
|
|
119
|
+
// Keyed on the group's first version, NOT on its label. Grouping
|
|
120
|
+
// walks consecutive runs, so an out-of-order page yields two
|
|
121
|
+
// non-adjacent groups both labelled "Today" — `groupVersionsByDay`
|
|
122
|
+
// has a test asserting exactly that — and a label key would collide.
|
|
123
|
+
<section key={day.versions[0]?.id ?? day.label}>
|
|
124
|
+
<h3 className="text-muted-foreground bg-muted border-border sticky top-0 z-2 border-b px-3.5 pt-2.5 pb-1.5 text-[10px] font-bold tracking-[0.7px] uppercase">
|
|
125
|
+
{day.label}
|
|
126
|
+
</h3>
|
|
127
|
+
{day.versions.map((version) => {
|
|
128
|
+
const index = versions.indexOf(version)
|
|
129
|
+
return (
|
|
130
|
+
<VersionRow
|
|
131
|
+
key={version.id}
|
|
132
|
+
version={version}
|
|
133
|
+
index={index}
|
|
134
|
+
changes={changesByVersion.get(version.id) ?? []}
|
|
135
|
+
selected={index === selectedIndex}
|
|
136
|
+
expanded={expandedId === version.id}
|
|
137
|
+
onToggle={() => {
|
|
138
|
+
// Re-clicking the row you are on collapses it; clicking
|
|
139
|
+
// any other selects it, which is also what opens it.
|
|
140
|
+
if (index === selectedIndex) setCollapsed((prev) => !prev)
|
|
141
|
+
else {
|
|
142
|
+
setCollapsed(false)
|
|
143
|
+
onSelect(index)
|
|
144
|
+
}
|
|
145
|
+
}}
|
|
146
|
+
onRestore={() => onRestore(version)}
|
|
147
|
+
restoring={restoring}
|
|
148
|
+
canRestore={canRestore}
|
|
149
|
+
/>
|
|
150
|
+
)
|
|
151
|
+
})}
|
|
152
|
+
</section>
|
|
153
|
+
))
|
|
154
|
+
)}
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const TONE_CLASS: Record<string, string> = {
|
|
161
|
+
live: 'bg-success/15 text-success',
|
|
162
|
+
draft: 'bg-warning/15 text-warning',
|
|
163
|
+
neutral: 'bg-muted text-muted-foreground',
|
|
164
|
+
danger: 'bg-destructive/15 text-destructive',
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function VersionRow({
|
|
168
|
+
version,
|
|
169
|
+
index,
|
|
170
|
+
changes,
|
|
171
|
+
selected,
|
|
172
|
+
expanded,
|
|
173
|
+
onToggle,
|
|
174
|
+
onRestore,
|
|
175
|
+
restoring,
|
|
176
|
+
canRestore,
|
|
177
|
+
}: {
|
|
178
|
+
version: PageVersion
|
|
179
|
+
index: number
|
|
180
|
+
changes: FieldChange[]
|
|
181
|
+
selected: boolean
|
|
182
|
+
expanded: boolean
|
|
183
|
+
onToggle: () => void
|
|
184
|
+
onRestore: () => void
|
|
185
|
+
restoring: boolean
|
|
186
|
+
canRestore: boolean
|
|
187
|
+
}) {
|
|
188
|
+
const kind = versionKind(version.changeType)
|
|
189
|
+
const isCurrent = index === 0
|
|
190
|
+
const groups = groupChangesBySection(changes)
|
|
191
|
+
const time = timeLabel(version.createdAt)
|
|
192
|
+
|
|
193
|
+
return (
|
|
194
|
+
<div className="border-border border-b">
|
|
195
|
+
<button
|
|
196
|
+
type="button"
|
|
197
|
+
onClick={onToggle}
|
|
198
|
+
aria-expanded={expanded}
|
|
199
|
+
className={`flex w-full items-center gap-2.5 py-2.5 pr-2.5 pl-3 text-left transition-colors ${
|
|
200
|
+
selected ? 'bg-accent shadow-[inset_2px_0_0_var(--acc)]' : 'hover:bg-accent/60'
|
|
201
|
+
}`}
|
|
202
|
+
>
|
|
203
|
+
<span
|
|
204
|
+
className="bg-muted text-muted-foreground flex h-6 w-6 shrink-0 items-center justify-center rounded-full text-[9.5px] font-semibold"
|
|
205
|
+
aria-hidden
|
|
206
|
+
>
|
|
207
|
+
{initials(version.author.name)}
|
|
208
|
+
</span>
|
|
209
|
+
|
|
210
|
+
<span className="min-w-0 flex-1">
|
|
211
|
+
<span className="text-foreground flex items-center gap-1.5 text-xs font-semibold">
|
|
212
|
+
{time}
|
|
213
|
+
{isCurrent && (
|
|
214
|
+
<span className="bg-accent text-accent-foreground rounded px-1.5 py-px text-[8.5px] font-bold tracking-[0.4px] uppercase">
|
|
215
|
+
Current
|
|
216
|
+
</span>
|
|
217
|
+
)}
|
|
218
|
+
</span>
|
|
219
|
+
<span className="text-muted-foreground mt-px block truncate text-[10.5px]">
|
|
220
|
+
{version.author.name} ·{' '}
|
|
221
|
+
{changes.length
|
|
222
|
+
? `${changes.length} change${changes.length === 1 ? '' : 's'}`
|
|
223
|
+
: 'no content changes'}
|
|
224
|
+
</span>
|
|
225
|
+
</span>
|
|
226
|
+
|
|
227
|
+
<span
|
|
228
|
+
className={`shrink-0 rounded px-1.5 py-px text-[8.5px] font-bold tracking-[0.4px] uppercase ${
|
|
229
|
+
TONE_CLASS[kind.tone] ?? TONE_CLASS.neutral
|
|
230
|
+
}`}
|
|
231
|
+
>
|
|
232
|
+
{kind.label}
|
|
233
|
+
</span>
|
|
234
|
+
</button>
|
|
235
|
+
|
|
236
|
+
{/*
|
|
237
|
+
* Opens on `expanded` alone — NOT on `expanded && changes.length`.
|
|
238
|
+
*
|
|
239
|
+
* Restore lives in here, and a version can legitimately have no computed
|
|
240
|
+
* diff: the oldest one held has no predecessor to compare against, and a
|
|
241
|
+
* metadata-only save changes no section. Gating the whole block on a
|
|
242
|
+
* non-empty diff makes exactly those versions reviewable but not
|
|
243
|
+
* restorable, which is the wrong way round — the oldest version is the
|
|
244
|
+
* one a rollback most often wants.
|
|
245
|
+
*/}
|
|
246
|
+
{expanded && (
|
|
247
|
+
<div className="flex flex-col gap-2.5 pt-0 pr-3 pb-3 pl-[38px]">
|
|
248
|
+
{groups.length > 0 ? (
|
|
249
|
+
groups.map((group) => <ChangeGroup key={group.sectionId} group={group} />)
|
|
250
|
+
) : (
|
|
251
|
+
<p className="text-muted-foreground text-[11px] leading-[1.45]">
|
|
252
|
+
No section changes to show against the version before it.
|
|
253
|
+
</p>
|
|
254
|
+
)}
|
|
255
|
+
|
|
256
|
+
{/* Restore is offered only on the row being reviewed, and never on the
|
|
257
|
+
newest one — "restore the version you are already looking at" is a
|
|
258
|
+
no-op dressed up as an action. */}
|
|
259
|
+
{selected && !isCurrent && canRestore && (
|
|
260
|
+
<button
|
|
261
|
+
type="button"
|
|
262
|
+
onClick={onRestore}
|
|
263
|
+
disabled={restoring}
|
|
264
|
+
className="border-border text-foreground hover:bg-accent inline-flex items-center justify-center gap-1.5 rounded-lg border px-3 py-1.5 text-xs font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-50"
|
|
265
|
+
>
|
|
266
|
+
{restoring ? (
|
|
267
|
+
<Loader2 className="h-3 w-3 animate-spin" aria-hidden />
|
|
268
|
+
) : (
|
|
269
|
+
<RotateCcw className="h-3 w-3" aria-hidden />
|
|
270
|
+
)}
|
|
271
|
+
Restore this version
|
|
272
|
+
</button>
|
|
273
|
+
)}
|
|
274
|
+
</div>
|
|
275
|
+
)}
|
|
276
|
+
</div>
|
|
277
|
+
)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function ChangeGroup({ group }: { group: SectionChangeGroup }) {
|
|
281
|
+
return (
|
|
282
|
+
<div>
|
|
283
|
+
<h4 className="text-foreground/80 mb-1 text-[9.5px] font-bold tracking-[0.4px] uppercase">
|
|
284
|
+
{group.sectionName}
|
|
285
|
+
</h4>
|
|
286
|
+
{group.changes.map((change, i) => (
|
|
287
|
+
<div key={`${change.field}-${i}`} className={i > 0 ? 'mt-1.5' : undefined}>
|
|
288
|
+
<div className="text-muted-foreground text-[9.5px] font-bold tracking-[0.3px]">
|
|
289
|
+
{change.field}
|
|
290
|
+
</div>
|
|
291
|
+
{/* Colour is not the only carrier: the old value is struck through
|
|
292
|
+
and the pair is ordered old-then-new, both of which survive a
|
|
293
|
+
reader who cannot separate red from green. */}
|
|
294
|
+
<div className="text-destructive text-[11px] leading-[1.45] line-through decoration-1">
|
|
295
|
+
{change.from}
|
|
296
|
+
</div>
|
|
297
|
+
<div className="text-success text-[11px] leading-[1.45]">{change.to}</div>
|
|
298
|
+
</div>
|
|
299
|
+
))}
|
|
300
|
+
</div>
|
|
301
|
+
)
|
|
302
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { createContext, useContext } from 'react'
|
|
4
|
+
import type { BreakpointId } from '../../lib/breakpoints.js'
|
|
5
|
+
import type { ReflowBaseline } from '../../lib/reflow-baseline.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* What the reflow guard needs that a field editor cannot know by itself.
|
|
9
|
+
*
|
|
10
|
+
* A context rather than props because the guard hangs off individual fields
|
|
11
|
+
* several levels down (inspector → field → repeater row → field), and threading
|
|
12
|
+
* five props through every renderer for a feature that only some field types
|
|
13
|
+
* use makes every one of them harder to read.
|
|
14
|
+
*
|
|
15
|
+
* The default is inert — no canvas, no baseline — so a field rendered outside a
|
|
16
|
+
* page editor (a repeater in a modal, a test) simply shows no guard rather than
|
|
17
|
+
* needing a provider to exist.
|
|
18
|
+
*/
|
|
19
|
+
export interface ReflowContextValue {
|
|
20
|
+
/** The live `.prenta-canvas` element the probe clones. */
|
|
21
|
+
canvas: HTMLElement | null
|
|
22
|
+
/** Section the inspector is editing — scopes field lookup. */
|
|
23
|
+
sectionId: string | null
|
|
24
|
+
baseline: ReflowBaseline | null
|
|
25
|
+
/** Breakpoint the canvas is showing. */
|
|
26
|
+
device: BreakpointId
|
|
27
|
+
/** Take the canvas to a breakpoint so the editor can look at it. */
|
|
28
|
+
onProof: (id: BreakpointId) => void
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const INERT: ReflowContextValue = {
|
|
32
|
+
canvas: null,
|
|
33
|
+
sectionId: null,
|
|
34
|
+
baseline: null,
|
|
35
|
+
device: 'wide',
|
|
36
|
+
onProof: () => {},
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const ReflowContext = createContext<ReflowContextValue>(INERT)
|
|
40
|
+
|
|
41
|
+
export function useReflowContext(): ReflowContextValue {
|
|
42
|
+
return useContext(ReflowContext)
|
|
43
|
+
}
|
|
@@ -16,13 +16,14 @@ export function ByTheNumbersSection({ section }: { section: PageSection }) {
|
|
|
16
16
|
return (
|
|
17
17
|
<div className="mx-auto max-w-5xl px-6 text-center">
|
|
18
18
|
{heading && (
|
|
19
|
-
<h2 className="text-foreground text-2xl font-semibold tracking-tight
|
|
19
|
+
<h2 className="text-foreground @pv-md:text-3xl @pv-2xl:text-4xl text-2xl font-semibold tracking-tight">
|
|
20
20
|
{heading}
|
|
21
21
|
</h2>
|
|
22
22
|
)}
|
|
23
23
|
{body && <p className="text-muted-foreground mx-auto mt-4 max-w-2xl text-base">{body}</p>}
|
|
24
|
+
{/* Handoff: stats collapse to 2-up below 900, and to 1-up below 460. */}
|
|
24
25
|
{stats.length > 0 && (
|
|
25
|
-
<dl className="mt-10 grid grid-cols-1 gap-8
|
|
26
|
+
<dl className="@pv-sm:grid-cols-2 @pv-lg:grid-cols-3 mt-10 grid grid-cols-1 gap-8">
|
|
26
27
|
{stats.map((s, i) => (
|
|
27
28
|
<div key={i}>
|
|
28
29
|
<dt className="text-brand text-4xl font-semibold tracking-tight">
|
|
@@ -9,7 +9,9 @@ export function CtaBannerSection({ section }: { section: PageSection }) {
|
|
|
9
9
|
|
|
10
10
|
return (
|
|
11
11
|
<div className="mx-auto max-w-3xl px-6 text-center">
|
|
12
|
-
<h2 className="text-2xl font-semibold tracking-tight
|
|
12
|
+
<h2 className="@pv-md:text-3xl @pv-2xl:text-4xl text-2xl font-semibold tracking-tight">
|
|
13
|
+
{heading}
|
|
14
|
+
</h2>
|
|
13
15
|
{body && (
|
|
14
16
|
<p
|
|
15
17
|
className={`mx-auto mt-3 max-w-xl text-base ${dark ? 'text-brand-foreground/80' : 'text-muted-foreground'}`}
|
|
@@ -18,7 +18,7 @@ export function FeatureHighlightSection({ section }: { section: PageSection }) {
|
|
|
18
18
|
{label}
|
|
19
19
|
</span>
|
|
20
20
|
)}
|
|
21
|
-
<h2 className="text-foreground text-2xl font-semibold tracking-tight
|
|
21
|
+
<h2 className="text-foreground @pv-md:text-3xl @pv-2xl:text-4xl text-2xl font-semibold tracking-tight">
|
|
22
22
|
{heading}
|
|
23
23
|
</h2>
|
|
24
24
|
{body && <p className="text-muted-foreground mt-4 text-base leading-relaxed">{body}</p>}
|
|
@@ -47,7 +47,8 @@ export function FeatureHighlightSection({ section }: { section: PageSection }) {
|
|
|
47
47
|
|
|
48
48
|
return (
|
|
49
49
|
<div className="mx-auto max-w-5xl px-6">
|
|
50
|
-
|
|
50
|
+
{/* Handoff: image+text stacks below 700. */}
|
|
51
|
+
<div className="@pv-md:flex-row flex flex-col items-center gap-10">
|
|
51
52
|
{imageLeft ? (
|
|
52
53
|
<>
|
|
53
54
|
{media}
|
|
@@ -15,12 +15,16 @@ export function GallerySection({ section }: { section: PageSection }) {
|
|
|
15
15
|
const images: GalleryImage[] = Array.isArray(c.images) ? (c.images as GalleryImage[]) : []
|
|
16
16
|
const withUrl = images.filter((img) => img && typeof img.url === 'string' && img.url)
|
|
17
17
|
|
|
18
|
+
// Ascending min-width container queries only: the column count is chosen
|
|
19
|
+
// from content, so mixing min- and max-width variants here would depend on
|
|
20
|
+
// Tailwind's variant sort order to resolve — the exact ambiguity the handoff
|
|
21
|
+
// warns about for data-driven grids.
|
|
18
22
|
const cols =
|
|
19
23
|
columns === 2
|
|
20
|
-
? 'sm:grid-cols-2'
|
|
24
|
+
? '@pv-sm:grid-cols-2'
|
|
21
25
|
: columns === 4
|
|
22
|
-
? 'sm:grid-cols-2
|
|
23
|
-
: 'sm:grid-cols-3'
|
|
26
|
+
? '@pv-sm:grid-cols-2 @pv-xl:grid-cols-4'
|
|
27
|
+
: '@pv-sm:grid-cols-2 @pv-lg:grid-cols-3'
|
|
24
28
|
|
|
25
29
|
return (
|
|
26
30
|
<div className="mx-auto max-w-5xl px-6">
|
|
@@ -17,12 +17,12 @@ export function HeroBannerSection({ section }: { section: PageSection }) {
|
|
|
17
17
|
{eyebrow}
|
|
18
18
|
</p>
|
|
19
19
|
)}
|
|
20
|
-
<h1 className="text-3xl leading-tight font-semibold tracking-tight
|
|
20
|
+
<h1 className="@pv-md:text-5xl @pv-2xl:text-6xl text-3xl leading-tight font-semibold tracking-tight">
|
|
21
21
|
{heading || 'Your headline goes here'}
|
|
22
22
|
</h1>
|
|
23
23
|
{body && (
|
|
24
24
|
<p
|
|
25
|
-
className={
|
|
25
|
+
className={`@pv-md:text-lg mx-auto mt-5 max-w-2xl text-base ${dark ? 'text-brand-foreground/80' : 'text-muted-foreground'}`}
|
|
26
26
|
>
|
|
27
27
|
{body}
|
|
28
28
|
</p>
|
|
@@ -12,12 +12,14 @@ export function MissionSection({ section }: { section: PageSection }) {
|
|
|
12
12
|
return (
|
|
13
13
|
<div className={`max-w-3xl px-6 ${align}`}>
|
|
14
14
|
{heading && (
|
|
15
|
-
<h2 className="text-foreground text-2xl font-semibold tracking-tight
|
|
15
|
+
<h2 className="text-foreground @pv-md:text-3xl @pv-2xl:text-4xl text-2xl font-semibold tracking-tight">
|
|
16
16
|
{heading}
|
|
17
17
|
</h2>
|
|
18
18
|
)}
|
|
19
19
|
{body && (
|
|
20
|
-
<p className="text-muted-foreground mt-4 text-base leading-relaxed
|
|
20
|
+
<p className="text-muted-foreground @pv-md:text-lg mt-4 text-base leading-relaxed">
|
|
21
|
+
{body}
|
|
22
|
+
</p>
|
|
21
23
|
)}
|
|
22
24
|
</div>
|
|
23
25
|
)
|
|
@@ -13,7 +13,9 @@ export function PullQuoteSection({ section }: { section: PageSection }) {
|
|
|
13
13
|
return (
|
|
14
14
|
<figure className="mx-auto max-w-3xl px-6">
|
|
15
15
|
<blockquote className="border-brand border-l-4 pl-6">
|
|
16
|
-
<p className="text-foreground text-xl leading-relaxed font-medium
|
|
16
|
+
<p className="text-foreground @pv-md:text-2xl text-xl leading-relaxed font-medium">
|
|
17
|
+
“{quote}”
|
|
18
|
+
</p>
|
|
17
19
|
</blockquote>
|
|
18
20
|
{(attribution || role) && (
|
|
19
21
|
<figcaption className="text-muted-foreground mt-3 pl-6 text-sm">
|
|
@@ -27,7 +27,8 @@ export function RelatedPostsSection({
|
|
|
27
27
|
url: undefined as string | undefined,
|
|
28
28
|
}))
|
|
29
29
|
|
|
30
|
-
const cols =
|
|
30
|
+
const cols =
|
|
31
|
+
count >= 4 ? '@pv-sm:grid-cols-2 @pv-xl:grid-cols-4' : '@pv-sm:grid-cols-2 @pv-lg:grid-cols-3'
|
|
31
32
|
|
|
32
33
|
return (
|
|
33
34
|
<div className="mx-auto max-w-5xl px-6">
|
|
@@ -45,10 +45,62 @@ export function hasCanvasRenderer(sectionType: string): boolean {
|
|
|
45
45
|
return CANVAS_RENDERED_TYPES.has(sectionType)
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
/**
|
|
48
|
+
/**
|
|
49
|
+
* Dispatch a section's body component by type. Shared by editor + preview.
|
|
50
|
+
*
|
|
51
|
+
* RESOLUTION ORDER — consumer-supplied renderer, then built-in, then
|
|
52
|
+
* placeholder. The consumer's renderer wins for EVERY type, including the
|
|
53
|
+
* built-ins, because that is the whole point of the `sectionRenderers` prop:
|
|
54
|
+
* hand the admin the same components the public site registers and the canvas
|
|
55
|
+
* shows what actually ships.
|
|
56
|
+
*
|
|
57
|
+
* Until this order was corrected the map was consulted only for types the
|
|
58
|
+
* switch below did not know, so a consumer passing
|
|
59
|
+
* `DEFAULT_SECTION_RENDERERS` from `@prenta/sections-react` had their hero,
|
|
60
|
+
* text, feature, stats and cta silently ignored — the canvas rendered the
|
|
61
|
+
* admin's own approximations while claiming to preview the page. Those
|
|
62
|
+
* approximations differ in font size, weight and breakpoint, so copy that fit
|
|
63
|
+
* on two lines in the canvas could wrap to three on the site.
|
|
64
|
+
*
|
|
65
|
+
* The built-ins below remain the fallback for consumers who pass nothing.
|
|
66
|
+
*/
|
|
49
67
|
function SectionBody({ section, context }: { section: PageSection; context?: PostRenderContext }) {
|
|
50
68
|
const customRenderers = useAdminSectionRenderers()
|
|
51
69
|
|
|
70
|
+
const Supplied = customRenderers[section.sectionType]
|
|
71
|
+
if (Supplied) {
|
|
72
|
+
// The renderer contract types `settings` as an open record (matching the
|
|
73
|
+
// sections-react wire shape); the admin's concrete `SectionSettings`
|
|
74
|
+
// interface is the same object at runtime but has no index signature, so
|
|
75
|
+
// bridge the nominal gap here.
|
|
76
|
+
const wireSection = {
|
|
77
|
+
...section,
|
|
78
|
+
settings: { ...section.settings } as Record<string, unknown>,
|
|
79
|
+
}
|
|
80
|
+
// Rendered as a component (not invoked inline) so a throw happens inside
|
|
81
|
+
// the boundary's subtree and is actually caught. A consumer renderer that
|
|
82
|
+
// breaks falls back to the built-in rather than blanking the editor.
|
|
83
|
+
return (
|
|
84
|
+
<ErrorBoundary
|
|
85
|
+
key={section.id}
|
|
86
|
+
fallback={<BuiltInSectionBody section={section} context={context} />}
|
|
87
|
+
>
|
|
88
|
+
<Supplied section={wireSection} ctx={context} options={ADMIN_RENDER_OPTIONS} />
|
|
89
|
+
</ErrorBoundary>
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return <BuiltInSectionBody section={section} context={context} />
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** The admin's own approximations, used when the consumer supplies nothing. */
|
|
97
|
+
function BuiltInSectionBody({
|
|
98
|
+
section,
|
|
99
|
+
context,
|
|
100
|
+
}: {
|
|
101
|
+
section: PageSection
|
|
102
|
+
context?: PostRenderContext
|
|
103
|
+
}) {
|
|
52
104
|
switch (section.sectionType) {
|
|
53
105
|
case 'hero':
|
|
54
106
|
return <HeroBannerSection section={section} />
|
|
@@ -74,33 +126,11 @@ function SectionBody({ section, context }: { section: PageSection; context?: Pos
|
|
|
74
126
|
return <EmbedSection section={section} />
|
|
75
127
|
case 'widget-embed':
|
|
76
128
|
return <WidgetEmbedSection section={section} />
|
|
77
|
-
default:
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
// to the structural placeholder instead of taking down the editor.
|
|
82
|
-
const Custom = customRenderers[section.sectionType]
|
|
83
|
-
if (Custom) {
|
|
84
|
-
// The renderer contract types `settings` as an open record (matching
|
|
85
|
-
// the sections-react wire shape); the admin's concrete
|
|
86
|
-
// `SectionSettings` interface is the same object at runtime but has
|
|
87
|
-
// no index signature, so bridge the nominal gap here.
|
|
88
|
-
const wireSection = {
|
|
89
|
-
...section,
|
|
90
|
-
settings: { ...section.settings } as Record<string, unknown>,
|
|
91
|
-
}
|
|
92
|
-
// Rendered as a component (not invoked inline) so a throw happens
|
|
93
|
-
// inside the boundary's subtree and is actually caught.
|
|
94
|
-
return (
|
|
95
|
-
<ErrorBoundary key={section.id} fallback={<PlaceholderSection section={section} />}>
|
|
96
|
-
<Custom section={wireSection} ctx={context} options={ADMIN_RENDER_OPTIONS} />
|
|
97
|
-
</ErrorBoundary>
|
|
98
|
-
)
|
|
99
|
-
}
|
|
100
|
-
// No renderer registered: a neutral placeholder keeps the canvas
|
|
101
|
-
// reflecting the section's presence + order instead of a blank gap.
|
|
129
|
+
default:
|
|
130
|
+
// Config-declared custom types and unmanaged sections with no supplied
|
|
131
|
+
// renderer: a neutral placeholder keeps the canvas reflecting the
|
|
132
|
+
// section's presence and order instead of leaving a blank gap.
|
|
102
133
|
return <PlaceholderSection section={section} />
|
|
103
|
-
}
|
|
104
134
|
}
|
|
105
135
|
}
|
|
106
136
|
|
|
@@ -134,6 +164,18 @@ export interface SectionRendererProps {
|
|
|
134
164
|
registerRef?: (id: string, el: HTMLElement | null) => void
|
|
135
165
|
/** Live post data for post-oriented section renderers (pages omit this). */
|
|
136
166
|
context?: PostRenderContext
|
|
167
|
+
/**
|
|
168
|
+
* Reviewing a past version: the canvas is a read-only render of a snapshot.
|
|
169
|
+
*
|
|
170
|
+
* A SEPARATE flag from `editable`, not a second use of it. Both modes
|
|
171
|
+
* suppress click-to-select and the hover outline, so it is tempting to fold
|
|
172
|
+
* them together — but review mode has to keep showing a label on the
|
|
173
|
+
* sections that changed, which is the entire reason it exists. One shared
|
|
174
|
+
* suppression rule hides exactly the thing the mode is for.
|
|
175
|
+
*/
|
|
176
|
+
reviewing?: boolean
|
|
177
|
+
/** This section differs from the version immediately before the one shown. */
|
|
178
|
+
changed?: boolean
|
|
137
179
|
}
|
|
138
180
|
|
|
139
181
|
/**
|
|
@@ -149,31 +191,70 @@ export function SectionRenderer({
|
|
|
149
191
|
onSelect,
|
|
150
192
|
registerRef,
|
|
151
193
|
context,
|
|
194
|
+
reviewing = false,
|
|
195
|
+
changed = false,
|
|
152
196
|
}: SectionRendererProps) {
|
|
153
197
|
const settings = section.settings ?? {}
|
|
154
|
-
|
|
198
|
+
// A snapshot's hidden sections stay muted: the page is still being shown as
|
|
199
|
+
// the editor's own view of it, and a hidden section that rendered solid here
|
|
200
|
+
// would misreport what was live at that moment.
|
|
201
|
+
const chromed = editable || reviewing
|
|
202
|
+
const hidden = chromed && !section.visible
|
|
203
|
+
// Review mode is read-only. Click-to-select would edit the CURRENT page
|
|
204
|
+
// while the canvas shows a past one.
|
|
205
|
+
const interactive = editable && !reviewing
|
|
155
206
|
|
|
156
207
|
return (
|
|
157
208
|
<section
|
|
158
209
|
ref={(el) => registerRef?.(section.id, el)}
|
|
159
210
|
data-section-id={section.id}
|
|
211
|
+
data-editable={interactive ? 'true' : undefined}
|
|
212
|
+
data-selected={interactive && selected ? 'true' : undefined}
|
|
213
|
+
data-reviewing={reviewing ? 'true' : undefined}
|
|
214
|
+
data-changed={reviewing && changed ? 'true' : undefined}
|
|
160
215
|
id={settings.anchorId || undefined}
|
|
161
216
|
className={`relative ${backgroundClass(settings)} ${paddingClass(settings)} ${
|
|
162
217
|
settings.cssClass ?? ''
|
|
163
218
|
} ${hidden ? 'opacity-40' : ''} ${
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
219
|
+
interactive
|
|
220
|
+
? 'cursor-pointer outline-2 -outline-offset-2 motion-safe:transition-[outline-color] motion-safe:duration-150'
|
|
221
|
+
: ''
|
|
222
|
+
} ${
|
|
223
|
+
reviewing
|
|
224
|
+
? changed
|
|
225
|
+
? 'outline-warning outline-2 -outline-offset-2 outline-dashed'
|
|
226
|
+
: 'outline-transparent'
|
|
227
|
+
: selected
|
|
228
|
+
? 'outline outline-violet-500'
|
|
229
|
+
: interactive
|
|
230
|
+
? 'outline-transparent hover:outline hover:outline-[color-mix(in_oklab,var(--acc)_40%,transparent)]'
|
|
231
|
+
: 'outline-transparent'
|
|
232
|
+
}`}
|
|
233
|
+
onClick={interactive ? () => onSelect?.(section.id) : undefined}
|
|
167
234
|
// Selection is a convenience in the editor; the section list is the
|
|
168
235
|
// primary, fully-accessible control surface, so this stays a region.
|
|
169
|
-
aria-label={
|
|
236
|
+
aria-label={
|
|
237
|
+
reviewing && changed
|
|
238
|
+
? `${section.name} section — changed in this version`
|
|
239
|
+
: interactive
|
|
240
|
+
? `${section.name} section`
|
|
241
|
+
: undefined
|
|
242
|
+
}
|
|
170
243
|
>
|
|
171
|
-
{
|
|
244
|
+
{interactive && selected && (
|
|
172
245
|
<span className="bg-brand pointer-events-none absolute top-0 left-0 z-10 rounded-br-md px-2 py-0.5 text-xs font-medium text-white">
|
|
173
246
|
{section.name}
|
|
174
247
|
</span>
|
|
175
248
|
)}
|
|
176
|
-
{
|
|
249
|
+
{/* Persistent, not hover-revealed: while reviewing, this label IS the
|
|
250
|
+
finding. Unchanged sections get no label at all, so the marked ones
|
|
251
|
+
are legible at a glance down a scaled-down page. */}
|
|
252
|
+
{reviewing && changed && (
|
|
253
|
+
<span className="bg-warning text-warning-foreground pointer-events-none absolute top-0 left-0 z-10 rounded-br-md px-2 py-0.5 text-xs font-medium">
|
|
254
|
+
{section.name} — changed
|
|
255
|
+
</span>
|
|
256
|
+
)}
|
|
257
|
+
{chromed && hidden && (
|
|
177
258
|
<span className="bg-foreground pointer-events-none absolute top-0 right-0 z-10 rounded-bl-md px-2 py-0.5 text-xs font-medium text-white">
|
|
178
259
|
Hidden
|
|
179
260
|
</span>
|