텍스트 스타일 및 프리뷰 렌더링 정리
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
+203
View File
@@ -0,0 +1,203 @@
"use client";
import type { Block, ButtonBlockProps, FormBlockProps, ListBlockProps, SectionBlockProps, TextBlockProps } from "@/features/editor/state/editorStore";
import { ButtonPropertiesPanel } from "./ButtonPropertiesPanel";
import { TextPropertiesPanel } from "./TextPropertiesPanel";
import { ListPropertiesPanel } from "./ListPropertiesPanel";
import { DividerPropertiesPanel } from "./DividerPropertiesPanel";
import { ImagePropertiesPanel } from "./ImagePropertiesPanel";
import { SectionPropertiesPanel } from "./SectionPropertiesPanel";
import { FormInputPropertiesPanel } from "../forms/FormInputPropertiesPanel";
import { FormSelectPropertiesPanel } from "../forms/FormSelectPropertiesPanel";
import { FormCheckboxPropertiesPanel } from "../forms/FormCheckboxPropertiesPanel";
import { FormRadioPropertiesPanel } from "../forms/FormRadioPropertiesPanel";
import { FormControllerPanel } from "../forms/FormControllerPanel";
interface PropertiesSidebarProps {
blocks: Block[];
selectedBlockId: string | null;
updateBlock: (id: string, partial: any) => void;
removeBlock: (id: string) => void;
duplicateBlock: (id: string) => void;
// 텍스트 속성 패널에서 사용하는 인라인 편집 상태를 그대로 전달한다.
editingBlockId: string | null;
setEditingText: (value: string) => void;
}
// 우측 속성 패널을 분리한 컴포넌트
export function PropertiesSidebar(props: PropertiesSidebarProps) {
const {
blocks,
selectedBlockId,
updateBlock,
removeBlock,
duplicateBlock,
editingBlockId,
setEditingText,
} = props;
return (
<aside
className="w-80 p-4 text-sm border-l border-slate-800 flex flex-col gap-4 overflow-auto"
onKeyDownCapture={(e) => {
// 속성 패널 안에서 발생한 키 입력은 에디터 단축키/텍스트 블록 편집으로 전달되지 않도록 막는다.
e.stopPropagation();
}}
>
<h2 className="font-medium mb-2"> </h2>
{selectedBlockId ? (
<div className="space-y-3">
<div className="flex gap-2 text-[11px]">
<button
type="button"
className="flex-1 rounded border border-slate-700 bg-slate-900 px-2 py-1 text-slate-100 hover:bg-red-900/60 hover:border-red-700"
onClick={() => removeBlock(selectedBlockId)}
>
</button>
<button
type="button"
className="flex-1 rounded border border-slate-700 bg-slate-900 px-2 py-1 text-slate-100 hover:bg-slate-800"
onClick={() => duplicateBlock(selectedBlockId)}
>
</button>
</div>
{(() => {
const selectedBlock = blocks.find((b) => b.id === selectedBlockId);
if (!selectedBlock) return null;
if (selectedBlock.type === "text") {
const textProps = selectedBlock.props as TextBlockProps;
return (
<TextPropertiesPanel
textProps={textProps}
selectedBlockId={selectedBlockId}
updateBlock={(id, partial) => updateBlock(id, partial as any)}
editingBlockId={editingBlockId}
setEditingText={setEditingText}
/>
);
}
if (selectedBlock.type === "button") {
const buttonProps = selectedBlock.props as ButtonBlockProps;
return (
<ButtonPropertiesPanel
buttonProps={buttonProps}
selectedBlockId={selectedBlockId}
updateBlock={(id, partial) => updateBlock(id, partial as any)}
/>
);
}
if (selectedBlock.type === "list") {
const listProps = selectedBlock.props as ListBlockProps;
return (
<ListPropertiesPanel
listProps={listProps}
selectedBlockId={selectedBlockId}
updateBlock={(id, partial) => updateBlock(id, partial as any)}
/>
);
}
if (selectedBlock.type === "divider") {
const dividerProps = selectedBlock.props as any;
return (
<DividerPropertiesPanel
dividerProps={dividerProps}
selectedBlockId={selectedBlockId}
updateBlock={(id, partial) => updateBlock(id, partial as any)}
/>
);
}
if (selectedBlock.type === "image") {
const imageProps = selectedBlock.props as any;
return (
<ImagePropertiesPanel
imageProps={imageProps}
selectedBlockId={selectedBlockId}
updateBlock={(id, partial) => updateBlock(id, partial as any)}
/>
);
}
if (selectedBlock.type === "section") {
const sectionProps = selectedBlock.props as SectionBlockProps;
return (
<SectionPropertiesPanel
sectionProps={sectionProps}
selectedBlockId={selectedBlockId}
updateBlock={(id, partial) => updateBlock(id, partial as any)}
/>
);
}
if (selectedBlock.type === "formInput") {
return (
<FormInputPropertiesPanel
block={selectedBlock}
selectedBlockId={selectedBlockId}
updateBlock={(id, partial) => updateBlock(id, partial as any)}
/>
);
}
if (selectedBlock.type === "formSelect") {
return (
<FormSelectPropertiesPanel
block={selectedBlock}
selectedBlockId={selectedBlockId}
updateBlock={(id, partial) => updateBlock(id, partial as any)}
/>
);
}
if (selectedBlock.type === "formCheckbox") {
return (
<FormCheckboxPropertiesPanel
block={selectedBlock}
selectedBlockId={selectedBlockId}
updateBlock={(id, partial) => updateBlock(id, partial as any)}
/>
);
}
if (selectedBlock.type === "formRadio") {
return (
<FormRadioPropertiesPanel
block={selectedBlock}
selectedBlockId={selectedBlockId}
updateBlock={(id, partial) => updateBlock(id, partial as any)}
/>
);
}
if (selectedBlock.type === "form") {
return (
<FormControllerPanel
block={selectedBlock}
blocks={blocks}
selectedBlockId={selectedBlockId}
updateBlock={(id, partial) => updateBlock(id, partial as any)}
/>
);
}
return null;
})()}
</div>
) : (
<p className="text-xs text-slate-500"> .</p>
)}
</aside>
);
}