291 lines
10 KiB
TypeScript
291 lines
10 KiB
TypeScript
"use client";
|
|
|
|
import React from "react";
|
|
import { LayoutRendererProps } from "../BaseLayoutRenderer";
|
|
import { filterDOMProps } from "@/lib/utils/domPropsFilter";
|
|
|
|
/**
|
|
* Flexbox 레이아웃 컴포넌트
|
|
*/
|
|
export interface FlexboxLayoutProps extends LayoutRendererProps {
|
|
renderer: any; // FlexboxLayoutRenderer 타입
|
|
}
|
|
|
|
export const FlexboxLayout: React.FC<FlexboxLayoutProps> = ({
|
|
layout,
|
|
isDesignMode = false,
|
|
isSelected = false,
|
|
onClick,
|
|
className = "",
|
|
renderer,
|
|
onZoneComponentDrop,
|
|
onZoneClick,
|
|
...props
|
|
}) => {
|
|
if (!layout.layoutConfig.flexbox) {
|
|
return (
|
|
<div className="error-layout flex items-center justify-center rounded border-2 border-red-300 bg-red-50 p-4">
|
|
<div className="text-center text-red-600">
|
|
<div className="font-medium">플렉스박스 레이아웃 설정이 없습니다.</div>
|
|
<div className="mt-1 text-sm">layoutConfig.flexbox가 필요합니다.</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const flexConfig = layout.layoutConfig.flexbox;
|
|
const containerStyle = renderer.getLayoutContainerStyle();
|
|
|
|
console.log("🔍 FlexboxLayout 컨테이너 설정:", {
|
|
layoutId: layout.id,
|
|
flexConfig,
|
|
direction: flexConfig.direction,
|
|
layoutConfig: layout.layoutConfig,
|
|
});
|
|
|
|
// 플렉스박스 스타일 설정 (containerStyle보다 flex 속성을 우선 적용)
|
|
const flexStyle: React.CSSProperties = {
|
|
display: "flex !important" as any,
|
|
flexDirection: flexConfig.direction,
|
|
justifyContent: flexConfig.justify,
|
|
alignItems: flexConfig.align,
|
|
flexWrap: flexConfig.wrap,
|
|
gap: `${flexConfig.gap}px`,
|
|
// 레이아웃 컴포넌트의 높이 적용 - 부모 높이를 100% 따라가도록
|
|
height: layout.size?.height ? `${layout.size.height}px` : "100%",
|
|
width: layout.size?.width ? `${layout.size.width}px` : "100%",
|
|
minHeight: layout.size?.height ? `${layout.size.height}px` : "200px", // 최소 높이 보장
|
|
// containerStyle을 나중에 적용하되, flex 관련 속성은 덮어쓰지 않도록
|
|
...containerStyle,
|
|
// flex 속성들을 다시 강제 적용 (!important 사용)
|
|
display: "flex !important" as any,
|
|
flexDirection: flexConfig.direction,
|
|
// 높이 관련 속성도 강제 적용
|
|
height: layout.size?.height ? `${layout.size.height}px` : "100%",
|
|
minHeight: layout.size?.height ? `${layout.size.height}px` : "200px",
|
|
};
|
|
|
|
console.log("🎨 FlexboxLayout 최종 스타일:", {
|
|
display: flexStyle.display,
|
|
flexDirection: flexStyle.flexDirection,
|
|
height: flexStyle.height,
|
|
width: flexStyle.width,
|
|
});
|
|
|
|
// 각 존의 크기 정보 상세 로깅
|
|
console.log(
|
|
"🔍 각 존의 크기 정보:",
|
|
layout.zones?.map((zone) => ({
|
|
id: zone.id,
|
|
size: zone.size,
|
|
calculatedStyle: {
|
|
width: zone.size?.width || "auto",
|
|
height: zone.size?.height || defaultZoneHeight,
|
|
minHeight: zone.size?.minHeight || "auto",
|
|
maxHeight: zone.size?.maxHeight || "none",
|
|
},
|
|
})),
|
|
);
|
|
|
|
// 디자인 모드 스타일
|
|
if (isDesignMode) {
|
|
flexStyle.border = isSelected ? "2px solid #3b82f6" : "1px solid #e2e8f0";
|
|
flexStyle.borderRadius = "8px";
|
|
flexStyle.padding = "8px";
|
|
}
|
|
|
|
// DOM 안전한 props만 필터링
|
|
const domProps = filterDOMProps(props);
|
|
|
|
return (
|
|
<>
|
|
<style jsx>{`
|
|
.force-flex-layout {
|
|
display: flex !important;
|
|
flex-direction: ${flexConfig.direction} !important;
|
|
height: ${layout.size?.height ? `${layout.size.height}px` : "100%"} !important;
|
|
min-height: ${layout.size?.height ? `${layout.size.height}px` : "200px"} !important;
|
|
width: 100% !important;
|
|
}
|
|
`}</style>
|
|
<div
|
|
className={`flexbox-layout force-flex-layout ${isDesignMode ? "design-mode" : ""} ${className}`}
|
|
style={flexStyle}
|
|
onClick={onClick}
|
|
draggable={isDesignMode}
|
|
onDragStart={onDragStart}
|
|
onDragEnd={onDragEnd}
|
|
{...domProps}
|
|
>
|
|
{layout.zones.map((zone: any) => {
|
|
const zoneChildren = renderer.getZoneChildren(zone.id);
|
|
|
|
// 레이아웃 전체 높이 기준으로 존 높이 계산
|
|
const layoutHeight = layout.size?.height || 400; // 기본값 400px
|
|
const defaultZoneHeight =
|
|
flexConfig.direction === "column"
|
|
? Math.floor(layoutHeight / layout.zones.length) - 32 // 세로 배치시 존 개수로 나눔
|
|
: layoutHeight - 32; // 가로 배치시 전체 높이 사용
|
|
|
|
console.log("🔍 FlexboxLayout 높이 정보:", {
|
|
layoutId: layout.id,
|
|
layoutSize: layout.size,
|
|
layoutHeight,
|
|
defaultZoneHeight,
|
|
flexDirection: flexConfig.direction,
|
|
zoneId: zone.id,
|
|
zoneSize: zone.size,
|
|
});
|
|
|
|
// 플렉스 아이템 스타일 설정
|
|
const zoneStyle: React.CSSProperties = {
|
|
flex: renderer.calculateFlexValue(zone, flexConfig.direction),
|
|
// 카드 레이아웃처럼 항상 명확한 경계 표시
|
|
backgroundColor: "white",
|
|
border: "1px solid #e5e7eb",
|
|
borderRadius: "8px",
|
|
padding: "16px",
|
|
boxShadow: "0 1px 3px 0 rgba(0, 0, 0, 0.1)",
|
|
// 존의 크기: 개별 설정이 있으면 우선, 없으면 계산된 기본값
|
|
width: zone.size?.width
|
|
? typeof zone.size.width === "number"
|
|
? `${zone.size.width}px`
|
|
: zone.size.width
|
|
: "auto",
|
|
height: zone.size?.height
|
|
? typeof zone.size.height === "number"
|
|
? `${zone.size.height}px`
|
|
: zone.size.height
|
|
: flexConfig.direction === "row"
|
|
? "100%" // 가로 배치일 때는 부모 높이를 100% 따라감
|
|
: `${defaultZoneHeight}px`,
|
|
minHeight: zone.size?.minHeight
|
|
? typeof zone.size.minHeight === "number"
|
|
? `${zone.size.minHeight}px`
|
|
: zone.size.minHeight
|
|
: "100px",
|
|
maxHeight: zone.size?.maxHeight
|
|
? typeof zone.size.maxHeight === "number"
|
|
? `${zone.size.maxHeight}px`
|
|
: zone.size.maxHeight
|
|
: "none",
|
|
position: "relative",
|
|
transition: "all 0.2s ease",
|
|
margin: "4px",
|
|
overflow: "hidden",
|
|
display: "flex",
|
|
flexDirection: "column",
|
|
};
|
|
|
|
return (
|
|
<div
|
|
key={zone.id}
|
|
style={zoneStyle}
|
|
className="flex-zone"
|
|
onMouseEnter={(e) => {
|
|
e.currentTarget.style.borderColor = "#3b82f6";
|
|
e.currentTarget.style.boxShadow =
|
|
"0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 0 0 2px rgba(59, 130, 246, 0.1)";
|
|
}}
|
|
onMouseLeave={(e) => {
|
|
e.currentTarget.style.borderColor = "#e5e7eb";
|
|
e.currentTarget.style.boxShadow = "0 1px 3px 0 rgba(0, 0, 0, 0.1)";
|
|
}}
|
|
// 드롭 이벤트 제거 - 일반 캔버스 드롭만 사용
|
|
onClick={(e) => {
|
|
e.stopPropagation();
|
|
if (onZoneClick) {
|
|
onZoneClick(zone.id);
|
|
}
|
|
}}
|
|
>
|
|
{/* 존 라벨 */}
|
|
{isDesignMode && (
|
|
<div
|
|
className="zone-label"
|
|
style={{
|
|
position: "absolute",
|
|
top: "-2px",
|
|
left: "8px",
|
|
backgroundColor: "#3b82f6",
|
|
color: "white",
|
|
fontSize: "10px",
|
|
padding: "2px 6px",
|
|
borderRadius: "0 0 4px 4px",
|
|
fontWeight: "500",
|
|
zIndex: 10,
|
|
}}
|
|
>
|
|
{zone.name || zone.id}
|
|
</div>
|
|
)}
|
|
|
|
{/* 존 내용 */}
|
|
<div
|
|
style={{
|
|
paddingTop: isDesignMode ? "16px" : "0",
|
|
flex: 1,
|
|
position: "relative",
|
|
minHeight: "100px",
|
|
}}
|
|
className="flex-zone-content"
|
|
>
|
|
{/* 존 안의 컴포넌트들을 절대 위치로 렌더링 */}
|
|
{zoneChildren.map((child: any) => (
|
|
<div
|
|
key={child.id}
|
|
style={{
|
|
position: "absolute",
|
|
left: child.position?.x || 0,
|
|
top: child.position?.y || 0,
|
|
width: child.size?.width || "auto",
|
|
height: child.size?.height || "auto",
|
|
zIndex: child.position?.z || 1,
|
|
}}
|
|
>
|
|
{renderer.renderChild(child)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
|
|
{/* 존이 없을 때 안내 메시지 */}
|
|
{layout.zones.length === 0 && (
|
|
<div
|
|
className="empty-flex-container"
|
|
style={{
|
|
flex: 1,
|
|
border: isDesignMode ? "2px dashed #cbd5e1" : "1px solid #e2e8f0",
|
|
borderRadius: "8px",
|
|
backgroundColor: isDesignMode ? "rgba(148, 163, 184, 0.05)" : "rgba(248, 250, 252, 0.5)",
|
|
display: "flex",
|
|
alignItems: "center",
|
|
justifyContent: "center",
|
|
fontSize: isDesignMode ? "14px" : "12px",
|
|
color: "#64748b",
|
|
minHeight: "100px",
|
|
padding: "20px",
|
|
textAlign: "center",
|
|
transition: "all 0.2s ease",
|
|
}}
|
|
onMouseEnter={(e) => {
|
|
e.currentTarget.style.borderColor = "#3b82f6";
|
|
e.currentTarget.style.backgroundColor = "rgba(59, 130, 246, 0.05)";
|
|
}}
|
|
onMouseLeave={(e) => {
|
|
e.currentTarget.style.borderColor = isDesignMode ? "#cbd5e1" : "#e2e8f0";
|
|
e.currentTarget.style.backgroundColor = isDesignMode
|
|
? "rgba(148, 163, 184, 0.05)"
|
|
: "rgba(248, 250, 252, 0.5)";
|
|
}}
|
|
>
|
|
{isDesignMode ? "플렉스박스 레이아웃에 존을 추가하세요" : "빈 레이아웃"}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</>
|
|
);
|
|
};
|