feat(screen-designer): 그리드 컬럼 시스템 개선 및 컴포넌트 너비 렌더링 수정

주요 변경사항:
- 격자 설정을 편집 탭에서 항상 표시 (해상도 설정 하단)
- 그리드 컬럼 수 동적 조정 가능 (1-24)
- 컴포넌트 생성 시 현재 그리드 컬럼 수 기반 자동 계산
- 컴포넌트 너비가 설정한 컬럼 수대로 정확히 표시되도록 수정

수정된 파일:
- ScreenDesigner: 컴포넌트 드롭 시 gridColumns와 style.width 동적 계산
- UnifiedPropertiesPanel: 격자 설정 UI 통합, 차지 컬럼 수 설정 시 width 자동 계산
- RealtimePreviewDynamic: getWidth 우선순위 수정, DOM 크기 디버깅 로그 추가
- 8개 컴포넌트: componentStyle.width를 항상 100%로 고정
  * ButtonPrimaryComponent
  * TextInputComponent
  * NumberInputComponent
  * TextareaBasicComponent
  * DateInputComponent
  * TableListComponent
  * CardDisplayComponent

문제 해결:
- 컴포넌트 내부에서 component.style.width를 재사용하여 이중 축소 발생
- 해결: 부모 컨테이너(RealtimePreviewDynamic)가 width 제어, 컴포넌트는 항상 100%
- 결과: 파란 테두리와 내부 콘텐츠가 동일한 크기로 정확히 표시
This commit is contained in:
kjs
2025-11-04 16:17:19 +09:00
parent 9f131a80ab
commit 6901baab8e
28 changed files with 1397 additions and 215 deletions

View File

