From 87b8aec759f69da4380c865e07521084d2337905 Mon Sep 17 00:00:00 2001 From: shin Date: Tue, 10 Mar 2026 18:36:27 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20tabs-component=20=EC=B6=A9=EB=8F=8C=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0=20-=20origin/main=20=EB=B2=84=EC=A0=84?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B3=B5=EC=9B=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 배지 기능(HEAD)을 제거하고 origin/main 버전을 유지함. 충돌 시 작성하지 않은 파일은 main 버전을 그대로 사용. Co-Authored-By: Claude Sonnet 4.6 --- .../v2-tabs-widget/tabs-component.tsx | 197 +++++++++++------- 1 file changed, 116 insertions(+), 81 deletions(-) diff --git a/frontend/lib/registry/components/v2-tabs-widget/tabs-component.tsx b/frontend/lib/registry/components/v2-tabs-widget/tabs-component.tsx index 93dd638f..d036c068 100644 --- a/frontend/lib/registry/components/v2-tabs-widget/tabs-component.tsx +++ b/frontend/lib/registry/components/v2-tabs-widget/tabs-component.tsx @@ -21,19 +21,21 @@ const TabsDesignEditor: React.FC<{ const [dragPosition, setDragPosition] = useState<{ x: number; y: number } | null>(null); const containerRef = useRef(null); const rafRef = useRef(null); - + // 리사이즈 상태 const [resizingCompId, setResizingCompId] = useState(null); const [resizeSize, setResizeSize] = useState<{ width: number; height: number } | null>(null); const [lastResizedCompId, setLastResizedCompId] = useState(null); const activeTab = tabs.find((t) => t.id === activeTabId); - + // 🆕 탭 컴포넌트 size가 업데이트되면 resizeSize 초기화 useEffect(() => { if (resizeSize && lastResizedCompId && !resizingCompId) { - const targetComp = activeTab?.components?.find((c) => c.id === lastResizedCompId); - if (targetComp && targetComp.size?.width === resizeSize.width && targetComp.size?.height === resizeSize.height) { + const targetComp = activeTab?.components?.find(c => c.id === lastResizedCompId); + if (targetComp && + targetComp.size?.width === resizeSize.width && + targetComp.size?.height === resizeSize.height) { setResizeSize(null); setLastResizedCompId(null); } @@ -46,7 +48,7 @@ const TabsDesignEditor: React.FC<{ "px-4 py-2 text-sm font-medium cursor-pointer transition-colors", isActive ? "bg-background border-b-2 border-primary text-primary" - : "text-muted-foreground hover:text-foreground hover:bg-muted/50", + : "text-muted-foreground hover:text-foreground hover:bg-muted/50" ); }; @@ -54,7 +56,7 @@ const TabsDesignEditor: React.FC<{ const handleDeleteComponent = useCallback( (compId: string) => { if (!onUpdateComponent) return; - + const updatedTabs = tabs.map((tab) => { if (tab.id === activeTabId) { return { @@ -73,7 +75,7 @@ const TabsDesignEditor: React.FC<{ }, }); }, - [activeTabId, component, onUpdateComponent, tabs], + [activeTabId, component, onUpdateComponent, tabs] ); // 10px 단위 스냅 함수 @@ -84,13 +86,13 @@ const TabsDesignEditor: React.FC<{ (e: React.MouseEvent, comp: TabInlineComponent) => { e.stopPropagation(); e.preventDefault(); - + // 드래그 시작 시 마우스 위치와 컴포넌트의 현재 위치 저장 const startMouseX = e.clientX; const startMouseY = e.clientY; const startLeft = comp.position?.x || 0; const startTop = comp.position?.y || 0; - + setDraggingCompId(comp.id); setDragPosition({ x: startLeft, y: startTop }); @@ -99,12 +101,12 @@ const TabsDesignEditor: React.FC<{ if (rafRef.current) { cancelAnimationFrame(rafRef.current); } - + rafRef.current = requestAnimationFrame(() => { // 마우스 이동량 계산 const deltaX = moveEvent.clientX - startMouseX; const deltaY = moveEvent.clientY - startMouseY; - + // 새 위치 = 시작 위치 + 이동량 (10px 단위 스냅 적용) const newX = snapTo10(Math.max(0, startLeft + deltaX)); const newY = snapTo10(Math.max(0, startTop + deltaY)); @@ -117,20 +119,20 @@ const TabsDesignEditor: React.FC<{ const handleMouseUp = (upEvent: MouseEvent) => { document.removeEventListener("mousemove", handleMouseMove); document.removeEventListener("mouseup", handleMouseUp); - + if (rafRef.current) { cancelAnimationFrame(rafRef.current); rafRef.current = null; } - + // 마우스 이동량 계산 const deltaX = upEvent.clientX - startMouseX; const deltaY = upEvent.clientY - startMouseY; - + // 새 위치 = 시작 위치 + 이동량 (10px 단위 스냅 적용) const newX = snapTo10(Math.max(0, startLeft + deltaX)); const newY = snapTo10(Math.max(0, startTop + deltaY)); - + setDraggingCompId(null); setDragPosition(null); @@ -149,7 +151,7 @@ const TabsDesignEditor: React.FC<{ y: newY, }, } - : c, + : c ), }; } @@ -173,7 +175,7 @@ const TabsDesignEditor: React.FC<{ document.addEventListener("mousemove", handleMouseMove); document.addEventListener("mouseup", handleMouseUp); }, - [activeTabId, component, onUpdateComponent, tabs, snapTo10], + [activeTabId, component, onUpdateComponent, tabs, snapTo10] ); // 리사이즈 시작 핸들러 @@ -181,12 +183,12 @@ const TabsDesignEditor: React.FC<{ (e: React.MouseEvent, comp: TabInlineComponent, direction: "e" | "s" | "se") => { e.stopPropagation(); e.preventDefault(); - + const startMouseX = e.clientX; const startMouseY = e.clientY; const startWidth = comp.size?.width || 200; const startHeight = comp.size?.height || 100; - + setResizingCompId(comp.id); setResizeSize({ width: startWidth, height: startHeight }); @@ -194,21 +196,21 @@ const TabsDesignEditor: React.FC<{ if (rafRef.current) { cancelAnimationFrame(rafRef.current); } - + rafRef.current = requestAnimationFrame(() => { const deltaX = moveEvent.clientX - startMouseX; const deltaY = moveEvent.clientY - startMouseY; - + let newWidth = startWidth; let newHeight = startHeight; - + if (direction === "e" || direction === "se") { newWidth = snapTo10(Math.max(50, startWidth + deltaX)); } if (direction === "s" || direction === "se") { newHeight = snapTo10(Math.max(20, startHeight + deltaY)); } - + setResizeSize({ width: newWidth, height: newHeight }); }); }; @@ -216,18 +218,18 @@ const TabsDesignEditor: React.FC<{ const handleMouseUp = (upEvent: MouseEvent) => { document.removeEventListener("mousemove", handleMouseMove); document.removeEventListener("mouseup", handleMouseUp); - + if (rafRef.current) { cancelAnimationFrame(rafRef.current); rafRef.current = null; } - + const deltaX = upEvent.clientX - startMouseX; const deltaY = upEvent.clientY - startMouseY; - + let newWidth = startWidth; let newHeight = startHeight; - + if (direction === "e" || direction === "se") { newWidth = snapTo10(Math.max(50, startWidth + deltaX)); } @@ -250,7 +252,7 @@ const TabsDesignEditor: React.FC<{ height: newHeight, }, } - : c, + : c ), }; } @@ -265,7 +267,7 @@ const TabsDesignEditor: React.FC<{ }, }); } - + // 🆕 리사이즈 상태 해제 (resizeSize는 마지막 크기 유지, lastResizedCompId 설정) setLastResizedCompId(comp.id); setResizingCompId(null); @@ -274,13 +276,13 @@ const TabsDesignEditor: React.FC<{ document.addEventListener("mousemove", handleMouseMove); document.addEventListener("mouseup", handleMouseUp); }, - [activeTabId, component, onUpdateComponent, tabs], + [activeTabId, component, onUpdateComponent, tabs] ); return ( -
+
{/* 탭 헤더 */} -
+
{tabs.length > 0 ? ( tabs.map((tab) => (
{tab.label || "탭"} - {tab.components && tab.components.length > 0 && ( - ({tab.components.length}) - )}
)) ) : ( -
탭이 없습니다
+
+ 탭이 없습니다 +
)}
@@ -312,7 +313,10 @@ const TabsDesignEditor: React.FC<{ onClick={() => onSelectTabComponent?.(activeTabId, "", {} as TabInlineComponent)} > {activeTab ? ( -
+
{activeTab.components && activeTab.components.length > 0 ? (
{activeTab.components.map((comp: TabInlineComponent) => { @@ -341,8 +345,8 @@ const TabsDesignEditor: React.FC<{ }; // 드래그 중인 컴포넌트는 dragPosition 사용, 아니면 저장된 position 사용 - const displayX = isDragging && dragPosition ? dragPosition.x : comp.position?.x || 0; - const displayY = isDragging && dragPosition ? dragPosition.y : comp.position?.y || 0; + const displayX = isDragging && dragPosition ? dragPosition.x : (comp.position?.x || 0); + const displayY = isDragging && dragPosition ? dragPosition.y : (comp.position?.y || 0); return (
{ e.stopPropagation(); - console.log("🔍 [탭 컴포넌트] 클릭:", { - activeTabId, - compId: comp.id, - hasOnSelectTabComponent: !!onSelectTabComponent, - }); + console.log("🔍 [탭 컴포넌트] 클릭:", { activeTabId, compId: comp.id, hasOnSelectTabComponent: !!onSelectTabComponent }); onSelectTabComponent?.(activeTabId, comp.id, comp); }} > @@ -368,14 +368,14 @@ const TabsDesignEditor: React.FC<{
handleDragStart(e, comp)} >
- + {comp.label || comp.componentType}
@@ -404,42 +404,44 @@ const TabsDesignEditor: React.FC<{
{/* 실제 컴포넌트 렌더링 - 핸들 아래에 별도 영역 */} -
-
+
- + {/* 리사이즈 가장자리 영역 - 선택된 컴포넌트에만 표시 */} {isSelected && ( <> {/* 오른쪽 가장자리 (너비 조절) */}
handleResizeStart(e, comp, "e")} /> {/* 아래 가장자리 (높이 조절) */}
handleResizeStart(e, comp, "s")} /> {/* 오른쪽 아래 모서리 (너비+높이 조절) */}
handleResizeStart(e, comp, "se")} /> @@ -452,14 +454,20 @@ const TabsDesignEditor: React.FC<{ ) : (
-

컴포넌트를 드래그하여 추가

-

좌측 패널에서 컴포넌트를 이 영역에 드롭하세요

+

+ 컴포넌트를 드래그하여 추가 +

+

+ 좌측 패널에서 컴포넌트를 이 영역에 드롭하세요 +

)}
) : (
-

설정 패널에서 탭을 추가하세요

+

+ 설정 패널에서 탭을 추가하세요 +

)}
@@ -469,13 +477,19 @@ const TabsDesignEditor: React.FC<{ // TabsWidget 래퍼 컴포넌트 const TabsWidgetWrapper: React.FC = (props) => { - const { component, isDesignMode, onUpdateComponent, onSelectTabComponent, selectedTabComponentId, ...restProps } = - props; + const { + component, + isDesignMode, + onUpdateComponent, + onSelectTabComponent, + selectedTabComponentId, + ...restProps + } = props; // componentConfig에서 탭 정보 추출 const tabsConfig = component.componentConfig || {}; const tabs: TabItem[] = tabsConfig.tabs || []; - + // 🎯 디자인 모드에서는 드롭 가능한 에디터 UI 렌더링 if (isDesignMode) { return ( @@ -501,7 +515,8 @@ const TabsWidgetWrapper: React.FC = (props) => { persistSelection: tabsConfig.persistSelection || false, }; - const TabsWidget = require("@/components/screen/widgets/TabsWidget").TabsWidget; + const TabsWidget = + require("@/components/screen/widgets/TabsWidget").TabsWidget; return (
@@ -518,7 +533,8 @@ const TabsWidgetWrapper: React.FC = (props) => { ComponentRegistry.registerComponent({ id: "v2-tabs-widget", name: "탭 컴포넌트", - description: "탭별로 컴포넌트를 자유롭게 배치할 수 있는 레이아웃 컴포넌트입니다.", + description: + "탭별로 컴포넌트를 자유롭게 배치할 수 있는 레이아웃 컴포넌트입니다.", category: ComponentCategory.LAYOUT, webType: "text" as any, component: TabsWidgetWrapper, @@ -580,12 +596,20 @@ ComponentRegistry.registerComponent({ }, // 에디터 모드에서의 렌더링 - 탭 선택 및 컴포넌트 드롭 지원 - renderEditor: ({ component, isSelected, onClick, onDragStart, onDragEnd }) => { + renderEditor: ({ + component, + isSelected, + onClick, + onDragStart, + onDragEnd, + }) => { const tabsConfig = (component as any).componentConfig || {}; const tabs: TabItem[] = tabsConfig.tabs || []; // 에디터 모드에서 선택된 탭 상태 관리 - const [activeTabId, setActiveTabId] = useState(tabs[0]?.id || ""); + const [activeTabId, setActiveTabId] = useState( + tabs[0]?.id || "" + ); const activeTab = tabs.find((t) => t.id === activeTabId); @@ -596,19 +620,19 @@ ComponentRegistry.registerComponent({ "px-4 py-2 text-sm font-medium cursor-pointer transition-colors", isActive ? "bg-background border-b-2 border-primary text-primary" - : "text-muted-foreground hover:text-foreground hover:bg-muted/50", + : "text-muted-foreground hover:text-foreground hover:bg-muted/50" ); }; return (
{/* 탭 헤더 */} -
+
{tabs.length > 0 ? ( tabs.map((tab) => (
{tab.label || "탭"} - {tab.components && tab.components.length > 0 && ( - ({tab.components.length}) - )}
)) ) : ( -
탭이 없습니다
+
+ 탭이 없습니다 +
)}
@@ -653,8 +676,12 @@ ComponentRegistry.registerComponent({ }} >
- {comp.label || comp.componentType} - {comp.componentType} + + {comp.label || comp.componentType} + + + {comp.componentType} +
))} @@ -662,21 +689,27 @@ ComponentRegistry.registerComponent({ ) : (
-

컴포넌트를 드래그하여 추가

-

좌측 패널에서 컴포넌트를 이 영역에 드롭하세요

+

+ 컴포넌트를 드래그하여 추가 +

+

+ 좌측 패널에서 컴포넌트를 이 영역에 드롭하세요 +

)}
) : (
-

설정 패널에서 탭을 추가하세요

+

+ 설정 패널에서 탭을 추가하세요 +

)}
{/* 선택 표시 */} {isSelected && ( -
+
)}
); @@ -689,9 +722,11 @@ ComponentRegistry.registerComponent({ // 설정 패널 configPanel: React.lazy(() => - import("@/components/screen/config-panels/TabsConfigPanel").then((module) => ({ - default: module.TabsConfigPanel, - })), + import("@/components/screen/config-panels/TabsConfigPanel").then( + (module) => ({ + default: module.TabsConfigPanel, + }) + ) ), // 검증 함수