레이아웃 추가기능
This commit is contained in:
160
frontend/lib/registry/layouts/grid/GridLayout.tsx
Normal file
160
frontend/lib/registry/layouts/grid/GridLayout.tsx
Normal file
@@ -0,0 +1,160 @@
|
||||
"use client";
|
||||
|
||||
import React from "react";
|
||||
import { LayoutRendererProps } from "../BaseLayoutRenderer";
|
||||
|
||||
/**
|
||||
* 그리드 레이아웃 컴포넌트
|
||||
*/
|
||||
export interface GridLayoutProps extends LayoutRendererProps {
|
||||
renderer: any; // GridLayoutRenderer 타입
|
||||
}
|
||||
|
||||
export const GridLayout: React.FC<GridLayoutProps> = ({
|
||||
layout,
|
||||
isDesignMode = false,
|
||||
isSelected = false,
|
||||
onClick,
|
||||
className = "",
|
||||
renderer,
|
||||
...props
|
||||
}) => {
|
||||
if (!layout.layoutConfig.grid) {
|
||||
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.grid가 필요합니다.</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const gridConfig = layout.layoutConfig.grid;
|
||||
const containerStyle = renderer.getLayoutContainerStyle();
|
||||
|
||||
// 그리드 컨테이너 스타일
|
||||
const gridStyle: React.CSSProperties = {
|
||||
...containerStyle,
|
||||
display: "grid",
|
||||
gridTemplateRows: `repeat(${gridConfig.rows}, 1fr)`,
|
||||
gridTemplateColumns: `repeat(${gridConfig.columns}, 1fr)`,
|
||||
gap: `${gridConfig.gap || 16}px`,
|
||||
height: "100%",
|
||||
width: "100%",
|
||||
};
|
||||
|
||||
// 디자인 모드 스타일
|
||||
if (isDesignMode) {
|
||||
gridStyle.border = isSelected ? "2px solid #3b82f6" : "1px solid #e2e8f0";
|
||||
gridStyle.borderRadius = "8px";
|
||||
}
|
||||
|
||||
// DOM props만 추출 (React DOM에서 인식하는 props만)
|
||||
const {
|
||||
children: propsChildren,
|
||||
onUpdateLayout,
|
||||
onSelectComponent,
|
||||
isDesignMode: _isDesignMode,
|
||||
allComponents,
|
||||
onZoneClick,
|
||||
onComponentDrop,
|
||||
onDragStart,
|
||||
onDragEnd,
|
||||
...domProps
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`grid-layout ${isDesignMode ? "design-mode" : ""} ${className}`}
|
||||
style={gridStyle}
|
||||
onClick={onClick}
|
||||
draggable={isDesignMode}
|
||||
onDragStart={onDragStart}
|
||||
onDragEnd={onDragEnd}
|
||||
{...domProps}
|
||||
>
|
||||
{layout.zones.map((zone: any) => {
|
||||
const zoneChildren = renderer.getZoneChildren(zone.id);
|
||||
|
||||
// 그리드 위치 설정
|
||||
const zoneStyle: React.CSSProperties = {
|
||||
gridRow: zone.position.row !== undefined ? zone.position.row + 1 : undefined,
|
||||
gridColumn: zone.position.column !== undefined ? zone.position.column + 1 : undefined,
|
||||
};
|
||||
|
||||
return renderer.renderZone(zone, zoneChildren, {
|
||||
style: zoneStyle,
|
||||
className: "grid-zone",
|
||||
});
|
||||
})}
|
||||
|
||||
{/* 디자인 모드에서 빈 그리드 셀 표시 */}
|
||||
{isDesignMode && <GridEmptyCells gridConfig={gridConfig} layout={layout} isDesignMode={isDesignMode} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* 빈 그리드 셀들을 렌더링하는 컴포넌트
|
||||
*/
|
||||
const GridEmptyCells: React.FC<{
|
||||
gridConfig: any;
|
||||
layout: any;
|
||||
isDesignMode: boolean;
|
||||
}> = ({ gridConfig, layout, isDesignMode }) => {
|
||||
const totalCells = gridConfig.rows * gridConfig.columns;
|
||||
const occupiedCells = new Set(
|
||||
layout.zones
|
||||
.map((zone: any) =>
|
||||
zone.position.row !== undefined && zone.position.column !== undefined
|
||||
? zone.position.row * gridConfig.columns + zone.position.column
|
||||
: -1,
|
||||
)
|
||||
.filter((index: number) => index >= 0),
|
||||
);
|
||||
|
||||
const emptyCells: React.ReactElement[] = [];
|
||||
|
||||
for (let i = 0; i < totalCells; i++) {
|
||||
if (!occupiedCells.has(i)) {
|
||||
const row = Math.floor(i / gridConfig.columns);
|
||||
const column = i % gridConfig.columns;
|
||||
|
||||
emptyCells.push(
|
||||
<div
|
||||
key={`empty-${i}`}
|
||||
className="empty-grid-cell"
|
||||
style={{
|
||||
gridRow: row + 1,
|
||||
gridColumn: column + 1,
|
||||
border: isDesignMode ? "1px dashed #cbd5e1" : "1px solid #f1f5f9",
|
||||
borderRadius: "4px",
|
||||
backgroundColor: isDesignMode ? "rgba(148, 163, 184, 0.05)" : "rgba(248, 250, 252, 0.3)",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
fontSize: "10px",
|
||||
color: "#94a3b8",
|
||||
minHeight: "40px",
|
||||
transition: "all 0.2s ease",
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.backgroundColor = "rgba(59, 130, 246, 0.05)";
|
||||
e.currentTarget.style.borderColor = "#3b82f6";
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.backgroundColor = isDesignMode
|
||||
? "rgba(148, 163, 184, 0.05)"
|
||||
: "rgba(248, 250, 252, 0.3)";
|
||||
e.currentTarget.style.borderColor = isDesignMode ? "#cbd5e1" : "#f1f5f9";
|
||||
}}
|
||||
>
|
||||
{isDesignMode ? `${row + 1},${column + 1}` : ""}
|
||||
</div>,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return <>{emptyCells}</>;
|
||||
};
|
||||
Reference in New Issue
Block a user