230 lines
8.2 KiB
TypeScript
230 lines
8.2 KiB
TypeScript
"use client";
|
|
|
|
import type { Block, ButtonBlockProps, FormBlockProps, ListBlockProps, SectionBlockProps, TextBlockProps, VideoBlockProps } 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 { VideoPropertiesPanel } from "./VideoPropertiesPanel";
|
|
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";
|
|
import { ProjectPropertiesPanel } from "./ProjectPropertiesPanel";
|
|
|
|
interface PropertiesSidebarProps {
|
|
blocks: Block[];
|
|
selectedBlockId: string | null;
|
|
selectedListItemId: string | null;
|
|
updateBlock: (id: string, partial: any) => void;
|
|
removeBlock: (id: string) => void;
|
|
duplicateBlock: (id: string) => void;
|
|
// 텍스트 속성 패널에서 사용하는 인라인 편집 상태를 그대로 전달한다.
|
|
editingBlockId: string | null;
|
|
setEditingText: (value: string) => void;
|
|
onMoveSelectedItemUp: (blockId: string) => void;
|
|
onMoveSelectedItemDown: (blockId: string) => void;
|
|
onIndentSelectedItem: (blockId: string) => void;
|
|
onOutdentSelectedItem: (blockId: string) => void;
|
|
}
|
|
|
|
// 우측 속성 패널을 분리한 컴포넌트
|
|
export function PropertiesSidebar(props: PropertiesSidebarProps) {
|
|
const {
|
|
blocks,
|
|
selectedBlockId,
|
|
selectedListItemId,
|
|
updateBlock,
|
|
removeBlock,
|
|
duplicateBlock,
|
|
editingBlockId,
|
|
setEditingText,
|
|
onMoveSelectedItemUp,
|
|
onMoveSelectedItemDown,
|
|
onIndentSelectedItem,
|
|
onOutdentSelectedItem,
|
|
} = props;
|
|
|
|
return (
|
|
<aside
|
|
data-testid="properties-sidebar"
|
|
className="w-80 p-4 text-sm border-l border-slate-800 flex flex-col gap-4 overflow-auto pb-scroll"
|
|
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}
|
|
selectedListItemId={selectedListItemId}
|
|
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 === "video") {
|
|
const videoProps = selectedBlock.props as VideoBlockProps;
|
|
|
|
return (
|
|
<VideoPropertiesPanel
|
|
videoProps={videoProps}
|
|
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>
|
|
) : (
|
|
<ProjectPropertiesPanel />
|
|
)}
|
|
</aside>
|
|
);
|
|
}
|