"use client"; import type { SectionBlockProps } from "@/features/editor/state/editorStore"; import { ColorPickerField, TEXT_COLOR_PALETTE } from "@/features/editor/components/ColorPickerField"; import { NumericPropertyControl } from "@/features/editor/components/NumericPropertyControl"; export type SectionPropertiesPanelProps = { sectionProps: SectionBlockProps; selectedBlockId: string; updateBlock: (id: string, partial: Partial) => void; }; export function SectionPropertiesPanel({ sectionProps, selectedBlockId, updateBlock }: SectionPropertiesPanelProps) { const backgroundSource: "none" | "url" | "upload" = (() => { const src = sectionProps.backgroundImageSrc?.trim(); const type = sectionProps.backgroundImageSourceType; if (type === "asset") return "upload"; if (type === "externalUrl") return "url"; if (src && src.startsWith("/api/image/")) return "upload"; if (src) return "url"; return "none"; })(); const positionMode: "preset" | "custom" = sectionProps.backgroundImagePositionMode ?? "preset"; const backgroundVideoSource: "none" | "url" | "upload" = (() => { const src = sectionProps.backgroundVideoSrc?.trim(); const type = sectionProps.backgroundVideoSourceType; if (type === "asset") return "upload"; if (type === "externalUrl") return "url"; if (src && src.startsWith("/api/video/")) return "upload"; if (src) return "url"; return "none"; })(); return ( <> {/* 섹션 배경: 커스텀 색상 피커만 사용 */}
{ const next = hex && hex.trim().length > 0 ? hex : undefined; updateBlock(selectedBlockId, { backgroundColorCustom: next } as any); }} palette={TEXT_COLOR_PALETTE} />
{/* 섹션 배경 이미지 */}
{backgroundSource === "url" && ( )} {backgroundSource === "upload" && ( )} {backgroundSource !== "none" && ( <> {positionMode === "preset" && ( )} {positionMode === "custom" && ( <> updateBlock(selectedBlockId, { backgroundImagePositionMode: "custom", backgroundImagePositionXPercent: next, } as any) } /> updateBlock(selectedBlockId, { backgroundImagePositionMode: "custom", backgroundImagePositionYPercent: next, } as any) } /> )} )}
{/* 섹션 배경 비디오 */}
{backgroundVideoSource === "url" && ( )} {backgroundVideoSource === "upload" && ( )}
{/* 세로 패딩 슬라이더 (px) - 프리셋 + 자유 슬라이더 */}
{ const safe = Number.isFinite(next) ? next : 48; updateBlock(selectedBlockId, { paddingYPx: safe } as any); }} />

섹션 레이아웃

{/* 컬럼 레이아웃 프리셋 */} {/* 컬럼 개수 직접 조정 (1~5열) */} {(() => { const current = sectionProps.columns ?? []; const count = current.length || 1; const handleChangeCount = (nextCount: number) => { const clamped = Math.max(1, Math.min(5, Math.floor(nextCount))); if (clamped === count) return; const base = Math.floor(12 / clamped) || 1; const rest = 12 - base * clamped; const nextColumns = Array.from({ length: clamped }).map((_, index) => { const existing = current[index]; const id = existing?.id ?? `${selectedBlockId}_col_${index + 1}`; const span = base + (index === clamped - 1 ? rest : 0); return { id, span }; }); updateBlock(selectedBlockId, { columns: nextColumns } as any); }; return (
{`컬럼 개수: ${count}열 (최대 5열)`}
); })()} {/* 컬럼별 span 조정 - 컬럼이 1개일 때: 12 고정, 편집 불가능 - 컬럼이 2개 이상일 때: 앞의 N-1개 컬럼만 직접 조정, 마지막 컬럼은 합이 항상 12가 되도록 자동 계산 */} {(() => { const columns = sectionProps.columns ?? []; const colCount = columns.length || 1; // 1컬럼인 경우: 12 고정 표시 if (colCount === 1) { const only = columns[0] ?? { id: `${selectedBlockId}_col_1`, span: 12 }; return ( ); } const lastIndex = colCount - 1; return columns.map((col, index) => { const maxSpanForEach = Math.max(1, 12 - (colCount - 1)); // 마지막 컬럼: 자동 계산, 편집 불가 if (index === lastIndex) { const nonLast = columns.slice(0, lastIndex); const nonLastSum = nonLast.reduce( (sum, c) => sum + (Number.isFinite(c.span) ? c.span : 0), 0, ); let autoSpan = 12 - nonLastSum; if (!Number.isFinite(autoSpan) || autoSpan < 1) autoSpan = 1; return ( ); } // 앞의 N-1개 컬럼: 직접 조정하면 마지막 컬럼을 자동으로 보정해서 합계 12 유지 const otherNonLastSum = columns.reduce((sum, c, idx) => { if (idx === index || idx === lastIndex) return sum; return sum + (Number.isFinite(c.span) ? c.span : 0); }, 0); const maxForCurrent = Math.max( 1, Math.min(maxSpanForEach, 11 - otherNonLastSum), ); const handleChange = (raw: number) => { if (Number.isNaN(raw)) return; const clampedCurrent = Math.min( maxForCurrent, Math.max(1, Math.floor(raw)), ); const newNonLastTotal = otherNonLastSum + clampedCurrent; let lastSpan = 12 - newNonLastTotal; if (!Number.isFinite(lastSpan) || lastSpan < 1) { lastSpan = 1; } const nextColumns = columns.map((c, idx) => { if (idx === index) { return { ...c, span: clampedCurrent }; } if (idx === lastIndex) { return { ...c, span: lastSpan }; } return c; }); updateBlock(selectedBlockId, { columns: nextColumns } as any); }; return ( ); }); })()} {/* 최대 폭 슬라이더 (px) - 프리셋 + 자유 슬라이더 */} { const safe = Number.isFinite(next) ? next : 960; updateBlock(selectedBlockId, { maxWidthPx: safe } as any); }} /> {/* 컬럼 간 간격 슬라이더 (px) - 프리셋 + 자유 슬라이더 */} { const safe = Number.isFinite(next) ? next : 24; updateBlock(selectedBlockId, { gapXPx: safe } as any); }} /> {/* 세로 정렬 */}
); }