@@ -200,19 +200,58 @@ export const RealtimePreviewDynamic: React.FC<RealtimePreviewProps> = ({
: {};
// 컴포넌트 기본 스타일 - 레이아웃은 항상 맨 아래
// 너비 우선순위: style.width > size.width (픽셀값)
// 너비 우선순위: style.width > 조건부 100% > size.width (픽셀값)
const getWidth = () => {
// 1순위: style.width가 있으면 우선 사용
// 1순위: style.width가 있으면 우선 사용 (퍼센트 값)
if (componentStyle?.width) {
console.log("✅ [getWidth] style.width 사용:", {
componentId: id,
label: component.label,
styleWidth: componentStyle.width,
gridColumns: (component as any).gridColumns,
componentStyle: componentStyle,
baseStyle: {
left: `${position.x}px`,
top: `${position.y}px`,
width: componentStyle.width,
height: getHeight(),
},
});
return componentStyle.width;
}
// 2순위: size.width (픽셀)
if (component.componentConfig?.type === "table-list") {
return `${Math.max(size?.width || 120, 120)}px`;
// 2순위: x=0인 컴포넌트는 전체 너비 사용 (버튼 제외)
const isButtonComponent =
(component.type === "widget" && (component as WidgetComponent).widgetType === "button") ||
(component.type === "component" && (component as any).componentType?.includes("button"));
if (position.x === 0 && !isButtonComponent) {
console.log("⚠️ [getWidth] 100% 사용 (x=0):", {
componentId: id,
label: component.label,
});
return "100%";
}
return `${size?.width || 100}px`;
// 3순위: size.width (픽셀)
if (component.componentConfig?.type === "table-list") {
const width = `${Math.max(size?.width || 120, 120)}px`;
console.log("📏 [getWidth] 픽셀 사용 (table-list):", {
componentId: id,
label: component.label,
width,
});
return width;
}
const width = `${size?.width || 100}px`;
console.log("📏 [getWidth] 픽셀 사용 (기본):", {
componentId: id,
label: component.label,
width,
sizeWidth: size?.width,
});
return width;
};
const getHeight = () => {
@@ -235,35 +274,54 @@ export const RealtimePreviewDynamic: React.FC<RealtimePreviewProps> = ({
return `${size?.height || 40}px`;
};
// 버튼 컴포넌트인지 확인
const isButtonComponent =
(component.type === "widget" && (component as WidgetComponent).widgetType === "button") ||
(component.type === "component" && (component as any).componentType?.includes("button"));
// 버튼일 경우 로그 출력 (편집기)
if (isButtonComponent && isDesignMode) {
console.log("🎨 [편집기] 버튼 위치:", {
label: component.label,
positionX: position.x,
positionY: position.y,
sizeWidth: size?.width,
sizeHeight: size?.height,
});
}
const baseStyle = {
left: `${position.x}px`,
top: `${position.y}px`,
// x=0인 컴포넌트는 전체 너비 사용 (버튼 제외)
width: (position.x === 0 && !isButtonComponent) ? "100%" : getWidth(),
width: getWidth(), // getWidth()가 모든 우선순위를 처리
height: getHeight(),
zIndex: component.type === "layout" ? 1 : position.z || 2,
...componentStyle,
// x=0인 컴포넌트는 100% 너비 강제 (버튼 제외)
...(position.x === 0 && !isButtonComponent && { width: "100%" }),
right: undefined,
};
// 🔍 DOM 렌더링 후 실제 크기 측정
const innerDivRef = React.useRef<HTMLDivElement>(null);
const outerDivRef = React.useRef<HTMLDivElement>(null);
React.useEffect(() => {
if (outerDivRef.current && innerDivRef.current) {
const outerRect = outerDivRef.current.getBoundingClientRect();
const innerRect = innerDivRef.current.getBoundingClientRect();
const computedOuter = window.getComputedStyle(outerDivRef.current);
const computedInner = window.getComputedStyle(innerDivRef.current);
console.log("📐 [DOM 실제 크기 상세]:", {
componentId: id,
label: component.label,
gridColumns: (component as any).gridColumns,
"1. baseStyle.width": baseStyle.width,
"2. 외부 div (파란 테두리)": {
width: `${outerRect.width}px`,
height: `${outerRect.height}px`,
computedWidth: computedOuter.width,
computedHeight: computedOuter.height,
},
"3. 내부 div (컨텐츠 래퍼)": {
width: `${innerRect.width}px`,
height: `${innerRect.height}px`,
computedWidth: computedInner.width,
computedHeight: computedInner.height,
className: innerDivRef.current.className,
inlineStyle: innerDivRef.current.getAttribute("style"),
},
"4. 너비 비교": {
"외부 / 내부": `${outerRect.width}px / ${innerRect.width}px`,
"비율": `${((innerRect.width / outerRect.width) * 100).toFixed(2)}%`,
},
});
}
}, [id, component.label, (component as any).gridColumns, baseStyle.width]);
const handleClick = (e: React.MouseEvent) => {
e.stopPropagation();
onClick?.(e);
@@ -285,7 +343,9 @@ export const RealtimePreviewDynamic: React.FC<RealtimePreviewProps> = ({
return (
<div
ref={outerDivRef}
id={`component-${id}`}
data-component-id={id}
className="absolute cursor-pointer transition-all duration-200 ease-out"
style={{ ...baseStyle, ...selectionStyle }}
onClick={handleClick}
@@ -296,10 +356,15 @@ export const RealtimePreviewDynamic: React.FC<RealtimePreviewProps> = ({
>
{/* 동적 컴포넌트 렌더링 */}
<div
ref={
component.type === "component" && (component as any).componentType === "flow-widget" ? contentRef : undefined
}
className={`${component.type === "component" && (component as any).componentType === "flow-widget" ? "h-auto" : "h-full"} w-full max-w-full overflow-visible`}
ref={(node) => {
// 멀티 ref 처리
innerDivRef.current = node;
if (component.type === "component" && (component as any).componentType === "flow-widget") {
(contentRef as any).current = node;
}
}}
className={`${component.type === "component" && (component as any).componentType === "flow-widget" ? "h-auto" : "h-full"} overflow-visible`}
style={{ width: "100%", maxWidth: "100%" }}
>
<DynamicComponentRenderer
component={component}