@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,89 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import * as Switch from '@radix-ui/react-switch'
|
|
4
|
+
|
|
5
|
+
export interface DirectiveSwitchProps {
|
|
6
|
+
id: string
|
|
7
|
+
/** Human label, e.g. "Show in search results". */
|
|
8
|
+
label: string
|
|
9
|
+
/** One line on what turning it off actually costs. */
|
|
10
|
+
hint?: string
|
|
11
|
+
/** `true` = permissive. */
|
|
12
|
+
checked: boolean
|
|
13
|
+
onChange: (next: boolean) => void
|
|
14
|
+
/** Directive token when permitted, e.g. `index`. */
|
|
15
|
+
permissiveToken: string
|
|
16
|
+
/** Directive token when restricted, e.g. `noindex`. */
|
|
17
|
+
restrictiveToken: string
|
|
18
|
+
disabled?: boolean
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A robots-directive switch.
|
|
23
|
+
*
|
|
24
|
+
* ── Why this is not the ordinary Toggle ───────────────────────────────────
|
|
25
|
+
*
|
|
26
|
+
* The admin's `Toggle` turns brand purple when on, because "on" is the state
|
|
27
|
+
* the product is nudging you towards. That reading is wrong here. These
|
|
28
|
+
* switches are not on/off, they are permissive/restrictive — and the meaningful
|
|
29
|
+
* distinction is not "did I enable something" but "is this page still allowed
|
|
30
|
+
* to be found". Purple would say "enabled" about a switch whose enabled state
|
|
31
|
+
* is simply the default, and say nothing about the one state worth noticing.
|
|
32
|
+
*
|
|
33
|
+
* So: green when permitted, red when restricted. And the literal directive
|
|
34
|
+
* token sits beside it, because the failure this UI has to prevent is an editor
|
|
35
|
+
* flipping something whose consequence they could not name. `noindex` next to
|
|
36
|
+
* the switch turns an abstract toggle into the thing it actually writes into
|
|
37
|
+
* the page's head.
|
|
38
|
+
*
|
|
39
|
+
* Colour never carries it alone — the token chip is text, and the switch's
|
|
40
|
+
* accessible name states the label and the token it will produce.
|
|
41
|
+
*/
|
|
42
|
+
export function DirectiveSwitch({
|
|
43
|
+
id,
|
|
44
|
+
label,
|
|
45
|
+
hint,
|
|
46
|
+
checked,
|
|
47
|
+
onChange,
|
|
48
|
+
permissiveToken,
|
|
49
|
+
restrictiveToken,
|
|
50
|
+
disabled = false,
|
|
51
|
+
}: DirectiveSwitchProps) {
|
|
52
|
+
const token = checked ? permissiveToken : restrictiveToken
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
<div className="flex items-start justify-between gap-3 py-1.5">
|
|
56
|
+
<div className="min-w-0">
|
|
57
|
+
<label htmlFor={id} className="text-foreground block text-[12.5px] font-medium">
|
|
58
|
+
{label}
|
|
59
|
+
</label>
|
|
60
|
+
{hint && <p className="text-muted-foreground mt-0.5 text-[11px] leading-snug">{hint}</p>}
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<div className="flex shrink-0 items-center gap-2">
|
|
64
|
+
<code
|
|
65
|
+
className={`rounded px-1.5 py-0.5 font-mono text-[9.5px] font-bold ${
|
|
66
|
+
checked ? 'bg-success/12 text-success' : 'bg-destructive/12 text-destructive'
|
|
67
|
+
}`}
|
|
68
|
+
>
|
|
69
|
+
{token}
|
|
70
|
+
</code>
|
|
71
|
+
<Switch.Root
|
|
72
|
+
id={id}
|
|
73
|
+
checked={checked}
|
|
74
|
+
onCheckedChange={onChange}
|
|
75
|
+
disabled={disabled}
|
|
76
|
+
aria-label={`${label} — currently ${token}`}
|
|
77
|
+
// `prenta-touch-hit`, not `prenta-touch-target`: growing the track to
|
|
78
|
+
// 44px would strand the thumb, which is positioned against it. The
|
|
79
|
+
// hit area grows; the switch keeps its measured 34×19.
|
|
80
|
+
className={`prenta-touch-hit focus-visible:ring-ring relative h-[19px] w-[34px] shrink-0 rounded-full transition-colors duration-(--mo-base) focus-visible:ring-2 focus-visible:outline-none disabled:opacity-50 ${
|
|
81
|
+
checked ? 'bg-success' : 'bg-destructive/55'
|
|
82
|
+
}`}
|
|
83
|
+
>
|
|
84
|
+
<Switch.Thumb className="block h-3.5 w-3.5 translate-x-[2.5px] rounded-full bg-white shadow-[0_1px_3px_rgba(0,0,0,0.25)] transition-transform duration-(--mo-base) data-[state=checked]:translate-x-[18px]" />
|
|
85
|
+
</Switch.Root>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
)
|
|
89
|
+
}
|
|
@@ -5,13 +5,21 @@ import { gradeFromScore, gradeMeta } from './primitives.js'
|
|
|
5
5
|
/** Compact color-coded score for the page/post editor toolbar (`77/100` left of "SEO"). */
|
|
6
6
|
export function EditorSeoScoreBadge({
|
|
7
7
|
score,
|
|
8
|
+
band,
|
|
8
9
|
inverted = false,
|
|
9
10
|
}: {
|
|
10
11
|
score: number
|
|
12
|
+
/**
|
|
13
|
+
* Band for the colour. Supplied by the caller because different scores use
|
|
14
|
+
* different scales: the page audit bands at 80/55, `analyzeContent` at
|
|
15
|
+
* 80/60/1. Deriving it here would colour one model's number with another
|
|
16
|
+
* model's thresholds — they disagree between 55 and 59.
|
|
17
|
+
*/
|
|
18
|
+
band?: 'good' | 'fair' | 'poor'
|
|
11
19
|
/** When the SEO toggle uses `bg-primary`, render score in primary-foreground. */
|
|
12
20
|
inverted?: boolean
|
|
13
21
|
}) {
|
|
14
|
-
const meta = gradeMeta(gradeFromScore(score))
|
|
22
|
+
const meta = gradeMeta(band ?? gradeFromScore(score))
|
|
15
23
|
return (
|
|
16
24
|
<span
|
|
17
25
|
className={`inline-flex items-baseline gap-0.5 text-xs font-medium tabular-nums ${
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react'
|
|
4
|
+
import { ChevronDown, Search, Sparkles } from 'lucide-react'
|
|
5
|
+
import {
|
|
6
|
+
scoreBand,
|
|
7
|
+
type AuditCheck,
|
|
8
|
+
type AuditGroupResult,
|
|
9
|
+
type PageAuditResult,
|
|
10
|
+
} from '@prenta/core/seo/page-audit'
|
|
11
|
+
|
|
12
|
+
const BAND_TEXT = { good: 'text-success', fair: 'text-warning', poor: 'text-destructive' } as const
|
|
13
|
+
const BAND_STROKE = {
|
|
14
|
+
good: 'stroke-success',
|
|
15
|
+
fair: 'stroke-warning',
|
|
16
|
+
poor: 'stroke-destructive',
|
|
17
|
+
} as const
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* A 52px score ring.
|
|
21
|
+
*
|
|
22
|
+
* `null` is a real state, not an error: a page held back from search has no
|
|
23
|
+
* search performance to rate. It renders as a dash on a neutral track rather
|
|
24
|
+
* than an alarming zero.
|
|
25
|
+
*/
|
|
26
|
+
function ScoreRing({ score, label }: { score: number | null; label: string }) {
|
|
27
|
+
const R = 21
|
|
28
|
+
const CIRCUMFERENCE = 2 * Math.PI * R
|
|
29
|
+
const band = score === null ? null : scoreBand(score)
|
|
30
|
+
const dash = score === null ? 0 : (score / 100) * CIRCUMFERENCE
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<svg
|
|
34
|
+
width="52"
|
|
35
|
+
height="52"
|
|
36
|
+
viewBox="0 0 52 52"
|
|
37
|
+
role="img"
|
|
38
|
+
aria-label={score === null ? `${label}: not rated` : `${label} score: ${score} of 100`}
|
|
39
|
+
>
|
|
40
|
+
<circle cx="26" cy="26" r={R} className="stroke-border" strokeWidth="5" fill="none" />
|
|
41
|
+
{score !== null && (
|
|
42
|
+
<circle
|
|
43
|
+
cx="26"
|
|
44
|
+
cy="26"
|
|
45
|
+
r={R}
|
|
46
|
+
className={`${BAND_STROKE[band!]} motion-safe:transition-[stroke-dasharray,stroke] motion-safe:duration-350`}
|
|
47
|
+
strokeWidth="5"
|
|
48
|
+
fill="none"
|
|
49
|
+
strokeLinecap="round"
|
|
50
|
+
strokeDasharray={`${dash} ${CIRCUMFERENCE - dash}`}
|
|
51
|
+
// Start the arc at twelve o'clock instead of three.
|
|
52
|
+
transform="rotate(-90 26 26)"
|
|
53
|
+
/>
|
|
54
|
+
)}
|
|
55
|
+
<text
|
|
56
|
+
x="26"
|
|
57
|
+
y="26"
|
|
58
|
+
textAnchor="middle"
|
|
59
|
+
dominantBaseline="central"
|
|
60
|
+
className={`fill-current text-[15px] font-bold tabular-nums ${
|
|
61
|
+
band ? BAND_TEXT[band] : 'text-muted-foreground'
|
|
62
|
+
}`}
|
|
63
|
+
>
|
|
64
|
+
{score === null ? '—' : score}
|
|
65
|
+
</text>
|
|
66
|
+
</svg>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function CheckRow({ check }: { check: AuditCheck }) {
|
|
71
|
+
const na = check.outcome === 'not-applicable'
|
|
72
|
+
const pass = check.outcome === 'pass'
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<li className="border-border flex gap-2 border-b py-[7px] last:border-b-0">
|
|
76
|
+
<span
|
|
77
|
+
aria-hidden
|
|
78
|
+
className={`mt-0.5 h-4 w-4 shrink-0 rounded ${
|
|
79
|
+
na ? 'bg-muted' : pass ? 'bg-success/15 text-success' : 'bg-warning/15 text-warning'
|
|
80
|
+
}`}
|
|
81
|
+
/>
|
|
82
|
+
<div className="min-w-0">
|
|
83
|
+
<p className={`text-xs font-medium ${na ? 'text-muted-foreground' : 'text-foreground'}`}>
|
|
84
|
+
{check.label}
|
|
85
|
+
{/* The outcome must not rest on the colour of the square alone. */}
|
|
86
|
+
<span className="sr-only">
|
|
87
|
+
{na ? ' — not applicable' : pass ? ' — passing' : ' — needs attention'}
|
|
88
|
+
</span>
|
|
89
|
+
</p>
|
|
90
|
+
{/* A not-applicable check explains itself; a failing one says what to do.
|
|
91
|
+
A passing one needs neither. */}
|
|
92
|
+
{na ? (
|
|
93
|
+
<p className="text-muted-foreground text-[10.5px]">{check.naReason}</p>
|
|
94
|
+
) : pass ? null : (
|
|
95
|
+
<p className="text-muted-foreground text-[10.5px]">{check.hint}</p>
|
|
96
|
+
)}
|
|
97
|
+
</div>
|
|
98
|
+
</li>
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function CheckGroup({ result, title }: { result: AuditGroupResult; title: string }) {
|
|
103
|
+
const [open, setOpen] = useState(true)
|
|
104
|
+
const band = result.score === null ? null : scoreBand(result.score)
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<section className="border-border border-b">
|
|
108
|
+
<button
|
|
109
|
+
type="button"
|
|
110
|
+
onClick={() => setOpen((o) => !o)}
|
|
111
|
+
aria-expanded={open}
|
|
112
|
+
className="hover:bg-accent flex w-full items-center justify-between px-3.5 py-2.5 transition-colors"
|
|
113
|
+
>
|
|
114
|
+
<span className="text-muted-foreground text-[10px] font-bold tracking-[0.7px] uppercase">
|
|
115
|
+
{title}
|
|
116
|
+
</span>
|
|
117
|
+
<span className="flex items-center gap-1.5">
|
|
118
|
+
{/* The pill counts SCORED checks. Gates and not-applicable checks are
|
|
119
|
+
listed below but excluded, so the denominator matches the score. */}
|
|
120
|
+
<span
|
|
121
|
+
className={`rounded-full px-1.5 text-[10px] font-bold tabular-nums ${
|
|
122
|
+
band === 'good'
|
|
123
|
+
? 'bg-success/15 text-success'
|
|
124
|
+
: band === 'fair'
|
|
125
|
+
? 'bg-warning/15 text-warning'
|
|
126
|
+
: band === 'poor'
|
|
127
|
+
? 'bg-destructive/15 text-destructive'
|
|
128
|
+
: 'bg-muted text-muted-foreground'
|
|
129
|
+
}`}
|
|
130
|
+
>
|
|
131
|
+
{result.score === null ? 'n/a' : `${result.passed}/${result.applicable}`}
|
|
132
|
+
</span>
|
|
133
|
+
<ChevronDown
|
|
134
|
+
className={`text-muted-foreground h-3.5 w-3.5 transition-transform ${open ? 'rotate-180' : ''}`}
|
|
135
|
+
aria-hidden
|
|
136
|
+
/>
|
|
137
|
+
</span>
|
|
138
|
+
</button>
|
|
139
|
+
{open && (
|
|
140
|
+
<ul className="px-3.5 pb-3.5">
|
|
141
|
+
{result.checks.map((c) => (
|
|
142
|
+
<CheckRow key={c.id} check={c} />
|
|
143
|
+
))}
|
|
144
|
+
</ul>
|
|
145
|
+
)}
|
|
146
|
+
</section>
|
|
147
|
+
)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export interface PageAuditPanelProps {
|
|
151
|
+
audit: PageAuditResult
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Twin score rings over the SEO and GEO check lists.
|
|
156
|
+
*
|
|
157
|
+
* Both scores are reported the same way and neither is folded into a single
|
|
158
|
+
* headline: classic search and AI answer visibility fail for different reasons
|
|
159
|
+
* and are fixed by different edits, so averaging them would hide whichever is
|
|
160
|
+
* worse.
|
|
161
|
+
*/
|
|
162
|
+
export function PageAuditPanel({ audit }: PageAuditPanelProps) {
|
|
163
|
+
const { seo, geo } = audit
|
|
164
|
+
|
|
165
|
+
return (
|
|
166
|
+
<div className="border-border border-b">
|
|
167
|
+
<div className="flex items-start justify-around gap-3 px-3.5 py-4">
|
|
168
|
+
{[
|
|
169
|
+
{ r: seo, icon: Search, label: 'SEO', sub: 'Classic search & crawlability' },
|
|
170
|
+
{ r: geo, icon: Sparkles, label: 'GEO', sub: 'AI answer visibility' },
|
|
171
|
+
].map(({ r, icon: Icon, label, sub }) => (
|
|
172
|
+
<div key={label} className="flex min-w-0 flex-col items-center gap-1.5 text-center">
|
|
173
|
+
<ScoreRing score={r.score} label={label} />
|
|
174
|
+
<span className="text-foreground flex items-center gap-1 text-xs font-bold">
|
|
175
|
+
<Icon className="h-3 w-3" aria-hidden />
|
|
176
|
+
{label}
|
|
177
|
+
</span>
|
|
178
|
+
<span className="text-muted-foreground text-[10.5px] leading-tight">{sub}</span>
|
|
179
|
+
{/* Names the denominator, so a score that moves because the set of
|
|
180
|
+
applicable checks changed is explainable rather than mysterious. */}
|
|
181
|
+
<span className="text-muted-foreground text-[10px] tabular-nums">
|
|
182
|
+
{r.score === null ? 'not rated' : `${r.applicable} of ${r.total} apply`}
|
|
183
|
+
</span>
|
|
184
|
+
</div>
|
|
185
|
+
))}
|
|
186
|
+
</div>
|
|
187
|
+
|
|
188
|
+
<CheckGroup result={seo} title="SEO checks" />
|
|
189
|
+
<CheckGroup result={geo} title="GEO checks" />
|
|
190
|
+
</div>
|
|
191
|
+
)
|
|
192
|
+
}
|
|
@@ -1,44 +1,75 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { AlertTriangle, Check } from 'lucide-react'
|
|
4
4
|
|
|
5
5
|
export interface SeoCharCountProps {
|
|
6
6
|
value: string
|
|
7
|
-
/**
|
|
8
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Recommended minimum length (inclusive). Omit for cap-only fields, where
|
|
9
|
+
* short is not a fault — there is then no "too short" state and no "good",
|
|
10
|
+
* because there is no target to hit, only a ceiling not to cross.
|
|
11
|
+
*/
|
|
12
|
+
min?: number
|
|
9
13
|
/** Recommended/hard maximum length (inclusive). */
|
|
10
14
|
max: number
|
|
15
|
+
/** Hide the meter bar where vertical space is tight. */
|
|
16
|
+
bar?: boolean
|
|
11
17
|
}
|
|
12
18
|
|
|
13
19
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
20
|
+
* Length readout for an SEO field: a meter bar plus a stated count.
|
|
21
|
+
*
|
|
22
|
+
* Under and over are different problems and are named as such. "Too short"
|
|
23
|
+
* means search engines have less to match on; "will be truncated" means the
|
|
24
|
+
* tail is written but nobody will read it. The previous wording collapsed the
|
|
25
|
+
* short case into "aim for 30–60", which describes the target without saying
|
|
26
|
+
* which way to edit.
|
|
27
|
+
*
|
|
28
|
+
* Colour never carries meaning alone — the band is always spelled out in text,
|
|
29
|
+
* with an icon at the two decisive states.
|
|
18
30
|
*/
|
|
19
|
-
export function SeoCharCount({ value, min, max }: SeoCharCountProps) {
|
|
31
|
+
export function SeoCharCount({ value, min, max, bar = true }: SeoCharCountProps) {
|
|
20
32
|
const len = value.trim().length
|
|
21
33
|
const empty = len === 0
|
|
22
|
-
const
|
|
23
|
-
const
|
|
34
|
+
const over = len > max
|
|
35
|
+
const under = min !== undefined && !empty && len < min
|
|
36
|
+
const good = min !== undefined && !empty && !over && !under
|
|
24
37
|
|
|
25
|
-
const tone =
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
? `${len}/${max} · too long`
|
|
38
|
+
const tone = over
|
|
39
|
+
? 'text-destructive'
|
|
40
|
+
: under
|
|
41
|
+
? 'text-warning'
|
|
30
42
|
: good
|
|
31
|
-
?
|
|
32
|
-
:
|
|
43
|
+
? 'text-success'
|
|
44
|
+
: 'text-muted-foreground'
|
|
45
|
+
|
|
46
|
+
const fill = over ? 'bg-destructive' : under ? 'bg-warning' : good ? 'bg-success' : 'bg-border'
|
|
47
|
+
|
|
48
|
+
// Derived from `good`, not from "everything else": a cap-only field has no
|
|
49
|
+
// good state, and an unconditional else branch was labelling five characters
|
|
50
|
+
// in a 200-character field as "good".
|
|
51
|
+
const suffix = over ? ' · will be truncated' : under ? ' · too short' : good ? ' · good' : ''
|
|
33
52
|
|
|
34
53
|
return (
|
|
35
|
-
<span className=
|
|
36
|
-
{
|
|
37
|
-
<
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
54
|
+
<span className="inline-flex min-w-0 flex-col items-end gap-1">
|
|
55
|
+
{bar && (
|
|
56
|
+
<span className="bg-border h-0.75 w-20 overflow-hidden rounded-full" aria-hidden>
|
|
57
|
+
<span
|
|
58
|
+
className={`block h-full rounded-full transition-[width] ${fill}`}
|
|
59
|
+
// Past the maximum the bar stays full and the overflow is reported
|
|
60
|
+
// in words. A bar that kept growing would imply more is better.
|
|
61
|
+
style={{ width: `${Math.min(100, (len / max) * 100)}%` }}
|
|
62
|
+
/>
|
|
63
|
+
</span>
|
|
64
|
+
)}
|
|
65
|
+
<span className={`inline-flex items-center gap-1 text-[11px] whitespace-nowrap ${tone}`}>
|
|
66
|
+
{over ? (
|
|
67
|
+
<AlertTriangle size={11} aria-hidden />
|
|
68
|
+
) : good ? (
|
|
69
|
+
<Check size={11} aria-hidden />
|
|
70
|
+
) : null}
|
|
71
|
+
{len} / {max} characters{suffix}
|
|
72
|
+
</span>
|
|
42
73
|
</span>
|
|
43
74
|
)
|
|
44
75
|
}
|