"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"; // ๐Ÿ†• Context๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ open ์ƒํƒœ ๊ณต์œ  const ResizableDialogContext = React.createContext<{ open: boolean }>({ open: false }); // ๐Ÿ†• ResizableDialog๋ฅผ ๋ž˜ํ•‘ํ•˜์—ฌ Context ์ œ๊ณต const ResizableDialog: React.FC> = ({ children, open = false, ...props }) => { return ( {children} ); }; 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; // ์‚ฌ์šฉ์ž๋ณ„ ์ €์žฅ์šฉ open?: boolean; // ๐Ÿ†• ๋ชจ๋‹ฌ ์—ด๋ฆผ/๋‹ซํž˜ ์ƒํƒœ (์™ธ๋ถ€์—์„œ ์ „๋‹ฌ) } const ResizableDialogContent = React.forwardRef< React.ElementRef, ResizableDialogContentProps >( ( { className, children, minWidth = 400, minHeight = 300, maxWidth = 1600, maxHeight = 1200, defaultWidth = 600, defaultHeight = 500, modalId, userId = "guest", open: externalOpen, // ๐Ÿ†• ์™ธ๋ถ€์—์„œ ์ „๋‹ฌ๋ฐ›์€ open ์ƒํƒœ style: userStyle, ...props }, ref ) => { const contentRef = React.useRef(null); // ๊ณ ์ •๋œ ID ์ƒ์„ฑ (ํ•œ๋ฒˆ ์ƒ์„ฑ๋˜๋ฉด ์ปดํฌ๋„ŒํŠธ ์ƒ๋ช…์ฃผ๊ธฐ ๋™์•ˆ ์œ ์ง€) const stableIdRef = React.useRef(null); if (!stableIdRef.current) { if (modalId) { stableIdRef.current = modalId; console.log("โœ… ResizableDialog - ๋ช…์‹œ์  modalId ์‚ฌ์šฉ:", 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)}`; console.log("๐Ÿ”„ ResizableDialog - className ๊ธฐ๋ฐ˜ ID ์ƒ์„ฑ:", { className, generatedId: stableIdRef.current, }); } 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)}`; console.log("๐Ÿ”„ ResizableDialog - userStyle ๊ธฐ๋ฐ˜ ID ์ƒ์„ฑ:", { userStyle, generatedId: stableIdRef.current, }); } else { // ๊ธฐ๋ณธ ID stableIdRef.current = 'modal-default'; console.log("โš ๏ธ ResizableDialog - ๊ธฐ๋ณธ ID ์‚ฌ์šฉ (๋ชจ๋“  ๋ชจ๋‹ฌ์ด ๊ฐ™์€ ํฌ๊ธฐ ๊ณต์œ )"); } } } 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); const [lastModalId, setLastModalId] = React.useState(null); const [userResized, setUserResized] = React.useState(false); // ์‚ฌ์šฉ์ž๊ฐ€ ์‹ค์ œ๋กœ ๋ฆฌ์‚ฌ์ด์ง•ํ–ˆ๋Š”์ง€ ์ถ”์  // ๐Ÿ†• Context์—์„œ open ์ƒํƒœ ๊ฐ€์ ธ์˜ค๊ธฐ (์šฐ์„ ์ˆœ์œ„: externalOpen > context.open) const context = React.useContext(ResizableDialogContext); const actualOpen = externalOpen !== undefined ? externalOpen : context.open; // ๐Ÿ†• ๋ชจ๋‹ฌ์ด ๋‹ซํ˜”๋‹ค๊ฐ€ ๋‹ค์‹œ ์—ด๋ฆด ๋•Œ ์ดˆ๊ธฐํ™” ๋ฆฌ์…‹ const [wasOpen, setWasOpen] = React.useState(false); React.useEffect(() => { console.log("๐Ÿ” ๋ชจ๋‹ฌ ์ƒํƒœ ๋ณ€ํ™” ๊ฐ์ง€:", { actualOpen, wasOpen, externalOpen, contextOpen: context.open, effectiveModalId }); if (actualOpen && !wasOpen) { // ๋ชจ๋‹ฌ์ด ๋ฐฉ๊ธˆ ์—ด๋ฆผ console.log("๐Ÿ”“ ๋ชจ๋‹ฌ ์—ด๋ฆผ ๊ฐ์ง€, ์ดˆ๊ธฐํ™” ๋ฆฌ์…‹:", { effectiveModalId }); setIsInitialized(false); setWasOpen(true); } else if (!actualOpen && wasOpen) { // ๋ชจ๋‹ฌ์ด ๋ฐฉ๊ธˆ ๋‹ซํž˜ console.log("๐Ÿ”’ ๋ชจ๋‹ฌ ๋‹ซํž˜ ๊ฐ์ง€:", { effectiveModalId }); setWasOpen(false); } }, [actualOpen, wasOpen, effectiveModalId, externalOpen, context.open]); // modalId๊ฐ€ ๋ณ€๊ฒฝ๋˜๋ฉด ์ดˆ๊ธฐํ™” ๋ฆฌ์…‹ (๋‹ค๋ฅธ ๋ชจ๋‹ฌ์ด ์—ด๋ฆฐ ๊ฒฝ์šฐ) React.useEffect(() => { if (effectiveModalId !== lastModalId) { console.log("๐Ÿ”„ ๋ชจ๋‹ฌ ID ๋ณ€๊ฒฝ ๊ฐ์ง€, ์ดˆ๊ธฐํ™” ๋ฆฌ์…‹:", { ์ด์ „: lastModalId, ํ˜„์žฌ: effectiveModalId, isInitialized, }); setIsInitialized(false); setUserResized(false); // ์‚ฌ์šฉ์ž ๋ฆฌ์‚ฌ์ด์ง• ํ”Œ๋ž˜๊ทธ๋„ ๋ฆฌ์…‹ setLastModalId(effectiveModalId); } }, [effectiveModalId, lastModalId, isInitialized]); // ๋ชจ๋‹ฌ์ด ์—ด๋ฆด ๋•Œ ์ดˆ๊ธฐ ํฌ๊ธฐ ์„ค์ • (localStorage์™€ ๋‚ด์šฉ ํฌ๊ธฐ ์ค‘ ํฐ ๊ฐ’ ์‚ฌ์šฉ) React.useEffect(() => { console.log("๐Ÿ” ์ดˆ๊ธฐ ํฌ๊ธฐ ์„ค์ • useEffect ์‹คํ–‰:", { isInitialized, hasContentRef: !!contentRef.current, effectiveModalId, }); if (!isInitialized) { // ๋‚ด์šฉ์˜ ์‹ค์ œ ํฌ๊ธฐ ์ธก์ • (์•ฝ๊ฐ„์˜ ์ง€์—ฐ ํ›„, contentRef๊ฐ€ ์ค€๋น„๋  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐ) // ์—ฌ๋Ÿฌ ๋ฒˆ ์‹œ๋„ํ•˜์—ฌ contentRef๊ฐ€ ์ค€๋น„๋  ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐ let attempts = 0; const maxAttempts = 10; const measureContent = () => { attempts++; // scrollHeight/scrollWidth๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‹ค์ œ ๋‚ด์šฉ ํฌ๊ธฐ ์ธก์ • (์Šคํฌ๋กค ํฌํ•จ) let contentWidth = defaultWidth; let contentHeight = defaultHeight; if (contentRef.current) { // scrollHeight/scrollWidth ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉ (์—ฌ์œ  ๊ณต๊ฐ„ ์ œ๊ฑฐ) contentWidth = contentRef.current.scrollWidth || defaultWidth; contentHeight = contentRef.current.scrollHeight || defaultHeight; console.log("๐Ÿ“ ๋ชจ๋‹ฌ ๋‚ด์šฉ ํฌ๊ธฐ ์ธก์ •:", { attempt: attempts, scrollWidth: contentRef.current.scrollWidth, scrollHeight: contentRef.current.scrollHeight, clientWidth: contentRef.current.clientWidth, clientHeight: contentRef.current.clientHeight, contentWidth, contentHeight, }); } else { console.log("โš ๏ธ contentRef ์—†์Œ, ์žฌ์‹œ๋„:", { attempt: attempts, maxAttempts, defaultWidth, defaultHeight }); // contentRef๊ฐ€ ์•„์ง ์—†์œผ๋ฉด ์žฌ์‹œ๋„ if (attempts < maxAttempts) { setTimeout(measureContent, 100); return; } } // ํŒจ๋”ฉ ์ถ”๊ฐ€ (p-6 * 2 = 48px) const paddingAndMargin = 48; const initialSize = getInitialSize(); // ๋‚ด์šฉ ํฌ๊ธฐ ๊ธฐ๋ฐ˜ ์ตœ์†Œ ํฌ๊ธฐ ๊ณ„์‚ฐ const contentBasedSize = { width: Math.max(minWidth, Math.min(maxWidth, Math.max(contentWidth + paddingAndMargin, initialSize.width))), height: Math.max(minHeight, Math.min(maxHeight, Math.max(contentHeight + paddingAndMargin, initialSize.height))), }; console.log("๐Ÿ“ ๋‚ด์šฉ ๊ธฐ๋ฐ˜ ํฌ๊ธฐ:", contentBasedSize); // localStorage์—์„œ ์ €์žฅ๋œ ํฌ๊ธฐ ํ™•์ธ let finalSize = contentBasedSize; if (effectiveModalId && typeof window !== 'undefined') { try { const storageKey = `modal_size_${effectiveModalId}_${userId}`; const saved = localStorage.getItem(storageKey); console.log("๐Ÿ“ฆ localStorage ํ™•์ธ:", { effectiveModalId, userId, storageKey, saved: saved ? "์žˆ์Œ" : "์—†์Œ", }); if (saved) { const parsed = JSON.parse(saved); // userResized ํ”Œ๋ž˜๊ทธ ํ™•์ธ if (parsed.userResized) { const savedSize = { width: Math.max(minWidth, Math.min(maxWidth, parsed.width)), height: Math.max(minHeight, Math.min(maxHeight, parsed.height)), }; console.log("๐Ÿ’พ ์‚ฌ์šฉ์ž๊ฐ€ ๋ฆฌ์‚ฌ์ด์ง•ํ•œ ํฌ๊ธฐ ๋ณต์›:", savedSize); // โœ… ์ค‘์š”: ์‚ฌ์šฉ์ž๊ฐ€ ๋ช…์‹œ์ ์œผ๋กœ ๋ฆฌ์‚ฌ์ด์ง•ํ•œ ๊ฒฝ์šฐ, ์‚ฌ์šฉ์ž ํฌ๊ธฐ๋ฅผ ์šฐ์„  ์‚ฌ์šฉ // (์‚ฌ์šฉ์ž๊ฐ€ ์˜๋„์ ์œผ๋กœ ์ž‘๊ฒŒ ๋งŒ๋“  ๊ฒƒ์„ ์กด์ค‘) finalSize = savedSize; setUserResized(true); console.log("โœ… ์ตœ์ข… ํฌ๊ธฐ (์‚ฌ์šฉ์ž๊ฐ€ ์„ค์ •ํ•œ ํฌ๊ธฐ ์šฐ์„  ์ ์šฉ):", { savedSize, contentBasedSize, finalSize, note: "์‚ฌ์šฉ์ž๊ฐ€ ๋ฆฌ์‚ฌ์ด์ง•ํ•œ ํฌ๊ธฐ๋ฅผ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค", }); } else { console.log("โ„น๏ธ ์ž๋™ ๊ณ„์‚ฐ๋œ ํฌ๊ธฐ๋Š” ๋ฌด์‹œ, ๋‚ด์šฉ ํฌ๊ธฐ ์‚ฌ์šฉ"); } } else { console.log("โ„น๏ธ localStorage์— ์ €์žฅ๋œ ํฌ๊ธฐ ์—†์Œ, ๋‚ด์šฉ ํฌ๊ธฐ ์‚ฌ์šฉ"); } } catch (error) { console.error("โŒ ๋ชจ๋‹ฌ ํฌ๊ธฐ ๋ณต์› ์‹คํŒจ:", error); } } setSize(finalSize); setIsInitialized(true); }; // ์ฒซ ์‹œ๋„๋Š” 300ms ํ›„์— ์‹œ์ž‘ setTimeout(measureContent, 300); } }, [isInitialized, getInitialSize, effectiveModalId, userId, minWidth, maxWidth, minHeight, maxHeight, defaultWidth, defaultHeight]); 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); // ์‚ฌ์šฉ์ž๊ฐ€ ๋ฆฌ์‚ฌ์ด์ง•ํ–ˆ์Œ์„ ํ‘œ์‹œ setUserResized(true); // โœ… ์ค‘์š”: ํ˜„์žฌ ์‹ค์ œ DOM ํฌ๊ธฐ๋ฅผ ์ €์žฅ (state๊ฐ€ ์•„๋‹Œ ์‹ค์ œ ํฌ๊ธฐ) if (effectiveModalId && typeof window !== 'undefined' && contentRef.current) { try { const storageKey = `modal_size_${effectiveModalId}_${userId}`; // contentRef์˜ ๋ถ€๋ชจ ์š”์†Œ(๋ชจ๋‹ฌ ์ปจํ…Œ์ด๋„ˆ)์˜ ์‹ค์ œ ํฌ๊ธฐ ์‚ฌ์šฉ const modalElement = contentRef.current.parentElement; const actualWidth = modalElement?.offsetWidth || size.width; const actualHeight = modalElement?.offsetHeight || size.height; const currentSize = { width: actualWidth, height: actualHeight, userResized: true, // ์‚ฌ์šฉ์ž๊ฐ€ ์ง์ ‘ ๋ฆฌ์‚ฌ์ด์ง•ํ–ˆ์Œ์„ ํ‘œ์‹œ }; localStorage.setItem(storageKey, JSON.stringify(currentSize)); console.log("๐Ÿ’พ localStorage์— ํฌ๊ธฐ ์ €์žฅ (์‚ฌ์šฉ์ž ๋ฆฌ์‚ฌ์ด์ง•):", { effectiveModalId, userId, storageKey, size: currentSize, stateSize: { width: size.width, height: size.height }, }); } 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, };