"use client"; import * as React from "react"; import * as DialogPrimitive from "@radix-ui/react-dialog"; import { X } from "lucide-react"; import { cn } from "@/lib/utils"; const ResizableDialog = DialogPrimitive.Root; const ResizableDialogTrigger = DialogPrimitive.Trigger; const ResizableDialogPortal = DialogPrimitive.Portal; const ResizableDialogClose = DialogPrimitive.Close; const ResizableDialogOverlay = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )); ResizableDialogOverlay.displayName = DialogPrimitive.Overlay.displayName; interface ResizableDialogContentProps extends React.ComponentPropsWithoutRef { minWidth?: number; minHeight?: number; maxWidth?: number; maxHeight?: number; defaultWidth?: number; defaultHeight?: number; modalId?: string; // localStorage 저장용 고유 ID userId?: string; // 사용자별 저장용 } const ResizableDialogContent = React.forwardRef< React.ElementRef, ResizableDialogContentProps >( ( { className, children, minWidth = 400, minHeight = 300, maxWidth = 1400, maxHeight = 900, defaultWidth = 600, defaultHeight = 500, modalId, userId = "guest", style: userStyle, ...props }, ref ) => { const contentRef = React.useRef(null); // 고정된 ID 생성 (한번 생성되면 컴포넌트 생명주기 동안 유지) const stableIdRef = React.useRef(null); if (!stableIdRef.current) { if (modalId) { stableIdRef.current = modalId; } else { // className 기반 ID 생성 if (className) { const hash = className.split('').reduce((acc, char) => { return ((acc << 5) - acc) + char.charCodeAt(0); }, 0); stableIdRef.current = `modal-${Math.abs(hash).toString(36)}`; } else if (userStyle) { // userStyle 기반 ID 생성 const styleStr = JSON.stringify(userStyle); const hash = styleStr.split('').reduce((acc, char) => { return ((acc << 5) - acc) + char.charCodeAt(0); }, 0); stableIdRef.current = `modal-${Math.abs(hash).toString(36)}`; } else { // 기본 ID stableIdRef.current = 'modal-default'; } } } const effectiveModalId = stableIdRef.current; // 실제 렌더링된 크기를 감지하여 초기 크기로 사용 const getInitialSize = React.useCallback(() => { if (typeof window === 'undefined') return { width: defaultWidth, height: defaultHeight }; // 1순위: userStyle에서 크기 추출 (화면관리에서 지정한 크기 - 항상 초기값으로 사용) if (userStyle) { const styleWidth = typeof userStyle.width === 'string' ? parseInt(userStyle.width) : userStyle.width; const styleHeight = typeof userStyle.height === 'string' ? parseInt(userStyle.height) : userStyle.height; if (styleWidth && styleHeight) { return { width: Math.max(minWidth, Math.min(maxWidth, styleWidth)), height: Math.max(minHeight, Math.min(maxHeight, styleHeight)), }; } } // 2순위: 현재 렌더링된 크기 사용 if (contentRef.current) { const rect = contentRef.current.getBoundingClientRect(); if (rect.width > 0 && rect.height > 0) { return { width: Math.max(minWidth, Math.min(maxWidth, rect.width)), height: Math.max(minHeight, Math.min(maxHeight, rect.height)), }; } } // 3순위: defaultWidth/defaultHeight 사용 return { width: defaultWidth, height: defaultHeight }; }, [defaultWidth, defaultHeight, minWidth, minHeight, maxWidth, maxHeight, userStyle]); const [size, setSize] = React.useState(getInitialSize); const [isResizing, setIsResizing] = React.useState(false); const [resizeDirection, setResizeDirection] = React.useState(""); const [isInitialized, setIsInitialized] = React.useState(false); // 모달이 열릴 때 초기 크기 설정 (localStorage 우선, 없으면 화면관리 설정) React.useEffect(() => { if (!isInitialized) { const initialSize = getInitialSize(); // localStorage에서 저장된 크기가 있는지 확인 if (effectiveModalId && typeof window !== 'undefined') { try { const storageKey = `modal_size_${effectiveModalId}_${userId}`; const saved = localStorage.getItem(storageKey); if (saved) { const parsed = JSON.parse(saved); // 저장된 크기가 있으면 그것을 사용 (사용자가 이전에 리사이즈한 크기) const restoredSize = { width: Math.max(minWidth, Math.min(maxWidth, parsed.width || initialSize.width)), height: Math.max(minHeight, Math.min(maxHeight, parsed.height || initialSize.height)), }; setSize(restoredSize); setIsInitialized(true); return; } } catch (error) { console.error("모달 크기 복원 실패:", error); } } // 저장된 크기가 없으면 초기 크기 사용 (화면관리 설정 크기) setSize(initialSize); setIsInitialized(true); } }, [isInitialized, getInitialSize, effectiveModalId, userId, minWidth, maxWidth, minHeight, maxHeight]); const startResize = (direction: string) => (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); setIsResizing(true); setResizeDirection(direction); const startX = e.clientX; const startY = e.clientY; const startWidth = size.width; const startHeight = size.height; const handleMouseMove = (moveEvent: MouseEvent) => { const deltaX = moveEvent.clientX - startX; const deltaY = moveEvent.clientY - startY; let newWidth = startWidth; let newHeight = startHeight; if (direction.includes("e")) { newWidth = Math.max(minWidth, Math.min(maxWidth, startWidth + deltaX)); } if (direction.includes("w")) { newWidth = Math.max(minWidth, Math.min(maxWidth, startWidth - deltaX)); } if (direction.includes("s")) { newHeight = Math.max(minHeight, Math.min(maxHeight, startHeight + deltaY)); } if (direction.includes("n")) { newHeight = Math.max(minHeight, Math.min(maxHeight, startHeight - deltaY)); } setSize({ width: newWidth, height: newHeight }); }; const handleMouseUp = () => { setIsResizing(false); setResizeDirection(""); document.removeEventListener("mousemove", handleMouseMove); document.removeEventListener("mouseup", handleMouseUp); // localStorage에 크기 저장 (리사이즈 후 새로고침해도 유지) if (effectiveModalId && typeof window !== 'undefined') { try { const storageKey = `modal_size_${effectiveModalId}_${userId}`; const currentSize = { width: size.width, height: size.height }; localStorage.setItem(storageKey, JSON.stringify(currentSize)); } catch (error) { console.error("모달 크기 저장 실패:", error); } } }; document.addEventListener("mousemove", handleMouseMove); document.addEventListener("mouseup", handleMouseUp); }; return (
{children}
{/* 리사이즈 핸들 */} {/* 오른쪽 */}
{/* 아래 */}
{/* 오른쪽 아래 */}
{/* 왼쪽 */}
{/* 위 */}
{/* 왼쪽 아래 */}
{/* 오른쪽 위 */}
{/* 왼쪽 위 */}
Close ); } ); ResizableDialogContent.displayName = DialogPrimitive.Content.displayName; const ResizableDialogHeader = ({ className, ...props }: React.HTMLAttributes) => (
); ResizableDialogHeader.displayName = "ResizableDialogHeader"; const ResizableDialogFooter = ({ className, ...props }: React.HTMLAttributes) => (
); ResizableDialogFooter.displayName = "ResizableDialogFooter"; const ResizableDialogTitle = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )); ResizableDialogTitle.displayName = DialogPrimitive.Title.displayName; const ResizableDialogDescription = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )); ResizableDialogDescription.displayName = DialogPrimitive.Description.displayName; export { ResizableDialog, ResizableDialogPortal, ResizableDialogOverlay, ResizableDialogClose, ResizableDialogTrigger, ResizableDialogContent, ResizableDialogHeader, ResizableDialogFooter, ResizableDialogTitle, ResizableDialogDescription, };