텍스트 스타일 및 프리뷰 렌더링 정리
CI / test (push) Successful in 11m5s
CI / pr_and_merge (push) Successful in 1m40s
CI / test (pull_request) Failing after 35m42s
CI / pr_and_merge (pull_request) Has been skipped

This commit is contained in:
2025-11-20 23:36:47 +09:00
parent 9e40ee405c
commit 2f59e3781e
19 changed files with 3831 additions and 556 deletions
+119
View File
@@ -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>
);
}