"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"; import { useModalPortal } from "@/lib/modalPortalRef"; import { useTabId } from "@/contexts/TabIdContext"; import { useTabStore } from "@/stores/tabStore"; // Dialog: 탭 시스템 내에서 자동으로 modal={false} + 비활성 탭이면 open={false} 처리 const Dialog: React.FC> = ({ modal, open, ...props }) => { const autoContainer = useModalPortal(); const tabId = useTabId(); const activeTabId = useTabStore((s) => s[s.mode].activeTabId); const isTabActive = !tabId || tabId === activeTabId; const effectiveModal = modal !== undefined ? modal : !autoContainer ? undefined : false; const effectiveOpen = open != null ? open && isTabActive : undefined; return ; }; Dialog.displayName = "Dialog"; const DialogTrigger = DialogPrimitive.Trigger; const DialogPortal = DialogPrimitive.Portal; const DialogClose = DialogPrimitive.Close; const DialogOverlay = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )); DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; interface ScopedDialogContentProps extends React.ComponentPropsWithoutRef { /** 포탈 대상 컨테이너. 명시적으로 전달하면 해당 값 사용, 미전달 시 탭 시스템 자동 감지 */ container?: HTMLElement | null; /** 탭 비활성 시 포탈 내용 숨김 */ hidden?: boolean; } const DialogContent = React.forwardRef< React.ElementRef, ScopedDialogContentProps >(({ className, children, container: explicitContainer, hidden: hiddenProp, onInteractOutside, style, ...props }, ref) => { const autoContainer = useModalPortal(); const container = explicitContainer !== undefined ? explicitContainer : autoContainer; const scoped = !!container; const handleInteractOutside = React.useCallback( (e: any) => { if (scoped && container) { const target = (e.detail?.originalEvent?.target ?? e.target) as HTMLElement | null; if (target && !container.contains(target)) { e.preventDefault(); return; } } onInteractOutside?.(e); }, [scoped, container, onInteractOutside], ); // scoped 모드: 뷰포트 기반 maxHeight/maxWidth 제거 → className의 max-h-full이 컨테이너 기준으로 적용됨 const adjustedStyle = scoped && style ? { ...style, maxHeight: undefined, maxWidth: undefined } : style; return (
{scoped ? (
) : ( )} {children} Close
); }); DialogContent.displayName = DialogPrimitive.Content.displayName; const DialogHeader = ({ className, ...props }: React.HTMLAttributes) => (
); DialogHeader.displayName = "DialogHeader"; const DialogFooter = ({ className, ...props }: React.HTMLAttributes) => (
); DialogFooter.displayName = "DialogFooter"; const DialogTitle = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )); DialogTitle.displayName = DialogPrimitive.Title.displayName; const DialogDescription = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )); DialogDescription.displayName = DialogPrimitive.Description.displayName; export { Dialog, DialogPortal, DialogOverlay, DialogClose, DialogTrigger, DialogContent, DialogHeader, DialogFooter, DialogTitle, DialogDescription, };