@actuate-media/cms-admin 0.14.1 → 0.16.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/dist/AdminRoot.d.ts.map +1 -1
- package/dist/AdminRoot.js +148 -2
- package/dist/AdminRoot.js.map +1 -1
- package/dist/__tests__/components/posts-badges.test.d.ts +2 -0
- package/dist/__tests__/components/posts-badges.test.d.ts.map +1 -0
- package/dist/__tests__/components/posts-badges.test.js +68 -0
- package/dist/__tests__/components/posts-badges.test.js.map +1 -0
- package/dist/__tests__/layout/sidebar-posts.test.d.ts +2 -0
- package/dist/__tests__/layout/sidebar-posts.test.d.ts.map +1 -0
- package/dist/__tests__/layout/sidebar-posts.test.js +162 -0
- package/dist/__tests__/layout/sidebar-posts.test.js.map +1 -0
- package/dist/__tests__/lib/page-editor-service.test.d.ts +2 -0
- package/dist/__tests__/lib/page-editor-service.test.d.ts.map +1 -0
- package/dist/__tests__/lib/page-editor-service.test.js +137 -0
- package/dist/__tests__/lib/page-editor-service.test.js.map +1 -0
- package/dist/__tests__/lib/pages-service.test.d.ts +2 -0
- package/dist/__tests__/lib/pages-service.test.d.ts.map +1 -0
- package/dist/__tests__/lib/pages-service.test.js +140 -0
- package/dist/__tests__/lib/pages-service.test.js.map +1 -0
- package/dist/__tests__/lib/post-editor-service.test.d.ts +2 -0
- package/dist/__tests__/lib/post-editor-service.test.d.ts.map +1 -0
- package/dist/__tests__/lib/post-editor-service.test.js +185 -0
- package/dist/__tests__/lib/post-editor-service.test.js.map +1 -0
- package/dist/__tests__/lib/posts-service.test.d.ts +2 -0
- package/dist/__tests__/lib/posts-service.test.d.ts.map +1 -0
- package/dist/__tests__/lib/posts-service.test.js +244 -0
- package/dist/__tests__/lib/posts-service.test.js.map +1 -0
- package/dist/__tests__/views/posts-list-view.test.d.ts +2 -0
- package/dist/__tests__/views/posts-list-view.test.d.ts.map +1 -0
- package/dist/__tests__/views/posts-list-view.test.js +98 -0
- package/dist/__tests__/views/posts-list-view.test.js.map +1 -0
- package/dist/actuate-admin.css +1 -1
- package/dist/components/pages/tag-badges.d.ts +21 -0
- package/dist/components/pages/tag-badges.d.ts.map +1 -0
- package/dist/components/pages/tag-badges.js +56 -0
- package/dist/components/pages/tag-badges.js.map +1 -0
- package/dist/components/posts/badges.d.ts +25 -0
- package/dist/components/posts/badges.d.ts.map +1 -0
- package/dist/components/posts/badges.js +123 -0
- package/dist/components/posts/badges.js.map +1 -0
- package/dist/components/posts/icons.d.ts +12 -0
- package/dist/components/posts/icons.d.ts.map +1 -0
- package/dist/components/posts/icons.js +26 -0
- package/dist/components/posts/icons.js.map +1 -0
- package/dist/layout/Header.d.ts +14 -1
- package/dist/layout/Header.d.ts.map +1 -1
- package/dist/layout/Header.js +10 -3
- package/dist/layout/Header.js.map +1 -1
- package/dist/layout/Layout.d.ts.map +1 -1
- package/dist/layout/Layout.js +78 -2
- package/dist/layout/Layout.js.map +1 -1
- package/dist/layout/Sidebar.d.ts +16 -0
- package/dist/layout/Sidebar.d.ts.map +1 -1
- package/dist/layout/Sidebar.js +176 -49
- package/dist/layout/Sidebar.js.map +1 -1
- package/dist/lib/page-editor-service.d.ts +45 -0
- package/dist/lib/page-editor-service.d.ts.map +1 -0
- package/dist/lib/page-editor-service.js +153 -0
- package/dist/lib/page-editor-service.js.map +1 -0
- package/dist/lib/pages-service.d.ts +143 -0
- package/dist/lib/pages-service.d.ts.map +1 -0
- package/dist/lib/pages-service.js +444 -0
- package/dist/lib/pages-service.js.map +1 -0
- package/dist/lib/post-editor-service.d.ts +57 -0
- package/dist/lib/post-editor-service.d.ts.map +1 -0
- package/dist/lib/post-editor-service.js +234 -0
- package/dist/lib/post-editor-service.js.map +1 -0
- package/dist/lib/posts-service.d.ts +73 -0
- package/dist/lib/posts-service.d.ts.map +1 -0
- package/dist/lib/posts-service.js +245 -0
- package/dist/lib/posts-service.js.map +1 -0
- package/dist/lib/preview-link.d.ts +32 -0
- package/dist/lib/preview-link.d.ts.map +1 -0
- package/dist/lib/preview-link.js +40 -0
- package/dist/lib/preview-link.js.map +1 -0
- package/dist/router/index.d.ts +1 -0
- package/dist/router/index.d.ts.map +1 -1
- package/dist/router/index.js +9 -1
- package/dist/router/index.js.map +1 -1
- package/dist/views/Dashboard.d.ts.map +1 -1
- package/dist/views/Dashboard.js +147 -62
- package/dist/views/Dashboard.js.map +1 -1
- package/dist/views/Pages/PageTagManager.d.ts +14 -0
- package/dist/views/Pages/PageTagManager.d.ts.map +1 -0
- package/dist/views/Pages/PageTagManager.js +145 -0
- package/dist/views/Pages/PageTagManager.js.map +1 -0
- package/dist/views/Pages/PagesListView.d.ts +18 -0
- package/dist/views/Pages/PagesListView.d.ts.map +1 -0
- package/dist/views/Pages/PagesListView.js +287 -0
- package/dist/views/Pages/PagesListView.js.map +1 -0
- package/dist/views/Posts/NewPostPicker.d.ts +16 -0
- package/dist/views/Posts/NewPostPicker.d.ts.map +1 -0
- package/dist/views/Posts/NewPostPicker.js +59 -0
- package/dist/views/Posts/NewPostPicker.js.map +1 -0
- package/dist/views/Posts/NewPostTypeInstructions.d.ts +18 -0
- package/dist/views/Posts/NewPostTypeInstructions.d.ts.map +1 -0
- package/dist/views/Posts/NewPostTypeInstructions.js +147 -0
- package/dist/views/Posts/NewPostTypeInstructions.js.map +1 -0
- package/dist/views/Posts/PostTypesView.d.ts +26 -0
- package/dist/views/Posts/PostTypesView.d.ts.map +1 -0
- package/dist/views/Posts/PostTypesView.js +52 -0
- package/dist/views/Posts/PostTypesView.js.map +1 -0
- package/dist/views/Posts/PostsListView.d.ts +25 -0
- package/dist/views/Posts/PostsListView.d.ts.map +1 -0
- package/dist/views/Posts/PostsListView.js +239 -0
- package/dist/views/Posts/PostsListView.js.map +1 -0
- package/dist/views/Posts/post-type-presets.d.ts +32 -0
- package/dist/views/Posts/post-type-presets.d.ts.map +1 -0
- package/dist/views/Posts/post-type-presets.js +72 -0
- package/dist/views/Posts/post-type-presets.js.map +1 -0
- package/dist/views/page-builder/Widgets.d.ts +13 -0
- package/dist/views/page-builder/Widgets.d.ts.map +1 -0
- package/dist/views/page-builder/Widgets.js +15 -0
- package/dist/views/page-builder/Widgets.js.map +1 -0
- package/dist/views/page-editor/AddSectionModal.d.ts +20 -0
- package/dist/views/page-editor/AddSectionModal.d.ts.map +1 -0
- package/dist/views/page-editor/AddSectionModal.js +22 -0
- package/dist/views/page-editor/AddSectionModal.js.map +1 -0
- package/dist/views/page-editor/EditorCanvas.d.ts +18 -0
- package/dist/views/page-editor/EditorCanvas.d.ts.map +1 -0
- package/dist/views/page-editor/EditorCanvas.js +64 -0
- package/dist/views/page-editor/EditorCanvas.js.map +1 -0
- package/dist/views/page-editor/EditorTopBar.d.ts +30 -0
- package/dist/views/page-editor/EditorTopBar.d.ts.map +1 -0
- package/dist/views/page-editor/EditorTopBar.js +42 -0
- package/dist/views/page-editor/EditorTopBar.js.map +1 -0
- package/dist/views/page-editor/PagePreview.d.ts +13 -0
- package/dist/views/page-editor/PagePreview.d.ts.map +1 -0
- package/dist/views/page-editor/PagePreview.js +37 -0
- package/dist/views/page-editor/PagePreview.js.map +1 -0
- package/dist/views/page-editor/PageSectionEditor.d.ts +16 -0
- package/dist/views/page-editor/PageSectionEditor.d.ts.map +1 -0
- package/dist/views/page-editor/PageSectionEditor.js +272 -0
- package/dist/views/page-editor/PageSectionEditor.js.map +1 -0
- package/dist/views/page-editor/PageSettingsModal.d.ts +12 -0
- package/dist/views/page-editor/PageSettingsModal.d.ts.map +1 -0
- package/dist/views/page-editor/PageSettingsModal.js +52 -0
- package/dist/views/page-editor/PageSettingsModal.js.map +1 -0
- package/dist/views/page-editor/SectionInspector.d.ts +17 -0
- package/dist/views/page-editor/SectionInspector.d.ts.map +1 -0
- package/dist/views/page-editor/SectionInspector.js +98 -0
- package/dist/views/page-editor/SectionInspector.js.map +1 -0
- package/dist/views/page-editor/SectionsPanel.d.ts +15 -0
- package/dist/views/page-editor/SectionsPanel.d.ts.map +1 -0
- package/dist/views/page-editor/SectionsPanel.js +43 -0
- package/dist/views/page-editor/SectionsPanel.js.map +1 -0
- package/dist/views/page-editor/ValidationSummary.d.ts +9 -0
- package/dist/views/page-editor/ValidationSummary.d.ts.map +1 -0
- package/dist/views/page-editor/ValidationSummary.js +10 -0
- package/dist/views/page-editor/ValidationSummary.js.map +1 -0
- package/dist/views/page-editor/section-icons.d.ts +4 -0
- package/dist/views/page-editor/section-icons.d.ts.map +1 -0
- package/dist/views/page-editor/section-icons.js +20 -0
- package/dist/views/page-editor/section-icons.js.map +1 -0
- package/dist/views/page-editor/section-types.d.ts +11 -0
- package/dist/views/page-editor/section-types.d.ts.map +1 -0
- package/dist/views/page-editor/section-types.js +10 -0
- package/dist/views/page-editor/section-types.js.map +1 -0
- package/dist/views/page-editor/sections/ArticleBodySection.d.ts +13 -0
- package/dist/views/page-editor/sections/ArticleBodySection.d.ts.map +1 -0
- package/dist/views/page-editor/sections/ArticleBodySection.js +23 -0
- package/dist/views/page-editor/sections/ArticleBodySection.js.map +1 -0
- package/dist/views/page-editor/sections/AuthorBioSection.d.ts +8 -0
- package/dist/views/page-editor/sections/AuthorBioSection.d.ts.map +1 -0
- package/dist/views/page-editor/sections/AuthorBioSection.js +21 -0
- package/dist/views/page-editor/sections/AuthorBioSection.js.map +1 -0
- package/dist/views/page-editor/sections/ByTheNumbersSection.d.ts +5 -0
- package/dist/views/page-editor/sections/ByTheNumbersSection.d.ts.map +1 -0
- package/dist/views/page-editor/sections/ByTheNumbersSection.js +10 -0
- package/dist/views/page-editor/sections/ByTheNumbersSection.js.map +1 -0
- package/dist/views/page-editor/sections/CtaBannerSection.d.ts +5 -0
- package/dist/views/page-editor/sections/CtaBannerSection.d.ts.map +1 -0
- package/dist/views/page-editor/sections/CtaBannerSection.js +10 -0
- package/dist/views/page-editor/sections/CtaBannerSection.js.map +1 -0
- package/dist/views/page-editor/sections/FeatureHighlightSection.d.ts +5 -0
- package/dist/views/page-editor/sections/FeatureHighlightSection.d.ts.map +1 -0
- package/dist/views/page-editor/sections/FeatureHighlightSection.js +18 -0
- package/dist/views/page-editor/sections/FeatureHighlightSection.js.map +1 -0
- package/dist/views/page-editor/sections/GallerySection.d.ts +6 -0
- package/dist/views/page-editor/sections/GallerySection.d.ts.map +1 -0
- package/dist/views/page-editor/sections/GallerySection.js +17 -0
- package/dist/views/page-editor/sections/GallerySection.js.map +1 -0
- package/dist/views/page-editor/sections/HeroBannerSection.d.ts +5 -0
- package/dist/views/page-editor/sections/HeroBannerSection.d.ts.map +1 -0
- package/dist/views/page-editor/sections/HeroBannerSection.js +11 -0
- package/dist/views/page-editor/sections/HeroBannerSection.js.map +1 -0
- package/dist/views/page-editor/sections/MissionSection.d.ts +6 -0
- package/dist/views/page-editor/sections/MissionSection.d.ts.map +1 -0
- package/dist/views/page-editor/sections/MissionSection.js +12 -0
- package/dist/views/page-editor/sections/MissionSection.js.map +1 -0
- package/dist/views/page-editor/sections/PullQuoteSection.d.ts +6 -0
- package/dist/views/page-editor/sections/PullQuoteSection.d.ts.map +1 -0
- package/dist/views/page-editor/sections/PullQuoteSection.js +13 -0
- package/dist/views/page-editor/sections/PullQuoteSection.js.map +1 -0
- package/dist/views/page-editor/sections/RelatedPostsSection.d.ts +12 -0
- package/dist/views/page-editor/sections/RelatedPostsSection.d.ts.map +1 -0
- package/dist/views/page-editor/sections/RelatedPostsSection.js +23 -0
- package/dist/views/page-editor/sections/RelatedPostsSection.js.map +1 -0
- package/dist/views/page-editor/sections/SectionRenderer.d.ts +22 -0
- package/dist/views/page-editor/sections/SectionRenderer.d.ts.map +1 -0
- package/dist/views/page-editor/sections/SectionRenderer.js +52 -0
- package/dist/views/page-editor/sections/SectionRenderer.js.map +1 -0
- package/dist/views/page-editor/sections/index.d.ts +14 -0
- package/dist/views/page-editor/sections/index.d.ts.map +1 -0
- package/dist/views/page-editor/sections/index.js +12 -0
- package/dist/views/page-editor/sections/index.js.map +1 -0
- package/dist/views/page-editor/sections/parts.d.ts +55 -0
- package/dist/views/page-editor/sections/parts.d.ts.map +1 -0
- package/dist/views/page-editor/sections/parts.js +60 -0
- package/dist/views/page-editor/sections/parts.js.map +1 -0
- package/dist/views/page-editor/viewports.d.ts +14 -0
- package/dist/views/page-editor/viewports.d.ts.map +1 -0
- package/dist/views/page-editor/viewports.js +12 -0
- package/dist/views/page-editor/viewports.js.map +1 -0
- package/dist/views/post-editor/PostEditorCanvas.d.ts +21 -0
- package/dist/views/post-editor/PostEditorCanvas.d.ts.map +1 -0
- package/dist/views/post-editor/PostEditorCanvas.js +58 -0
- package/dist/views/post-editor/PostEditorCanvas.js.map +1 -0
- package/dist/views/post-editor/PostFieldsModal.d.ts +12 -0
- package/dist/views/post-editor/PostFieldsModal.d.ts.map +1 -0
- package/dist/views/post-editor/PostFieldsModal.js +53 -0
- package/dist/views/post-editor/PostFieldsModal.js.map +1 -0
- package/dist/views/post-editor/PostHeader.d.ts +15 -0
- package/dist/views/post-editor/PostHeader.d.ts.map +1 -0
- package/dist/views/post-editor/PostHeader.js +27 -0
- package/dist/views/post-editor/PostHeader.js.map +1 -0
- package/dist/views/post-editor/PostHeaderPanel.d.ts +11 -0
- package/dist/views/post-editor/PostHeaderPanel.d.ts.map +1 -0
- package/dist/views/post-editor/PostHeaderPanel.js +21 -0
- package/dist/views/post-editor/PostHeaderPanel.js.map +1 -0
- package/dist/views/post-editor/PostPreview.d.ts +14 -0
- package/dist/views/post-editor/PostPreview.d.ts.map +1 -0
- package/dist/views/post-editor/PostPreview.js +53 -0
- package/dist/views/post-editor/PostPreview.js.map +1 -0
- package/dist/views/post-editor/PostSectionEditor.d.ts +17 -0
- package/dist/views/post-editor/PostSectionEditor.d.ts.map +1 -0
- package/dist/views/post-editor/PostSectionEditor.js +290 -0
- package/dist/views/post-editor/PostSectionEditor.js.map +1 -0
- package/dist/views/post-editor/PostTemplateEditor.d.ts +9 -0
- package/dist/views/post-editor/PostTemplateEditor.d.ts.map +1 -0
- package/dist/views/post-editor/PostTemplateEditor.js +144 -0
- package/dist/views/post-editor/PostTemplateEditor.js.map +1 -0
- package/package.json +2 -2
- package/src/AdminRoot.tsx +213 -2
- package/src/__tests__/components/posts-badges.test.tsx +77 -0
- package/src/__tests__/layout/sidebar-posts.test.ts +179 -0
- package/src/__tests__/lib/page-editor-service.test.ts +173 -0
- package/src/__tests__/lib/pages-service.test.ts +157 -0
- package/src/__tests__/lib/post-editor-service.test.ts +215 -0
- package/src/__tests__/lib/posts-service.test.ts +279 -0
- package/src/__tests__/views/posts-list-view.test.ts +110 -0
- package/src/components/pages/tag-badges.tsx +104 -0
- package/src/components/posts/badges.tsx +170 -0
- package/src/components/posts/icons.ts +40 -0
- package/src/layout/Header.tsx +90 -43
- package/src/layout/Layout.tsx +166 -1
- package/src/layout/Sidebar.tsx +287 -101
- package/src/lib/page-editor-service.ts +233 -0
- package/src/lib/pages-service.ts +599 -0
- package/src/lib/post-editor-service.ts +342 -0
- package/src/lib/posts-service.ts +366 -0
- package/src/lib/preview-link.ts +63 -0
- package/src/router/index.ts +10 -1
- package/src/views/Dashboard.tsx +270 -131
- package/src/views/Pages/PageTagManager.tsx +436 -0
- package/src/views/Pages/PagesListView.tsx +944 -0
- package/src/views/Posts/NewPostPicker.tsx +147 -0
- package/src/views/Posts/NewPostTypeInstructions.tsx +344 -0
- package/src/views/Posts/PostTypesView.tsx +194 -0
- package/src/views/Posts/PostsListView.tsx +869 -0
- package/src/views/Posts/post-type-presets.ts +105 -0
- package/src/views/page-builder/Widgets.tsx +44 -0
- package/src/views/page-editor/AddSectionModal.tsx +56 -0
- package/src/views/page-editor/EditorCanvas.tsx +127 -0
- package/src/views/page-editor/EditorTopBar.tsx +233 -0
- package/src/views/page-editor/PagePreview.tsx +96 -0
- package/src/views/page-editor/PageSectionEditor.tsx +476 -0
- package/src/views/page-editor/PageSettingsModal.tsx +154 -0
- package/src/views/page-editor/SectionInspector.tsx +599 -0
- package/src/views/page-editor/SectionsPanel.tsx +270 -0
- package/src/views/page-editor/ValidationSummary.tsx +53 -0
- package/src/views/page-editor/section-icons.ts +33 -0
- package/src/views/page-editor/section-types.ts +27 -0
- package/src/views/page-editor/sections/ArticleBodySection.tsx +57 -0
- package/src/views/page-editor/sections/AuthorBioSection.tsx +61 -0
- package/src/views/page-editor/sections/ByTheNumbersSection.tsx +41 -0
- package/src/views/page-editor/sections/CtaBannerSection.tsx +29 -0
- package/src/views/page-editor/sections/FeatureHighlightSection.tsx +63 -0
- package/src/views/page-editor/sections/GallerySection.tsx +54 -0
- package/src/views/page-editor/sections/HeroBannerSection.tsx +44 -0
- package/src/views/page-editor/sections/MissionSection.tsx +22 -0
- package/src/views/page-editor/sections/PullQuoteSection.tsx +27 -0
- package/src/views/page-editor/sections/RelatedPostsSection.tsx +55 -0
- package/src/views/page-editor/sections/SectionRenderer.tsx +102 -0
- package/src/views/page-editor/sections/index.ts +13 -0
- package/src/views/page-editor/sections/parts.tsx +113 -0
- package/src/views/page-editor/viewports.ts +25 -0
- package/src/views/post-editor/PostEditorCanvas.tsx +123 -0
- package/src/views/post-editor/PostFieldsModal.tsx +196 -0
- package/src/views/post-editor/PostHeader.tsx +87 -0
- package/src/views/post-editor/PostHeaderPanel.tsx +107 -0
- package/src/views/post-editor/PostPreview.tsx +114 -0
- package/src/views/post-editor/PostSectionEditor.tsx +506 -0
- package/src/views/post-editor/PostTemplateEditor.tsx +347 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { PageSection } from '../../../lib/page-editor-service.js'
|
|
2
|
+
import { PreviewButton, isDarkBackground, str } from './parts.js'
|
|
3
|
+
|
|
4
|
+
export function HeroBannerSection({ section }: { section: PageSection }) {
|
|
5
|
+
const c = section.content
|
|
6
|
+
const dark = isDarkBackground(section.settings)
|
|
7
|
+
const eyebrow = str(c, 'eyebrow')
|
|
8
|
+
const heading = str(c, 'heading')
|
|
9
|
+
const body = str(c, 'body')
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
<div className="mx-auto max-w-3xl px-6 text-center">
|
|
13
|
+
{eyebrow && (
|
|
14
|
+
<p
|
|
15
|
+
className={`mb-3 text-sm font-medium tracking-wide uppercase ${dark ? 'text-violet-200' : 'text-violet-600'}`}
|
|
16
|
+
>
|
|
17
|
+
{eyebrow}
|
|
18
|
+
</p>
|
|
19
|
+
)}
|
|
20
|
+
<h1 className="text-3xl leading-tight font-semibold tracking-tight sm:text-5xl">
|
|
21
|
+
{heading || 'Your headline goes here'}
|
|
22
|
+
</h1>
|
|
23
|
+
{body && (
|
|
24
|
+
<p
|
|
25
|
+
className={`mx-auto mt-5 max-w-2xl text-base sm:text-lg ${dark ? 'text-violet-100' : 'text-zinc-600'}`}
|
|
26
|
+
>
|
|
27
|
+
{body}
|
|
28
|
+
</p>
|
|
29
|
+
)}
|
|
30
|
+
<div className="mt-8 flex flex-wrap items-center justify-center gap-3">
|
|
31
|
+
<PreviewButton
|
|
32
|
+
label={str(c, 'primaryButtonLabel')}
|
|
33
|
+
href={str(c, 'primaryButtonUrl')}
|
|
34
|
+
tone={dark ? 'onDark' : 'primary'}
|
|
35
|
+
/>
|
|
36
|
+
<PreviewButton
|
|
37
|
+
label={str(c, 'secondaryButtonLabel')}
|
|
38
|
+
href={str(c, 'secondaryButtonUrl')}
|
|
39
|
+
tone="ghost"
|
|
40
|
+
/>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { PageSection } from '../../../lib/page-editor-service.js'
|
|
2
|
+
import { str } from './parts.js'
|
|
3
|
+
|
|
4
|
+
/** Generic "Text Block" / mission statement section. */
|
|
5
|
+
export function MissionSection({ section }: { section: PageSection }) {
|
|
6
|
+
const c = section.content
|
|
7
|
+
const alignment = str(c, 'alignment', 'center')
|
|
8
|
+
const align = alignment === 'left' ? 'text-left' : 'text-center mx-auto'
|
|
9
|
+
const heading = str(c, 'heading')
|
|
10
|
+
const body = str(c, 'body')
|
|
11
|
+
|
|
12
|
+
return (
|
|
13
|
+
<div className={`max-w-3xl px-6 ${align}`}>
|
|
14
|
+
{heading && (
|
|
15
|
+
<h2 className="text-2xl font-semibold tracking-tight text-zinc-900 sm:text-3xl">
|
|
16
|
+
{heading}
|
|
17
|
+
</h2>
|
|
18
|
+
)}
|
|
19
|
+
{body && <p className="mt-4 text-base leading-relaxed text-zinc-600 sm:text-lg">{body}</p>}
|
|
20
|
+
</div>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { PageSection } from '../../../lib/page-editor-service.js'
|
|
2
|
+
import { str } from './parts.js'
|
|
3
|
+
|
|
4
|
+
/** A highlighted pull quote with optional attribution. */
|
|
5
|
+
export function PullQuoteSection({ section }: { section: PageSection }) {
|
|
6
|
+
const c = section.content
|
|
7
|
+
const quote = str(c, 'quote')
|
|
8
|
+
const attribution = str(c, 'attribution')
|
|
9
|
+
const role = str(c, 'role')
|
|
10
|
+
|
|
11
|
+
if (!quote) return null
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<figure className="mx-auto max-w-3xl px-6">
|
|
15
|
+
<blockquote className="border-l-4 border-violet-500 pl-6">
|
|
16
|
+
<p className="text-xl leading-relaxed font-medium text-zinc-800 sm:text-2xl">“{quote}”</p>
|
|
17
|
+
</blockquote>
|
|
18
|
+
{(attribution || role) && (
|
|
19
|
+
<figcaption className="mt-3 pl-6 text-sm text-zinc-500">
|
|
20
|
+
{attribution && <span className="font-medium text-zinc-700">{attribution}</span>}
|
|
21
|
+
{attribution && role && <span aria-hidden> · </span>}
|
|
22
|
+
{role}
|
|
23
|
+
</figcaption>
|
|
24
|
+
)}
|
|
25
|
+
</figure>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { PageSection } from '../../../lib/page-editor-service.js'
|
|
2
|
+
import { str, type PostRenderContext } from './parts.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A grid of related posts. Uses resolved posts from context when available;
|
|
6
|
+
* otherwise renders placeholder cards so the layout reads correctly in the
|
|
7
|
+
* editor before data is wired up.
|
|
8
|
+
*/
|
|
9
|
+
export function RelatedPostsSection({
|
|
10
|
+
section,
|
|
11
|
+
context,
|
|
12
|
+
}: {
|
|
13
|
+
section: PageSection
|
|
14
|
+
context?: PostRenderContext
|
|
15
|
+
}) {
|
|
16
|
+
const c = section.content
|
|
17
|
+
const heading = str(c, 'heading', 'Related posts')
|
|
18
|
+
const count = Number.parseInt(str(c, 'count', '3'), 10) || 3
|
|
19
|
+
|
|
20
|
+
const resolved = context?.relatedPosts ?? []
|
|
21
|
+
const cards =
|
|
22
|
+
resolved.length > 0
|
|
23
|
+
? resolved.slice(0, count)
|
|
24
|
+
: Array.from({ length: count }, (_, i) => ({
|
|
25
|
+
title: `Related post ${i + 1}`,
|
|
26
|
+
excerpt: 'A short summary of the related post will appear here.',
|
|
27
|
+
url: undefined as string | undefined,
|
|
28
|
+
}))
|
|
29
|
+
|
|
30
|
+
const cols = count >= 4 ? 'sm:grid-cols-2 lg:grid-cols-4' : 'sm:grid-cols-3'
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<div className="mx-auto max-w-5xl px-6">
|
|
34
|
+
{heading && (
|
|
35
|
+
<h2 className="mb-6 text-2xl font-semibold tracking-tight text-zinc-900">{heading}</h2>
|
|
36
|
+
)}
|
|
37
|
+
<div className={`grid grid-cols-1 gap-4 ${cols}`}>
|
|
38
|
+
{cards.map((post, i) => (
|
|
39
|
+
<div
|
|
40
|
+
key={i}
|
|
41
|
+
className="overflow-hidden rounded-xl border border-zinc-200 bg-white shadow-sm"
|
|
42
|
+
>
|
|
43
|
+
<div className="aspect-[16/9] bg-zinc-100" aria-hidden />
|
|
44
|
+
<div className="p-4">
|
|
45
|
+
<p className="text-base font-medium text-zinc-900">{post.title}</p>
|
|
46
|
+
{post.excerpt && (
|
|
47
|
+
<p className="mt-1 line-clamp-2 text-sm text-zinc-500">{post.excerpt}</p>
|
|
48
|
+
)}
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
))}
|
|
52
|
+
</div>
|
|
53
|
+
</div>
|
|
54
|
+
)
|
|
55
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import type { PageSection } from '../../../lib/page-editor-service.js'
|
|
4
|
+
import { backgroundClass, paddingClass, type PostRenderContext } from './parts.js'
|
|
5
|
+
import { HeroBannerSection } from './HeroBannerSection.js'
|
|
6
|
+
import { MissionSection } from './MissionSection.js'
|
|
7
|
+
import { FeatureHighlightSection } from './FeatureHighlightSection.js'
|
|
8
|
+
import { ByTheNumbersSection } from './ByTheNumbersSection.js'
|
|
9
|
+
import { CtaBannerSection } from './CtaBannerSection.js'
|
|
10
|
+
import { ArticleBodySection } from './ArticleBodySection.js'
|
|
11
|
+
import { PullQuoteSection } from './PullQuoteSection.js'
|
|
12
|
+
import { AuthorBioSection } from './AuthorBioSection.js'
|
|
13
|
+
import { RelatedPostsSection } from './RelatedPostsSection.js'
|
|
14
|
+
import { GallerySection } from './GallerySection.js'
|
|
15
|
+
|
|
16
|
+
/** Dispatch a section's body component by type. Shared by editor + preview. */
|
|
17
|
+
function SectionBody({ section, context }: { section: PageSection; context?: PostRenderContext }) {
|
|
18
|
+
switch (section.sectionType) {
|
|
19
|
+
case 'hero':
|
|
20
|
+
return <HeroBannerSection section={section} />
|
|
21
|
+
case 'text':
|
|
22
|
+
return <MissionSection section={section} />
|
|
23
|
+
case 'feature':
|
|
24
|
+
return <FeatureHighlightSection section={section} />
|
|
25
|
+
case 'stats':
|
|
26
|
+
return <ByTheNumbersSection section={section} />
|
|
27
|
+
case 'cta':
|
|
28
|
+
return <CtaBannerSection section={section} />
|
|
29
|
+
case 'article-body':
|
|
30
|
+
return <ArticleBodySection section={section} context={context} />
|
|
31
|
+
case 'quote':
|
|
32
|
+
return <PullQuoteSection section={section} />
|
|
33
|
+
case 'author-bio':
|
|
34
|
+
return <AuthorBioSection section={section} context={context} />
|
|
35
|
+
case 'related-posts':
|
|
36
|
+
return <RelatedPostsSection section={section} context={context} />
|
|
37
|
+
case 'gallery':
|
|
38
|
+
return <GallerySection section={section} />
|
|
39
|
+
default:
|
|
40
|
+
return null
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface SectionRendererProps {
|
|
45
|
+
section: PageSection
|
|
46
|
+
/** Editor mode: adds click-to-select, the selection outline + label, and
|
|
47
|
+
* renders hidden sections muted instead of removing them. */
|
|
48
|
+
editable?: boolean
|
|
49
|
+
selected?: boolean
|
|
50
|
+
onSelect?: (id: string) => void
|
|
51
|
+
/** Lets the canvas register the DOM node for scroll-to-section. */
|
|
52
|
+
registerRef?: (id: string, el: HTMLElement | null) => void
|
|
53
|
+
/** Live post data for post-oriented section renderers (pages omit this). */
|
|
54
|
+
context?: PostRenderContext
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Renders one section with its background + padding, dispatching to the
|
|
59
|
+
* shared body component. In `editable` mode it layers editor-only chrome
|
|
60
|
+
* (outline + a small purple type label) on top of the same markup the
|
|
61
|
+
* public preview uses — so what you edit is what ships.
|
|
62
|
+
*/
|
|
63
|
+
export function SectionRenderer({
|
|
64
|
+
section,
|
|
65
|
+
editable = false,
|
|
66
|
+
selected = false,
|
|
67
|
+
onSelect,
|
|
68
|
+
registerRef,
|
|
69
|
+
context,
|
|
70
|
+
}: SectionRendererProps) {
|
|
71
|
+
const settings = section.settings ?? {}
|
|
72
|
+
const hidden = editable && !section.visible
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<section
|
|
76
|
+
ref={(el) => registerRef?.(section.id, el)}
|
|
77
|
+
data-section-id={section.id}
|
|
78
|
+
id={settings.anchorId || undefined}
|
|
79
|
+
className={`relative ${backgroundClass(settings)} ${paddingClass(settings)} ${
|
|
80
|
+
settings.cssClass ?? ''
|
|
81
|
+
} ${hidden ? 'opacity-40' : ''} ${
|
|
82
|
+
editable ? 'cursor-pointer outline-2 -outline-offset-2 transition-[outline]' : ''
|
|
83
|
+
} ${selected ? 'outline outline-violet-500' : 'outline-transparent'}`}
|
|
84
|
+
onClick={editable ? () => onSelect?.(section.id) : undefined}
|
|
85
|
+
// Selection is a convenience in the editor; the section list is the
|
|
86
|
+
// primary, fully-accessible control surface, so this stays a region.
|
|
87
|
+
aria-label={editable ? `${section.name} section` : undefined}
|
|
88
|
+
>
|
|
89
|
+
{editable && selected && (
|
|
90
|
+
<span className="pointer-events-none absolute top-0 left-0 z-10 rounded-br-md bg-violet-600 px-2 py-0.5 text-xs font-medium text-white">
|
|
91
|
+
{section.name}
|
|
92
|
+
</span>
|
|
93
|
+
)}
|
|
94
|
+
{editable && hidden && (
|
|
95
|
+
<span className="pointer-events-none absolute top-0 right-0 z-10 rounded-bl-md bg-zinc-700 px-2 py-0.5 text-xs font-medium text-white">
|
|
96
|
+
Hidden
|
|
97
|
+
</span>
|
|
98
|
+
)}
|
|
99
|
+
<SectionBody section={section} context={context} />
|
|
100
|
+
</section>
|
|
101
|
+
)
|
|
102
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { SectionRenderer } from './SectionRenderer.js'
|
|
2
|
+
export type { SectionRendererProps } from './SectionRenderer.js'
|
|
3
|
+
export type { PostRenderContext } from './parts.js'
|
|
4
|
+
export { HeroBannerSection } from './HeroBannerSection.js'
|
|
5
|
+
export { MissionSection } from './MissionSection.js'
|
|
6
|
+
export { FeatureHighlightSection } from './FeatureHighlightSection.js'
|
|
7
|
+
export { ByTheNumbersSection } from './ByTheNumbersSection.js'
|
|
8
|
+
export { CtaBannerSection } from './CtaBannerSection.js'
|
|
9
|
+
export { ArticleBodySection } from './ArticleBodySection.js'
|
|
10
|
+
export { PullQuoteSection } from './PullQuoteSection.js'
|
|
11
|
+
export { AuthorBioSection } from './AuthorBioSection.js'
|
|
12
|
+
export { RelatedPostsSection } from './RelatedPostsSection.js'
|
|
13
|
+
export { GallerySection } from './GallerySection.js'
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared building blocks for the section renderers.
|
|
3
|
+
*
|
|
4
|
+
* NOTE ON STYLING: the components under `sections/` render the consumer's
|
|
5
|
+
* *public website* content (purple hero, white prose bands, etc.), not the
|
|
6
|
+
* admin chrome. They therefore use a small, fixed website palette (violet
|
|
7
|
+
* for primary, zinc for neutrals) instead of the admin theme tokens. This
|
|
8
|
+
* is deliberate and scoped to this folder so the preview looks like a real
|
|
9
|
+
* page; the surrounding editor UI still uses the admin design tokens.
|
|
10
|
+
*/
|
|
11
|
+
import type { SectionSettings } from '../section-types.js'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Live post data passed to the post-oriented section renderers
|
|
15
|
+
* (article body, author bio, related posts). Pages never supply this, so
|
|
16
|
+
* every consumer treats it as optional and falls back to placeholder copy.
|
|
17
|
+
*/
|
|
18
|
+
export interface PostRenderContext {
|
|
19
|
+
title?: string
|
|
20
|
+
excerpt?: string
|
|
21
|
+
/** the post's main body copy (rendered by the Article Body section) */
|
|
22
|
+
body?: string
|
|
23
|
+
featuredImageUrl?: string
|
|
24
|
+
publishDate?: string | null
|
|
25
|
+
category?: string
|
|
26
|
+
author?: { name?: string; bio?: string; avatarUrl?: string }
|
|
27
|
+
/** resolved related posts for the Related Posts section */
|
|
28
|
+
relatedPosts?: Array<{ title: string; url?: string; excerpt?: string }>
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function str(content: Record<string, unknown>, key: string, fallback = ''): string {
|
|
32
|
+
const v = content[key]
|
|
33
|
+
return typeof v === 'string' ? v : fallback
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function bool(content: Record<string, unknown>, key: string, fallback = false): boolean {
|
|
37
|
+
const v = content[key]
|
|
38
|
+
return typeof v === 'boolean' ? v : fallback
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Vertical padding scale for a section. */
|
|
42
|
+
export function paddingClass(settings: SectionSettings): string {
|
|
43
|
+
switch (settings.paddingY) {
|
|
44
|
+
case 'sm':
|
|
45
|
+
return 'py-8'
|
|
46
|
+
case 'md':
|
|
47
|
+
return 'py-12'
|
|
48
|
+
case 'xl':
|
|
49
|
+
return 'py-20 sm:py-28'
|
|
50
|
+
case 'lg':
|
|
51
|
+
default:
|
|
52
|
+
return 'py-16 sm:py-20'
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Returns true when the section background is the dark/purple treatment. */
|
|
57
|
+
export function isDarkBackground(settings: SectionSettings): boolean {
|
|
58
|
+
return (
|
|
59
|
+
(settings.backgroundType === 'solid' || settings.backgroundType === 'gradient') &&
|
|
60
|
+
settings.backgroundColor === 'purple'
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Background utility classes derived from settings. */
|
|
65
|
+
export function backgroundClass(settings: SectionSettings): string {
|
|
66
|
+
if (settings.backgroundType === 'gradient' && settings.backgroundColor === 'purple') {
|
|
67
|
+
return 'bg-gradient-to-br from-violet-600 to-violet-700 text-white'
|
|
68
|
+
}
|
|
69
|
+
if (settings.backgroundType === 'solid' && settings.backgroundColor === 'purple') {
|
|
70
|
+
return 'bg-violet-600 text-white'
|
|
71
|
+
}
|
|
72
|
+
if (settings.backgroundType === 'solid' && settings.backgroundColor === 'muted') {
|
|
73
|
+
return 'bg-zinc-50 text-zinc-900'
|
|
74
|
+
}
|
|
75
|
+
return 'bg-white text-zinc-900'
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Preview-only button. `tone="onDark"` renders the white-on-purple button
|
|
80
|
+
* from the screenshot; `tone="primary"` renders a solid violet button for
|
|
81
|
+
* light backgrounds.
|
|
82
|
+
*/
|
|
83
|
+
export function PreviewButton({
|
|
84
|
+
label,
|
|
85
|
+
href,
|
|
86
|
+
tone = 'primary',
|
|
87
|
+
}: {
|
|
88
|
+
label: string
|
|
89
|
+
href?: string
|
|
90
|
+
tone?: 'primary' | 'onDark' | 'ghost'
|
|
91
|
+
}) {
|
|
92
|
+
if (!label) return null
|
|
93
|
+
const base =
|
|
94
|
+
'inline-flex items-center justify-center rounded-lg px-5 py-2.5 text-sm font-medium transition-colors'
|
|
95
|
+
const toneClass =
|
|
96
|
+
tone === 'onDark'
|
|
97
|
+
? 'bg-white text-violet-700 hover:bg-violet-50'
|
|
98
|
+
: tone === 'ghost'
|
|
99
|
+
? 'border border-zinc-300 text-zinc-700 hover:bg-zinc-50'
|
|
100
|
+
: 'bg-violet-600 text-white hover:bg-violet-700'
|
|
101
|
+
// Editor preview: links are inert (preventDefault) so clicking content
|
|
102
|
+
// selects the section instead of navigating away.
|
|
103
|
+
return (
|
|
104
|
+
<a
|
|
105
|
+
href={href || '#'}
|
|
106
|
+
onClick={(e) => e.preventDefault()}
|
|
107
|
+
className={`${base} ${toneClass}`}
|
|
108
|
+
tabIndex={-1}
|
|
109
|
+
>
|
|
110
|
+
{label}
|
|
111
|
+
</a>
|
|
112
|
+
)
|
|
113
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Preview viewport presets for the page editor canvas. */
|
|
2
|
+
export type ViewportId = '4k' | '1080p' | 'laptop' | 'tablet' | 'mobile'
|
|
3
|
+
|
|
4
|
+
export interface Viewport {
|
|
5
|
+
id: ViewportId
|
|
6
|
+
label: string
|
|
7
|
+
/** lucide-react icon name */
|
|
8
|
+
icon: string
|
|
9
|
+
/** logical CSS width of the preview surface, in px */
|
|
10
|
+
width: number
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const VIEWPORTS: Viewport[] = [
|
|
14
|
+
{ id: '4k', label: '4K', icon: 'monitor', width: 1920 },
|
|
15
|
+
{ id: '1080p', label: '1080p', icon: 'monitor', width: 1440 },
|
|
16
|
+
{ id: 'laptop', label: 'Laptop', icon: 'laptop', width: 1280 },
|
|
17
|
+
{ id: 'tablet', label: 'Tablet', icon: 'tablet', width: 768 },
|
|
18
|
+
{ id: 'mobile', label: 'Mobile', icon: 'smartphone', width: 390 },
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
export const DEFAULT_VIEWPORT: ViewportId = '1080p'
|
|
22
|
+
|
|
23
|
+
export function getViewport(id: ViewportId): Viewport {
|
|
24
|
+
return VIEWPORTS.find((v) => v.id === id) ?? VIEWPORTS[1]!
|
|
25
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'
|
|
4
|
+
import { FileText, Plus } from 'lucide-react'
|
|
5
|
+
import type { PageSection, PostHeaderConfig } from '../../lib/post-editor-service.js'
|
|
6
|
+
import { SectionRenderer, type PostRenderContext } from '../page-editor/sections/index.js'
|
|
7
|
+
import { getViewport, type ViewportId } from '../page-editor/viewports.js'
|
|
8
|
+
import { PostHeader } from './PostHeader.js'
|
|
9
|
+
|
|
10
|
+
interface PostEditorCanvasProps {
|
|
11
|
+
sections: PageSection[]
|
|
12
|
+
header: PostHeaderConfig
|
|
13
|
+
context: PostRenderContext
|
|
14
|
+
selectedId: string | null
|
|
15
|
+
viewport: ViewportId
|
|
16
|
+
onSelect: (id: string) => void
|
|
17
|
+
onAddSection: () => void
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Post preview canvas. Mirrors the Page editor canvas (scaled surface that
|
|
22
|
+
* fits the selected viewport) but renders the post header above the section
|
|
23
|
+
* list so the editor shows the full post layout — header regions plus body
|
|
24
|
+
* sections — exactly as it will ship.
|
|
25
|
+
*/
|
|
26
|
+
export function PostEditorCanvas({
|
|
27
|
+
sections,
|
|
28
|
+
header,
|
|
29
|
+
context,
|
|
30
|
+
selectedId,
|
|
31
|
+
viewport,
|
|
32
|
+
onSelect,
|
|
33
|
+
onAddSection,
|
|
34
|
+
}: PostEditorCanvasProps) {
|
|
35
|
+
const vp = getViewport(viewport)
|
|
36
|
+
const containerRef = useRef<HTMLDivElement>(null)
|
|
37
|
+
const surfaceRef = useRef<HTMLDivElement>(null)
|
|
38
|
+
const sectionRefs = useRef<Map<string, HTMLElement>>(new Map())
|
|
39
|
+
const [scale, setScale] = useState(1)
|
|
40
|
+
const [surfaceHeight, setSurfaceHeight] = useState<number | null>(null)
|
|
41
|
+
|
|
42
|
+
const registerRef = useCallback((id: string, el: HTMLElement | null) => {
|
|
43
|
+
if (el) sectionRefs.current.set(id, el)
|
|
44
|
+
else sectionRefs.current.delete(id)
|
|
45
|
+
}, [])
|
|
46
|
+
|
|
47
|
+
useLayoutEffect(() => {
|
|
48
|
+
const container = containerRef.current
|
|
49
|
+
if (!container) return
|
|
50
|
+
const recompute = () => {
|
|
51
|
+
const available = container.clientWidth - 48
|
|
52
|
+
const next = Math.min(1, available / vp.width)
|
|
53
|
+
setScale(next > 0 ? next : 1)
|
|
54
|
+
if (surfaceRef.current) {
|
|
55
|
+
setSurfaceHeight(surfaceRef.current.offsetHeight * (next > 0 ? next : 1))
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
recompute()
|
|
59
|
+
const ro = new ResizeObserver(recompute)
|
|
60
|
+
ro.observe(container)
|
|
61
|
+
if (surfaceRef.current) ro.observe(surfaceRef.current)
|
|
62
|
+
return () => ro.disconnect()
|
|
63
|
+
}, [vp.width, sections, header, context])
|
|
64
|
+
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
if (!selectedId) return
|
|
67
|
+
const el = sectionRefs.current.get(selectedId)
|
|
68
|
+
if (el) el.scrollIntoView({ behavior: 'smooth', block: 'nearest' })
|
|
69
|
+
}, [selectedId])
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<div ref={containerRef} className="bg-muted h-full overflow-auto p-6">
|
|
73
|
+
<div
|
|
74
|
+
className="mx-auto"
|
|
75
|
+
style={{ width: vp.width * scale, height: surfaceHeight ?? undefined }}
|
|
76
|
+
>
|
|
77
|
+
<div
|
|
78
|
+
ref={surfaceRef}
|
|
79
|
+
className="border-border origin-top overflow-hidden rounded-xl border bg-white shadow-sm"
|
|
80
|
+
style={{ width: vp.width, transform: `scale(${scale})`, transformOrigin: 'top left' }}
|
|
81
|
+
>
|
|
82
|
+
<PostHeader config={header} context={context} />
|
|
83
|
+
{sections.length === 0 ? (
|
|
84
|
+
<EmptyBody onAddSection={onAddSection} />
|
|
85
|
+
) : (
|
|
86
|
+
sections.map((section) => (
|
|
87
|
+
<SectionRenderer
|
|
88
|
+
key={section.id}
|
|
89
|
+
section={section}
|
|
90
|
+
context={context}
|
|
91
|
+
editable
|
|
92
|
+
selected={section.id === selectedId}
|
|
93
|
+
onSelect={onSelect}
|
|
94
|
+
registerRef={registerRef}
|
|
95
|
+
/>
|
|
96
|
+
))
|
|
97
|
+
)}
|
|
98
|
+
</div>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function EmptyBody({ onAddSection }: { onAddSection: () => void }) {
|
|
105
|
+
return (
|
|
106
|
+
<div className="flex flex-col items-center justify-center gap-3 border-t border-zinc-100 px-6 py-16 text-center">
|
|
107
|
+
<span className="flex h-12 w-12 items-center justify-center rounded-full bg-zinc-100">
|
|
108
|
+
<FileText className="h-5 w-5 text-zinc-400" aria-hidden />
|
|
109
|
+
</span>
|
|
110
|
+
<p className="text-sm text-zinc-500">
|
|
111
|
+
The header is set by the template. Add body sections to build out this post.
|
|
112
|
+
</p>
|
|
113
|
+
<button
|
|
114
|
+
type="button"
|
|
115
|
+
onClick={onAddSection}
|
|
116
|
+
className="inline-flex items-center gap-2 rounded-lg bg-violet-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-violet-700"
|
|
117
|
+
>
|
|
118
|
+
<Plus className="h-4 w-4" aria-hidden />
|
|
119
|
+
Add Section
|
|
120
|
+
</button>
|
|
121
|
+
</div>
|
|
122
|
+
)
|
|
123
|
+
}
|