ERP-node/frontend/components/screen/ResponsiveDesignerContainer...

173 lines
5.3 KiB
TypeScript
Raw Normal View History

import React, { useState, useRef } from "react";
import { Button } from "@/components/ui/button";
import { Monitor, Maximize2, ZoomIn, ZoomOut } from "lucide-react";
import { useContainerSize } from "@/hooks/useViewportSize";
interface ResponsiveDesignerContainerProps {
children: React.ReactNode;
designWidth: number;
designHeight: number;
screenName?: string;
onScaleChange?: (scale: number) => void;
}
type DesignerViewMode = "fit" | "original" | "custom";
/**
*
*
*/
export const ResponsiveDesignerContainer: React.FC<ResponsiveDesignerContainerProps> = ({
children,
designWidth,
designHeight,
screenName,
onScaleChange,
}) => {
const containerRef = useRef<HTMLDivElement>(null);
2025-09-23 15:31:27 +09:00
const [viewMode, setViewMode] = useState<DesignerViewMode>("original");
const [customScale, setCustomScale] = useState(1);
const containerSize = useContainerSize(containerRef);
// 스케일 계산
const calculateScale = (): number => {
if (containerSize.width === 0 || containerSize.height === 0) return 1;
switch (viewMode) {
case "fit":
2025-11-24 12:02:23 +09:00
// 컨테이너에 맞춰 비율 유지하며 조정 (좌우 여백 16px씩 유지)
const scaleX = (containerSize.width - 32) / designWidth;
const scaleY = (containerSize.height - 64) / designHeight;
return Math.min(scaleX, scaleY, 2); // 최대 2배까지 허용
case "custom":
return customScale;
case "original":
default:
return 1;
}
};
const scale = calculateScale();
// 스케일 변경 시 콜백 호출
React.useEffect(() => {
onScaleChange?.(scale);
}, [scale, onScaleChange]);
const handleZoomIn = () => {
const newScale = Math.min(customScale * 1.1, 3);
setCustomScale(newScale);
setViewMode("custom");
};
const handleZoomOut = () => {
const newScale = Math.max(customScale * 0.9, 0.1);
setCustomScale(newScale);
setViewMode("custom");
};
const getViewModeInfo = (mode: DesignerViewMode) => {
switch (mode) {
case "fit":
return {
label: "화면 맞춤",
description: "뷰포트에 맞춰 자동 조정",
icon: <Monitor className="h-4 w-4" />,
};
case "original":
return {
label: "원본 크기",
description: "설계 해상도 100% 표시",
icon: <Maximize2 className="h-4 w-4" />,
};
case "custom":
return {
label: `사용자 정의 (${Math.round(customScale * 100)}%)`,
description: "사용자가 조정한 배율",
icon: <ZoomIn className="h-4 w-4" />,
};
}
};
const screenStyle = {
width: `${designWidth}px`,
height: `${designHeight}px`,
transform: `scale(${scale})`,
transformOrigin: "top left",
transition: "transform 0.3s ease-in-out",
};
const wrapperStyle = {
width: `${designWidth * scale}px`,
height: `${designHeight * scale}px`,
overflow: "hidden",
};
return (
<div className="flex h-full w-full flex-col bg-gray-100">
{/* 상단 컨트롤 바 */}
<div className="flex items-center justify-between border-b bg-white px-4 py-2 shadow-sm">
<div className="flex items-center space-x-2">
<span className="text-sm font-medium text-gray-700">
{screenName && `${screenName} - `}
{designWidth} × {designHeight}
</span>
<span className="text-xs text-gray-500">
(: {Math.round(scale * 100)}% | : {containerSize.width}×{containerSize.height})
</span>
</div>
<div className="flex items-center space-x-1">
{/* 줌 컨트롤 */}
<Button variant="ghost" size="sm" onClick={handleZoomOut} className="h-8 w-8 p-0" title="축소">
<ZoomOut className="h-4 w-4" />
</Button>
<span className="min-w-[60px] px-2 text-center text-xs text-muted-foreground">{Math.round(scale * 100)}%</span>
<Button variant="ghost" size="sm" onClick={handleZoomIn} className="h-8 w-8 p-0" title="확대">
<ZoomIn className="h-4 w-4" />
</Button>
{/* 뷰 모드 버튼 */}
{(["fit", "original"] as DesignerViewMode[]).map((mode) => {
const info = getViewModeInfo(mode);
return (
<Button
key={mode}
variant={viewMode === mode ? "default" : "ghost"}
size="sm"
onClick={() => setViewMode(mode)}
className="h-8 text-xs"
title={info.description}
>
{info.icon}
<span className="ml-1 hidden sm:inline">{info.label}</span>
</Button>
);
})}
</div>
</div>
{/* 디자인 영역 */}
<div
ref={containerRef}
2025-11-24 12:02:23 +09:00
className="flex-1 overflow-auto px-4 py-8"
style={{
justifyContent: "center",
alignItems: "flex-start",
display: "flex",
}}
>
<div style={wrapperStyle}>
<div style={screenStyle}>{children}</div>
</div>
</div>
</div>
);
};
export default ResponsiveDesignerContainer;