feat: Section Card/Paper 레이아웃 컴포넌트 추가 및 설정 패널 통합
새로운 그룹화 레이아웃 컴포넌트 2종 추가: - Section Card: 제목+테두리 기반 명확한 섹션 구분 - Section Paper: 배경색 기반 미니멀한 섹션 구분 주요 변경사항: - 새 컴포넌트 등록 (각 4개 파일: Component, ConfigPanel, Renderer, index) - UnifiedPropertiesPanel에 인라인 설정 UI 추가 (280줄) - DetailSettingsPanel ConfigPanel 인터페이스 통일화 (config → componentConfig) - getComponentConfigPanel에 동적 import 매핑 추가 - 기존 컴포넌트 타입 정리 (autocomplete, entity-search, modal-repeater) 특징: - shadcn/ui 기반 일관된 디자인 시스템 준수 - 중첩 박스 금지 원칙 적용 - 반응형 지원 (모바일 우선) - collapsible 기능 지원 (Section Card)
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
|
||||
export interface SectionCardProps {
|
||||
component?: {
|
||||
id: string;
|
||||
componentConfig?: {
|
||||
title?: string;
|
||||
description?: string;
|
||||
showHeader?: boolean;
|
||||
headerPosition?: "top" | "left";
|
||||
padding?: "none" | "sm" | "md" | "lg";
|
||||
backgroundColor?: "default" | "muted" | "transparent";
|
||||
borderStyle?: "solid" | "dashed" | "none";
|
||||
collapsible?: boolean;
|
||||
defaultOpen?: boolean;
|
||||
};
|
||||
style?: React.CSSProperties;
|
||||
};
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
onClick?: (e?: React.MouseEvent) => void;
|
||||
isSelected?: boolean;
|
||||
isDesignMode?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Section Card 컴포넌트
|
||||
* 제목과 테두리가 있는 명확한 그룹화 컨테이너
|
||||
*/
|
||||
export function SectionCardComponent({
|
||||
component,
|
||||
children,
|
||||
className,
|
||||
onClick,
|
||||
isSelected = false,
|
||||
isDesignMode = false,
|
||||
}: SectionCardProps) {
|
||||
const config = component?.componentConfig || {};
|
||||
const [isOpen, setIsOpen] = React.useState(config.defaultOpen !== false);
|
||||
|
||||
// 🔄 실시간 업데이트를 위해 config에서 직접 읽기
|
||||
const title = config.title || "";
|
||||
const description = config.description || "";
|
||||
const showHeader = config.showHeader !== false; // 기본값: true
|
||||
const padding = config.padding || "md";
|
||||
const backgroundColor = config.backgroundColor || "default";
|
||||
const borderStyle = config.borderStyle || "solid";
|
||||
const collapsible = config.collapsible || false;
|
||||
|
||||
// 🎯 디버깅: config 값 확인
|
||||
React.useEffect(() => {
|
||||
console.log("✅ Section Card Config:", {
|
||||
title,
|
||||
description,
|
||||
showHeader,
|
||||
fullConfig: config,
|
||||
});
|
||||
}, [config.title, config.description, config.showHeader]);
|
||||
|
||||
// 패딩 매핑
|
||||
const paddingMap = {
|
||||
none: "p-0",
|
||||
sm: "p-3",
|
||||
md: "p-6",
|
||||
lg: "p-8",
|
||||
};
|
||||
|
||||
// 배경색 매핑
|
||||
const backgroundColorMap = {
|
||||
default: "bg-card",
|
||||
muted: "bg-muted/30",
|
||||
transparent: "bg-transparent",
|
||||
};
|
||||
|
||||
// 테두리 스타일 매핑
|
||||
const borderStyleMap = {
|
||||
solid: "border-solid",
|
||||
dashed: "border-dashed",
|
||||
none: "border-none",
|
||||
};
|
||||
|
||||
const handleToggle = () => {
|
||||
if (collapsible) {
|
||||
setIsOpen(!isOpen);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Card
|
||||
className={cn(
|
||||
"transition-all",
|
||||
backgroundColorMap[backgroundColor],
|
||||
borderStyleMap[borderStyle],
|
||||
borderStyle === "none" && "shadow-none",
|
||||
isDesignMode && isSelected && "ring-2 ring-primary ring-offset-2",
|
||||
isDesignMode && !children && "min-h-[150px]",
|
||||
className
|
||||
)}
|
||||
style={component?.style}
|
||||
onClick={onClick}
|
||||
>
|
||||
{/* 헤더 */}
|
||||
{showHeader && (title || description || isDesignMode) && (
|
||||
<CardHeader
|
||||
className={cn(
|
||||
"cursor-pointer",
|
||||
collapsible && "hover:bg-accent/50 transition-colors"
|
||||
)}
|
||||
onClick={handleToggle}
|
||||
>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex-1">
|
||||
{(title || isDesignMode) && (
|
||||
<CardTitle className="text-xl font-semibold">
|
||||
{title || (isDesignMode ? "섹션 제목" : "")}
|
||||
</CardTitle>
|
||||
)}
|
||||
{(description || isDesignMode) && (
|
||||
<CardDescription className="text-sm text-muted-foreground mt-1.5">
|
||||
{description || (isDesignMode ? "섹션 설명 (선택사항)" : "")}
|
||||
</CardDescription>
|
||||
)}
|
||||
</div>
|
||||
{collapsible && (
|
||||
<div className={cn(
|
||||
"ml-4 transition-transform",
|
||||
isOpen ? "rotate-180" : "rotate-0"
|
||||
)}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path d="m6 9 6 6 6-6" />
|
||||
</svg>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
)}
|
||||
|
||||
{/* 컨텐츠 */}
|
||||
{(!collapsible || isOpen) && (
|
||||
<CardContent className={cn(paddingMap[padding])}>
|
||||
{/* 디자인 모드에서 빈 상태 안내 */}
|
||||
{isDesignMode && !children && (
|
||||
<div className="flex items-center justify-center py-12 text-muted-foreground text-sm">
|
||||
<div className="text-center">
|
||||
<div className="mb-2">🃏 Section Card</div>
|
||||
<div className="text-xs">컴포넌트를 이곳에 배치하세요</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 자식 컴포넌트들 */}
|
||||
{children}
|
||||
</CardContent>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user