프리뷰 모드: 에디터 크롬 없이 페이지 미리보기 구현
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
"use client";
|
||||
|
||||
import type { Block, TextBlockProps, ButtonBlockProps, ImageBlockProps, SectionBlockProps } from "@/features/editor/state/editorStore";
|
||||
|
||||
// 에디터 크롬 없이 실제 랜딩 페이지처럼 블록들을 렌더링하는 컴포넌트
|
||||
interface PublicPageRendererProps {
|
||||
blocks: Block[];
|
||||
}
|
||||
|
||||
export function PublicPageRenderer({ blocks }: PublicPageRendererProps) {
|
||||
const sectionBlocks = blocks.filter((b) => b.type === "section");
|
||||
const rootBlocks = blocks.filter((b) => !b.sectionId && b.type !== "section");
|
||||
|
||||
const renderBlock = (block: Block) => {
|
||||
if (block.type === "text") {
|
||||
const props = block.props as TextBlockProps;
|
||||
|
||||
const alignClass =
|
||||
props.align === "center" ? "text-center" : props.align === "right" ? "text-right" : "text-left";
|
||||
const sizeClass = props.size === "sm" ? "text-sm" : props.size === "lg" ? "text-2xl" : "text-base";
|
||||
|
||||
return (
|
||||
<p key={block.id} className={`${alignClass} ${sizeClass} text-slate-50 leading-relaxed`}>
|
||||
{props.text}
|
||||
</p>
|
||||
);
|
||||
}
|
||||
|
||||
if (block.type === "button") {
|
||||
const props = block.props as ButtonBlockProps;
|
||||
|
||||
return (
|
||||
<a
|
||||
key={block.id}
|
||||
href={props.href}
|
||||
className="inline-flex items-center justify-center rounded-md bg-sky-600 px-4 py-2 text-sm font-medium text-white hover:bg-sky-500 transition-colors"
|
||||
>
|
||||
{props.label}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
if (block.type === "image") {
|
||||
const props = block.props as ImageBlockProps;
|
||||
|
||||
return (
|
||||
<div key={block.id} className="w-full flex justify-center">
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img src={props.src || ""} alt={props.alt} className="max-w-full h-auto object-contain" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const renderSection = (section: Block) => {
|
||||
const props = section.props as SectionBlockProps;
|
||||
|
||||
const bgClass =
|
||||
props.background === "muted" ? "bg-slate-900" : props.background === "primary" ? "bg-sky-900" : "bg-slate-950";
|
||||
|
||||
const pyClass = props.paddingY === "sm" ? "py-8" : props.paddingY === "lg" ? "py-20" : "py-12";
|
||||
|
||||
const columns = props.columns && props.columns.length > 0 ? props.columns : [{ id: `${section.id}_col`, span: 12 }];
|
||||
|
||||
return (
|
||||
<section key={section.id} className={`${bgClass} ${pyClass}`}>
|
||||
<div className="mx-auto max-w-5xl px-4">
|
||||
<div className="flex gap-8">
|
||||
{columns.map((col) => {
|
||||
const basis = `${(col.span / 12) * 100}%`;
|
||||
const columnBlocks = blocks.filter((b) => b.sectionId === section.id && b.columnId === col.id);
|
||||
|
||||
return (
|
||||
<div key={col.id} className="flex flex-col gap-4" style={{ flexBasis: basis }}>
|
||||
{columnBlocks.map((b) => (
|
||||
<div key={b.id}>{renderBlock(b)}</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex-1 flex flex-col bg-slate-950 text-slate-50">
|
||||
{/* 루트 텍스트/버튼/이미지 블록들이 있다면 페이지 상단에 노출 */}
|
||||
{rootBlocks.length > 0 && (
|
||||
<section className="bg-slate-950 py-12">
|
||||
<div className="mx-auto max-w-3xl px-4 flex flex-col gap-4">
|
||||
{rootBlocks.map((b) => (
|
||||
<div key={b.id}>{renderBlock(b)}</div>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* 섹션 블록들 */}
|
||||
{sectionBlocks.map((section) => renderSection(section))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user