디자이너 캔버스 UX 개선: 헤더 제거 + 실제 데이터 렌더링 + 컴포넌트 목록
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
bd7bf69a99
commit
6f45efef03
|
|
@ -652,6 +652,9 @@ export default function PopDesigner({
|
||||||
? (updates) => handleUpdateComponent(selectedComponentId, updates)
|
? (updates) => handleUpdateComponent(selectedComponentId, updates)
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
|
allComponents={Object.values(layout.components)}
|
||||||
|
onSelectComponent={setSelectedComponentId}
|
||||||
|
selectedComponentId={selectedComponentId}
|
||||||
/>
|
/>
|
||||||
</ResizablePanel>
|
</ResizablePanel>
|
||||||
</ResizablePanelGroup>
|
</ResizablePanelGroup>
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import {
|
||||||
PopGridPosition,
|
PopGridPosition,
|
||||||
GridMode,
|
GridMode,
|
||||||
GRID_BREAKPOINTS,
|
GRID_BREAKPOINTS,
|
||||||
PopComponentType,
|
|
||||||
} from "../types/pop-layout";
|
} from "../types/pop-layout";
|
||||||
import {
|
import {
|
||||||
Settings,
|
Settings,
|
||||||
|
|
@ -16,6 +15,7 @@ import {
|
||||||
Grid3x3,
|
Grid3x3,
|
||||||
MoveHorizontal,
|
MoveHorizontal,
|
||||||
MoveVertical,
|
MoveVertical,
|
||||||
|
Layers,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
|
|
@ -36,12 +36,21 @@ interface ComponentEditorPanelProps {
|
||||||
onUpdateComponent?: (updates: Partial<PopComponentDefinitionV5>) => void;
|
onUpdateComponent?: (updates: Partial<PopComponentDefinitionV5>) => void;
|
||||||
/** 추가 className */
|
/** 추가 className */
|
||||||
className?: string;
|
className?: string;
|
||||||
|
/** 그리드에 배치된 모든 컴포넌트 */
|
||||||
|
allComponents?: PopComponentDefinitionV5[];
|
||||||
|
/** 컴포넌트 선택 콜백 */
|
||||||
|
onSelectComponent?: (componentId: string) => void;
|
||||||
|
/** 현재 선택된 컴포넌트 ID */
|
||||||
|
selectedComponentId?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// 컴포넌트 타입별 라벨
|
// 컴포넌트 타입별 라벨
|
||||||
// ========================================
|
// ========================================
|
||||||
const COMPONENT_TYPE_LABELS: Record<PopComponentType, string> = {
|
const COMPONENT_TYPE_LABELS: Record<string, string> = {
|
||||||
|
"pop-sample": "샘플",
|
||||||
|
"pop-text": "텍스트",
|
||||||
|
"pop-dashboard": "대시보드",
|
||||||
"pop-field": "필드",
|
"pop-field": "필드",
|
||||||
"pop-button": "버튼",
|
"pop-button": "버튼",
|
||||||
"pop-list": "리스트",
|
"pop-list": "리스트",
|
||||||
|
|
@ -61,6 +70,9 @@ export default function ComponentEditorPanel({
|
||||||
currentMode,
|
currentMode,
|
||||||
onUpdateComponent,
|
onUpdateComponent,
|
||||||
className,
|
className,
|
||||||
|
allComponents,
|
||||||
|
onSelectComponent,
|
||||||
|
selectedComponentId,
|
||||||
}: ComponentEditorPanelProps) {
|
}: ComponentEditorPanelProps) {
|
||||||
const breakpoint = GRID_BREAKPOINTS[currentMode];
|
const breakpoint = GRID_BREAKPOINTS[currentMode];
|
||||||
|
|
||||||
|
|
@ -119,6 +131,43 @@ export default function ComponentEditorPanel({
|
||||||
|
|
||||||
{/* 위치 탭 */}
|
{/* 위치 탭 */}
|
||||||
<TabsContent value="position" className="min-h-0 flex-1 overflow-auto p-4">
|
<TabsContent value="position" className="min-h-0 flex-1 overflow-auto p-4">
|
||||||
|
{/* 배치된 컴포넌트 목록 */}
|
||||||
|
{allComponents && allComponents.length > 0 && (
|
||||||
|
<div className="mb-4">
|
||||||
|
<div className="flex items-center gap-1 mb-2">
|
||||||
|
<Layers className="h-3 w-3 text-muted-foreground" />
|
||||||
|
<span className="text-xs font-medium text-muted-foreground">
|
||||||
|
배치된 컴포넌트 ({allComponents.length})
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-1">
|
||||||
|
{allComponents.map((comp) => {
|
||||||
|
const label = comp.label
|
||||||
|
|| COMPONENT_TYPE_LABELS[comp.type]
|
||||||
|
|| comp.type;
|
||||||
|
const isActive = comp.id === selectedComponentId;
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
key={comp.id}
|
||||||
|
onClick={() => onSelectComponent?.(comp.id)}
|
||||||
|
className={cn(
|
||||||
|
"flex w-full items-center gap-2 rounded px-2 py-1.5 text-left text-xs transition-colors",
|
||||||
|
isActive
|
||||||
|
? "bg-primary/10 text-primary font-medium"
|
||||||
|
: "hover:bg-gray-100 text-gray-600"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span className="truncate flex-1">{label}</span>
|
||||||
|
<span className="shrink-0 text-[10px] text-gray-400">
|
||||||
|
({comp.position.col},{comp.position.row})
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<div className="h-px bg-gray-200 mt-3" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<PositionForm
|
<PositionForm
|
||||||
component={component}
|
component={component}
|
||||||
currentMode={currentMode}
|
currentMode={currentMode}
|
||||||
|
|
|
||||||
|
|
@ -507,27 +507,22 @@ function ComponentContent({ component, effectivePosition, isDesignMode, isSelect
|
||||||
const registeredComp = PopComponentRegistry.getComponent(component.type);
|
const registeredComp = PopComponentRegistry.getComponent(component.type);
|
||||||
const PreviewComponent = registeredComp?.preview;
|
const PreviewComponent = registeredComp?.preview;
|
||||||
|
|
||||||
// 디자인 모드: 미리보기 컴포넌트 또는 플레이스홀더 표시
|
// 디자인 모드: 실제 컴포넌트 또는 미리보기 표시 (헤더 없음 - 뷰어와 동일하게)
|
||||||
if (isDesignMode) {
|
if (isDesignMode) {
|
||||||
return (
|
const ActualComp = registeredComp?.component;
|
||||||
<div className="flex h-full w-full flex-col">
|
|
||||||
{/* 헤더 */}
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
"flex h-5 shrink-0 items-center border-b px-2",
|
|
||||||
isSelected ? "bg-primary/10 border-primary" : "bg-gray-50 border-gray-200"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<span className={cn(
|
|
||||||
"text-[10px] font-medium truncate",
|
|
||||||
isSelected ? "text-primary" : "text-gray-600"
|
|
||||||
)}>
|
|
||||||
{component.label || typeLabel}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* 내용: 등록된 preview 컴포넌트 또는 기본 플레이스홀더 */}
|
// 실제 컴포넌트가 등록되어 있으면 실제 데이터로 렌더링 (대시보드 등)
|
||||||
<div className="flex flex-1 items-center justify-center overflow-hidden">
|
if (ActualComp) {
|
||||||
|
return (
|
||||||
|
<div className="h-full w-full overflow-hidden pointer-events-none">
|
||||||
|
<ActualComp config={component.config} label={component.label} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 미등록: preview 컴포넌트 또는 기본 플레이스홀더
|
||||||
|
return (
|
||||||
|
<div className="flex h-full w-full items-center justify-center overflow-hidden">
|
||||||
{PreviewComponent ? (
|
{PreviewComponent ? (
|
||||||
<PreviewComponent config={component.config} />
|
<PreviewComponent config={component.config} />
|
||||||
) : (
|
) : (
|
||||||
|
|
@ -536,13 +531,6 @@ function ComponentContent({ component, effectivePosition, isDesignMode, isSelect
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 위치 정보 표시 (유효 위치 사용) */}
|
|
||||||
<div className="absolute bottom-1 right-1 text-[9px] text-gray-400 bg-white/80 px-1 rounded">
|
|
||||||
{effectivePosition.col},{effectivePosition.row}
|
|
||||||
({effectivePosition.colSpan}×{effectivePosition.rowSpan})
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue