diff --git a/frontend/components/screen/EditModal.tsx b/frontend/components/screen/EditModal.tsx index b3d0c1cd..c49dca58 100644 --- a/frontend/components/screen/EditModal.tsx +++ b/frontend/components/screen/EditModal.tsx @@ -563,8 +563,20 @@ export const EditModal: React.FC = ({ className }) => { if (screenInfo && layoutData) { const components = layoutData.components || []; - // 화면의 실제 크기 계산 - const dimensions = calculateScreenDimensions(components); + // 화면 관리에서 설정한 해상도 우선 사용 (ScreenModal과 동일) + const screenResolution = (layoutData as any).screenResolution || (screenInfo as any).screenResolution; + + let dimensions; + if (screenResolution && screenResolution.width && screenResolution.height) { + dimensions = { + width: screenResolution.width, + height: screenResolution.height, + offsetX: 0, + offsetY: 0, + }; + } else { + dimensions = calculateScreenDimensions(components); + } setScreenDimensions(dimensions); setScreenData({ @@ -1547,31 +1559,25 @@ export const EditModal: React.FC = ({ className }) => { } }; - // 모달 크기 설정 - 화면관리 설정 크기 + 헤더 + // 모달 크기 설정 - ScreenModal과 동일한 방식 (maxHeight로 유연 처리) const getModalStyle = () => { if (!screenDimensions) { return { - className: "w-fit min-w-[400px] max-w-4xl max-h-[90vh] overflow-hidden p-0", - style: undefined, // undefined로 변경 - defaultWidth/defaultHeight 사용 + className: "w-fit min-w-[400px] max-w-4xl overflow-hidden", + style: { padding: 0, gap: 0, maxHeight: "calc(100dvh - 8px)" }, }; } - // 화면관리에서 설정한 크기 = 컨텐츠 영역 크기 - // 실제 모달 크기 = 컨텐츠 + 헤더 + gap + padding + 라벨 공간 - const headerHeight = 52; // DialogHeader (타이틀 + border-b + py-3) - const dialogGap = 16; // DialogContent gap-4 - const extraPadding = 24; // 추가 여백 (안전 마진) - const labelSpace = 30; // 입력 필드 위 라벨 공간 (-top-6 = 24px + 여유) - - const totalHeight = screenDimensions.height + headerHeight + dialogGap + extraPadding + labelSpace; + const finalWidth = Math.min(screenDimensions.width, window.innerWidth * 0.98); return { className: "overflow-hidden p-0", style: { - width: `${Math.min(screenDimensions.width + 48, window.innerWidth * 0.98)}px`, // 좌우 패딩 추가 - height: `${Math.min(totalHeight, window.innerHeight * 0.95)}px`, + width: `${finalWidth}px`, + maxHeight: "calc(100dvh - 8px)", maxWidth: "98vw", - maxHeight: "95vh", + padding: 0, + gap: 0, }, }; }; @@ -1593,7 +1599,7 @@ export const EditModal: React.FC = ({ className }) => { -
+
{loading ? (
@@ -1608,42 +1614,41 @@ export const EditModal: React.FC = ({ className }) => { >
{ - const baseHeight = (screenDimensions?.height || 600) + 30; + const baseHeight = screenDimensions?.height || 600; if (activeConditionalComponents.length > 0) { - // 조건부 레이어 컴포넌트 중 가장 아래 위치 계산 const offsetY = screenDimensions?.offsetY || 0; let maxBottom = 0; activeConditionalComponents.forEach((comp) => { - const y = parseFloat(comp.position?.y?.toString() || "0") - offsetY + 30; + const y = parseFloat(comp.position?.y?.toString() || "0") - offsetY; const h = parseFloat(comp.size?.height?.toString() || "40"); maxBottom = Math.max(maxBottom, y + h); }); - return Math.max(baseHeight, maxBottom + 20); // 20px 여백 + return `${Math.max(baseHeight, maxBottom + 20)}px`; } - return baseHeight; + return `${baseHeight}px`; })(), - transformOrigin: "center center", - maxWidth: "100%", + overflow: "visible", }} > {/* 기본 레이어 컴포넌트 렌더링 */} {screenData.components.map((component) => { - // 컴포넌트 위치를 offset만큼 조정 const offsetX = screenDimensions?.offsetX || 0; const offsetY = screenDimensions?.offsetY || 0; - const labelSpace = 30; // 라벨 공간 (입력 필드 위 -top-6 라벨용) + // screenResolution이 있으면 offsetY=0이므로 디자이너 좌표 그대로 사용 + // offsetY > 0 (자동 계산)일 때만 라벨 공간 보정 + const labelSpace = offsetY > 0 ? 30 : 0; const adjustedComponent = { ...component, position: { ...component.position, x: parseFloat(component.position?.x?.toString() || "0") - offsetX, - y: parseFloat(component.position?.y?.toString() || "0") - offsetY + labelSpace, // 라벨 공간 추가 + y: parseFloat(component.position?.y?.toString() || "0") - offsetY + labelSpace, }, }; @@ -1709,11 +1714,11 @@ export const EditModal: React.FC = ({ className }) => { ); })} - {/* 🆕 조건부 레이어 컴포넌트 렌더링 */} + {/* 조건부 레이어 컴포넌트 렌더링 */} {activeConditionalComponents.map((component) => { const offsetX = screenDimensions?.offsetX || 0; const offsetY = screenDimensions?.offsetY || 0; - const labelSpace = 30; + const labelSpace = offsetY > 0 ? 30 : 0; const adjustedComponent = { ...component, diff --git a/frontend/components/v2/V2Input.tsx b/frontend/components/v2/V2Input.tsx index 3464f982..5e4ed162 100644 --- a/frontend/components/v2/V2Input.tsx +++ b/frontend/components/v2/V2Input.tsx @@ -89,12 +89,12 @@ function formatTel(value: string): string { return `${digits.slice(0, 4)}-${digits.slice(4, 8)}`; } - // 서울: 02 → 2-4-4 + // 서울: 02 → 9자리 2-3-4, 10자리 2-4-4 if (digits.startsWith("02")) { if (digits.length <= 2) return digits; - if (digits.length <= 6) return `${digits.slice(0, 2)}-${digits.slice(2)}`; - if (digits.length <= 10) return `${digits.slice(0, 2)}-${digits.slice(2, 6)}-${digits.slice(6)}`; - return `${digits.slice(0, 2)}-${digits.slice(2, 6)}-${digits.slice(6, 10)}`; + if (digits.length <= 5) return `${digits.slice(0, 2)}-${digits.slice(2)}`; + const mid = digits.length >= 10 ? 4 : 3; + return `${digits.slice(0, 2)}-${digits.slice(2, 2 + mid)}-${digits.slice(2 + mid, 2 + mid + 4)}`; } // 안심번호: 050x → 4-4-4 @@ -1228,7 +1228,7 @@ export const V2Input = forwardRef((props, ref) => ref={ref} id={id} className={cn( - "flex gap-1", + "flex", labelPos === "left" ? "flex-row items-center" : "flex-row-reverse items-center", )} style={{ diff --git a/frontend/components/v2/V2Select.tsx b/frontend/components/v2/V2Select.tsx index 9ced9670..a078ea67 100644 --- a/frontend/components/v2/V2Select.tsx +++ b/frontend/components/v2/V2Select.tsx @@ -1291,7 +1291,7 @@ export const V2Select = forwardRef((props, ref) = ref={ref} id={id} className={cn( - "flex gap-1", + "flex", labelPos === "left" ? "flex-row items-center" : "flex-row-reverse items-center", isDesignMode && "pointer-events-none", )} diff --git a/frontend/lib/registry/DynamicComponentRenderer.tsx b/frontend/lib/registry/DynamicComponentRenderer.tsx index 24a50cdd..f06a43fe 100644 --- a/frontend/lib/registry/DynamicComponentRenderer.tsx +++ b/frontend/lib/registry/DynamicComponentRenderer.tsx @@ -808,12 +808,10 @@ export const DynamicComponentRenderer: React.FC = ? component.style?.labelText || (component as any).label || component.componentConfig?.label : undefined; - // 🔧 수평 라벨(left/right) 감지 → 런타임에서만 외부 flex 컨테이너로 라벨 처리 - // 디자인 모드에서는 V2 컴포넌트가 자체적으로 라벨을 렌더링 (height 체인 문제 방지) + // 🔧 수평 라벨(left/right) 감지 → 외부 absolute 래퍼로 라벨 처리 (카테고리 셀렉트와 동일 방식) const labelPosition = component.style?.labelPosition; const isV2Component = componentType?.startsWith("v2-"); const needsExternalHorizLabel = !!( - !props.isDesignMode && isV2Component && effectiveLabel && (labelPosition === "left" || labelPosition === "right")