텍스트 스타일 및 프리뷰 렌더링 정리
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
"use client";
|
||||
|
||||
import { ReactNode } from "react";
|
||||
import {
|
||||
DndContext,
|
||||
DragOverlay,
|
||||
type DragEndEvent,
|
||||
type DragStartEvent,
|
||||
} from "@dnd-kit/core";
|
||||
import { SortableContext, verticalListSortingStrategy } from "@dnd-kit/sortable";
|
||||
import { rectIntersection } from "@dnd-kit/core";
|
||||
|
||||
import type { Block, ButtonBlockProps, ImageBlockProps, TextBlockProps } from "@/features/editor/state/editorStore";
|
||||
|
||||
interface EditorCanvasProps {
|
||||
blocks: Block[];
|
||||
rootBlocks: Block[];
|
||||
selectedBlockId: string | null;
|
||||
editingBlockId: string | null;
|
||||
editingText: string;
|
||||
activeDragId: string | null;
|
||||
sensors: any;
|
||||
onCanvasEmptyClick: () => void;
|
||||
renderBlocks: (targetBlocks: Block[]) => ReactNode;
|
||||
handleDragStart: (event: DragStartEvent) => void;
|
||||
handleDragEnd: (event: DragEndEvent) => void;
|
||||
handleDragCancel: () => void;
|
||||
}
|
||||
|
||||
export function EditorCanvas(props: EditorCanvasProps) {
|
||||
const {
|
||||
blocks,
|
||||
rootBlocks,
|
||||
activeDragId,
|
||||
sensors,
|
||||
onCanvasEmptyClick,
|
||||
renderBlocks,
|
||||
handleDragStart,
|
||||
handleDragEnd,
|
||||
handleDragCancel,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex-1 p-4 flex flex-col gap-2 text-sm text-slate-200 border-r border-slate-800 overflow-auto"
|
||||
data-testid="editor-canvas"
|
||||
onClick={(event) => {
|
||||
if (event.target === event.currentTarget) {
|
||||
onCanvasEmptyClick();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{blocks.length === 0 ? (
|
||||
<div className="flex-1 flex items-center justify-center text-slate-500 text-xs">
|
||||
왼쪽에서 "텍스트 블록 추가" 버튼을 눌러 블록을 추가해 보세요.
|
||||
</div>
|
||||
) : (
|
||||
<DndContext
|
||||
sensors={sensors}
|
||||
collisionDetection={rectIntersection}
|
||||
onDragStart={handleDragStart}
|
||||
onDragEnd={handleDragEnd}
|
||||
onDragCancel={handleDragCancel}
|
||||
>
|
||||
<SortableContext
|
||||
items={rootBlocks.map((b) => b.id)}
|
||||
strategy={verticalListSortingStrategy}
|
||||
>
|
||||
{renderBlocks(rootBlocks)}
|
||||
</SortableContext>
|
||||
<DragOverlay>
|
||||
{activeDragId && (
|
||||
<DragPreview block={blocks.find((b) => b.id === activeDragId) ?? null} />
|
||||
)}
|
||||
</DragOverlay>
|
||||
</DndContext>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface DragPreviewProps {
|
||||
block: Block | null;
|
||||
}
|
||||
|
||||
function DragPreview({ block }: DragPreviewProps) {
|
||||
if (!block) return null;
|
||||
|
||||
return (
|
||||
<div className="pointer-events-none rounded border border-sky-500 bg-slate-900/80 px-3 py-2 text-xs text-slate-100 shadow-lg opacity-90 min-w-[160px] max-w-[260px]">
|
||||
<div className="text-[10px] uppercase tracking-wide text-sky-300 mb-1">
|
||||
{block.type === "text"
|
||||
? "Text"
|
||||
: block.type === "button"
|
||||
? "Button"
|
||||
: block.type === "image"
|
||||
? "Image"
|
||||
: block.type === "divider"
|
||||
? "Divider"
|
||||
: block.type === "list"
|
||||
? "List"
|
||||
: "Section"}
|
||||
</div>
|
||||
<div className="truncate">
|
||||
{block.type === "text"
|
||||
? (block.props as TextBlockProps).text || "텍스트 블록"
|
||||
: block.type === "button"
|
||||
? (block.props as ButtonBlockProps).label || "버튼 블록"
|
||||
: block.type === "image"
|
||||
? (block.props as ImageBlockProps).alt || "이미지 블록"
|
||||
: block.type === "list"
|
||||
? "리스트 블록"
|
||||
: block.type === "divider"
|
||||
? "구분선 블록"
|
||||
: "섹션 블록"}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user