ERP-node/frontend/components/screen/ResponsiveScreenContainer.tsx

163 lines
5.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, { useState, useRef } from "react";
import { Button } from "@/components/ui/button";
import { Monitor, Smartphone, Maximize2, Minimize2 } from "lucide-react";
import { useContainerSize } from "@/hooks/useViewportSize";
interface ResponsiveScreenContainerProps {
children: React.ReactNode;
designWidth: number;
designHeight: number;
screenName?: string;
}
type ViewMode = "fit" | "scale" | "original" | "fullwidth";
/**
* 반응형 화면 컨테이너
* 다양한 모니터 크기에 맞춰 화면을 자동 조정합니다.
*/
export const ResponsiveScreenContainer: React.FC<ResponsiveScreenContainerProps> = ({
children,
designWidth,
designHeight,
screenName,
}) => {
const containerRef = useRef<HTMLDivElement>(null);
const [viewMode, setViewMode] = useState<ViewMode>("fit");
const containerSize = useContainerSize(containerRef);
// 스케일 계산 (실시간 계산으로 변경)
const calculateScale = (): number => {
if (containerSize.width === 0 || containerSize.height === 0) return 1;
let newScale = 1;
switch (viewMode) {
case "fit":
// 컨테이너에 맞춰 비율 유지하며 조정 (여백 허용)
const scaleX = (containerSize.width - 40) / designWidth; // 20px 여백
const scaleY = (containerSize.height - 40) / designHeight; // 20px 여백
newScale = Math.min(scaleX, scaleY, 1); // 최대 1배까지만
break;
case "scale":
// 컨테이너를 가득 채우도록 조정 (비율 유지)
const fillScaleX = containerSize.width / designWidth;
const fillScaleY = containerSize.height / designHeight;
newScale = Math.min(fillScaleX, fillScaleY);
break;
case "fullwidth":
// 가로폭을 컨테이너에 맞춤 (세로는 비율 유지)
newScale = containerSize.width / designWidth;
break;
case "original":
default:
// 원본 크기 유지
newScale = 1;
break;
}
return Math.max(newScale, 0.1); // 최소 0.1배
};
const scale = calculateScale();
const getViewModeInfo = (mode: ViewMode) => {
switch (mode) {
case "fit":
return {
label: "화면 맞춤",
description: "모니터 크기에 맞춰 비율 유지하며 조정",
icon: <Monitor className="h-4 w-4" />,
};
case "scale":
return {
label: "전체 채움",
description: "화면을 가득 채우도록 조정",
icon: <Maximize2 className="h-4 w-4" />,
};
case "fullwidth":
return {
label: "가로 맞춤",
description: "가로폭을 화면에 맞춤",
icon: <Smartphone className="h-4 w-4" />,
};
case "original":
return {
label: "원본 크기",
description: "설계된 원본 크기로 표시",
icon: <Minimize2 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: viewMode === "original" ? "auto" : "hidden",
};
return (
<div className="flex h-full w-full flex-col bg-gray-50">
{/* 상단 컨트롤 바 */}
<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">
{(["fit", "scale", "fullwidth", "original"] as ViewMode[]).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}
className="flex-1 overflow-auto p-4"
style={{
justifyContent: viewMode === "original" ? "flex-start" : "center",
alignItems: viewMode === "original" ? "flex-start" : "center",
display: "flex",
}}
>
<div style={wrapperStyle}>
<div style={screenStyle}>{children}</div>
</div>
</div>
</div>
);
};
export default ResponsiveScreenContainer;