레이아웃 시스템: 섹션/컬럼 및 컬럼 간 DnD 구현
This commit is contained in:
@@ -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 컴포넌트에서 사용하는 전역 훅 스토어
|
||||
|
||||
Reference in New Issue
Block a user