레이아웃 시스템: 섹션/컬럼 및 컬럼 간 DnD 구현

This commit is contained in:
2025-11-18 08:16:42 +09:00
parent d21252eb1f
commit dc55449efb
4 changed files with 399 additions and 25 deletions
+92
View File
@@ -30,6 +30,11 @@ export interface ImageBlockProps {
export interface SectionBlockProps {
background: "default" | "muted" | "primary";
paddingY: "sm" | "md" | "lg";
// 레이아웃 컬럼 정의 (12 그리드 기준 span)
columns: Array<{
id: string;
span: number;
}>;
}
// 공통 블록 모델
@@ -37,6 +42,9 @@ export interface Block {
id: string;
type: BlockType;
props: TextBlockProps | ButtonBlockProps | ImageBlockProps | SectionBlockProps;
// 레이아웃 트리 상 위치 정보 (루트 텍스트/버튼/이미지 블록은 null)
sectionId?: string | null;
columnId?: string | null;
}
// 에디터 상태 인터페이스
@@ -51,6 +59,7 @@ export interface EditorState {
selectBlock: (id: string | null) => void;
replaceBlocks: (blocks: Block[]) => void;
reorderBlocks: (activeId: string, overId: string) => void;
moveBlock: (id: string, sectionId: string | null, columnId: string | null) => void;
}
// 간단한 ID 생성기 (추후 uuid 라이브러리로 교체 가능)
@@ -66,6 +75,25 @@ const createEditorState = (set: any, get: any): EditorState => ({
// 텍스트 블록 추가: 기본 텍스트와 함께 생성 후 선택 상태로 만든다
addTextBlock: () => {
const id = createId();
const { selectedBlockId, blocks } = get();
let sectionId: string | null = null;
let columnId: string | null = null;
if (selectedBlockId) {
const target = blocks.find((b: Block) => b.id === selectedBlockId);
if (target) {
if (target.type === "section") {
const sProps = target.props as SectionBlockProps;
sectionId = target.id;
columnId = sProps.columns?.[0]?.id ?? null;
} else {
sectionId = (target as any).sectionId ?? null;
columnId = (target as any).columnId ?? null;
}
}
}
const newBlock: Block = {
id,
type: "text",
@@ -74,6 +102,8 @@ const createEditorState = (set: any, get: any): EditorState => ({
align: "left",
size: "base",
},
sectionId,
columnId,
};
set((state: EditorState) => ({
@@ -85,6 +115,25 @@ const createEditorState = (set: any, get: any): EditorState => ({
// 이미지 블록 추가: 기본 플레이스홀더와 함께 생성 후 선택 상태로 만든다
addImageBlock: () => {
const id = createId();
const { selectedBlockId, blocks } = get();
let sectionId: string | null = null;
let columnId: string | null = null;
if (selectedBlockId) {
const target = blocks.find((b: Block) => b.id === selectedBlockId);
if (target) {
if (target.type === "section") {
const sProps = target.props as SectionBlockProps;
sectionId = target.id;
columnId = sProps.columns?.[0]?.id ?? null;
} else {
sectionId = (target as any).sectionId ?? null;
columnId = (target as any).columnId ?? null;
}
}
}
const newBlock: Block = {
id,
type: "image",
@@ -92,6 +141,8 @@ const createEditorState = (set: any, get: any): EditorState => ({
src: "",
alt: "이미지 설명",
},
sectionId,
columnId,
};
set((state: EditorState) => ({
@@ -109,7 +160,15 @@ const createEditorState = (set: any, get: any): EditorState => ({
props: {
background: "default",
paddingY: "md",
columns: [
{
id: `${id}_col_1`,
span: 12,
},
],
},
sectionId: null,
columnId: null,
};
set((state: EditorState) => ({
@@ -121,6 +180,24 @@ const createEditorState = (set: any, get: any): EditorState => ({
// 버튼 블록 추가: 기본 라벨/링크와 함께 생성 후 선택 상태로 만든다
addButtonBlock: () => {
const id = createId();
const { selectedBlockId, blocks } = get();
let sectionId: string | null = null;
let columnId: string | null = null;
if (selectedBlockId) {
const target = blocks.find((b: Block) => b.id === selectedBlockId);
if (target) {
if (target.type === "section") {
const sProps = target.props as SectionBlockProps;
sectionId = target.id;
columnId = sProps.columns?.[0]?.id ?? null;
} else {
sectionId = (target as any).sectionId ?? null;
columnId = (target as any).columnId ?? null;
}
}
}
const newBlock: Block = {
id,
type: "button",
@@ -128,6 +205,8 @@ const createEditorState = (set: any, get: any): EditorState => ({
label: "버튼",
href: "#",
},
sectionId,
columnId,
};
set((state: EditorState) => ({
@@ -185,6 +264,19 @@ const createEditorState = (set: any, get: any): EditorState => ({
return { blocks: updated };
});
},
moveBlock: (id, sectionId, columnId) => {
set((state: EditorState) => ({
blocks: state.blocks.map((block: Block) =>
block.id === id
? {
...block,
sectionId,
columnId,
}
: block,
),
}));
},
});
// React 컴포넌트에서 사용하는 전역 훅 스토어