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:
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user