"use client"; import * as DialogPrimitive from "@radix-ui/react-dialog"; import { X } from "lucide-react"; import { type ComponentPropsWithoutRef, createContext, type Dispatch, type ElementRef, forwardRef, type HTMLAttributes, type PointerEvent, type ReactNode, type SetStateAction, useContext, useEffect, useRef, useState, } from "react"; import { cn } from "~/lib/utils"; interface DialogContextValue { innerOpen: boolean; setInnerOpen: Dispatch>; } const DialogContext = createContext(undefined); function Dialog({ children }: { children: ReactNode }) { const [outerOpen, setOuterOpen] = useState(false); const [innerOpen, setInnerOpen] = useState(false); return ( {children} ); } const DialogTrigger = DialogPrimitive.Trigger; const DialogPortal = DialogPrimitive.Portal; const DialogClose = DialogPrimitive.Close; const DialogOverlay = forwardRef< ElementRef, ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )); DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; const DialogContent = forwardRef< ElementRef, ComponentPropsWithoutRef >(({ className, children, ...props }, ref) => { const context = useContext(DialogContext); if (!context) throw new Error("DialogContent must be used within a Dialog"); return ( {children} Close ); }); DialogContent.displayName = DialogPrimitive.Content.displayName; function InnerDialog({ children }: { children: ReactNode }) { const context = useContext(DialogContext); if (!context) throw new Error("InnerDialog must be used within a Dialog"); useEffect(() => { const handleEscapeKeyDown = (event: KeyboardEvent) => { if (event.key === "Escape" && context.innerOpen) { context.setInnerOpen(false); event.stopPropagation(); } }; document.addEventListener("keydown", handleEscapeKeyDown); return () => { document.removeEventListener("keydown", handleEscapeKeyDown); }; }, [context.innerOpen, context.setInnerOpen]); return ( {children} ); } const InnerDialogTrigger = DialogPrimitive.Trigger; const InnerDialogClose = DialogPrimitive.Close; interface InnerDialogContentProps extends ComponentPropsWithoutRef { position?: "default" | "bottom" | "top" | "left" | "right"; draggable?: boolean; } const InnerDialogContent = forwardRef< ElementRef, InnerDialogContentProps >( ( { className, children, position = "default", draggable = false, ...props }, ref, ) => { const context = useContext(DialogContext); if (!context) throw new Error("InnerDialogContent must be used within a Dialog"); const [isDragging, setIsDragging] = useState(false); const [startY, setStartY] = useState(0); const [currentY, setCurrentY] = useState(0); const [isClosingByDrag, setIsClosingByDrag] = useState(false); const contentRef = useRef(null); useEffect(() => { if (context.innerOpen) { setCurrentY(0); setIsClosingByDrag(false); } }, [context.innerOpen]); const handlePointerDown = (e: PointerEvent) => { if (!draggable) return; setIsDragging(true); setStartY(e.clientY - currentY); e.currentTarget.setPointerCapture(e.pointerId); }; const handlePointerMove = (e: PointerEvent) => { if (!isDragging || !draggable) return; const newY = e.clientY - startY; setCurrentY(newY > 0 ? newY : 0); }; const handlePointerUp = () => { if (!draggable) return; setIsDragging(false); if (currentY > (contentRef.current?.offsetHeight || 0) / 2) { setIsClosingByDrag(true); context.setInnerOpen(false); } else { setCurrentY(0); } }; return (
{children}
Close
); }, ); InnerDialogContent.displayName = "InnerDialogContent"; const InnerDialogHeader = ({ className, ...props }: HTMLAttributes) => (
); InnerDialogHeader.displayName = "InnerDialogHeader"; const InnerDialogFooter = ({ className, ...props }: HTMLAttributes) => (
); InnerDialogFooter.displayName = "InnerDialogFooter"; const InnerDialogTitle = forwardRef< ElementRef, ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )); InnerDialogTitle.displayName = "InnerDialogTitle"; const InnerDialogDescription = forwardRef< ElementRef, ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )); InnerDialogDescription.displayName = "InnerDialogDescription"; const DialogHeader = ({ className, ...props }: HTMLAttributes) => (
); DialogHeader.displayName = "DialogHeader"; const DialogFooter = ({ className, ...props }: HTMLAttributes) => (
); DialogFooter.displayName = "DialogFooter"; const DialogTitle = forwardRef< ElementRef, ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )); DialogTitle.displayName = DialogPrimitive.Title.displayName; const DialogDescription = forwardRef< ElementRef, ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( )); DialogDescription.displayName = DialogPrimitive.Description.displayName; export type { InnerDialogContentProps }; export { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogFooter, DialogTitle, DialogDescription, DialogClose, InnerDialog, InnerDialogTrigger, InnerDialogContent, InnerDialogHeader, InnerDialogFooter, InnerDialogTitle, InnerDialogDescription, InnerDialogClose, DialogPortal, DialogOverlay, };