Highly ambitious ATProtocol AppView service and sdks
at fix-postgres 108 lines 2.6 kB view raw
1import { useEffect, useRef } from "react"; 2import { cn } from "../utils/cn.ts"; 3 4interface DialogProps { 5 open: boolean; 6 onClose: () => void; 7 title: string; 8 children: React.ReactNode; 9 className?: string; 10 maxWidth?: "sm" | "md" | "lg" | "xl" | "2xl"; 11} 12 13export function Dialog({ 14 open, 15 onClose, 16 title, 17 children, 18 className, 19 maxWidth = "md", 20}: DialogProps) { 21 const dialogRef = useRef<HTMLDialogElement>(null); 22 23 const maxWidthClasses = { 24 sm: "max-w-sm", 25 md: "max-w-md", 26 lg: "max-w-lg", 27 xl: "max-w-xl", 28 "2xl": "max-w-2xl", 29 }; 30 31 useEffect(() => { 32 const dialog = dialogRef.current; 33 if (!dialog) return; 34 35 if (open) { 36 dialog.showModal(); 37 } else { 38 dialog.close(); 39 } 40 }, [open]); 41 42 useEffect(() => { 43 const dialog = dialogRef.current; 44 if (!dialog) return; 45 46 const handleCancel = (e: Event) => { 47 e.preventDefault(); 48 onClose(); 49 }; 50 51 dialog.addEventListener("cancel", handleCancel); 52 return () => dialog.removeEventListener("cancel", handleCancel); 53 }, [onClose]); 54 55 const handleBackdropClick = (e: React.MouseEvent<HTMLDialogElement>) => { 56 const dialog = dialogRef.current; 57 if (!dialog) return; 58 59 const rect = dialog.getBoundingClientRect(); 60 const isInDialog = 61 rect.top <= e.clientY && 62 e.clientY <= rect.top + rect.height && 63 rect.left <= e.clientX && 64 e.clientX <= rect.left + rect.width; 65 66 if (!isInDialog) { 67 onClose(); 68 } 69 }; 70 71 return ( 72 <dialog 73 ref={dialogRef} 74 onClick={handleBackdropClick} 75 className={cn( 76 "fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 bg-zinc-900 border border-zinc-800 rounded-lg p-0 backdrop:bg-black/50 w-full m-0", 77 maxWidthClasses[maxWidth] 78 )} 79 > 80 <div className={cn("p-6", className)}> 81 <div className="flex items-center justify-between mb-6"> 82 <h2 className="text-lg font-medium text-zinc-200">{title}</h2> 83 <button 84 type="button" 85 onClick={onClose} 86 className="text-zinc-500 hover:text-zinc-300 transition-colors" 87 aria-label="Close dialog" 88 > 89 <svg 90 className="w-5 h-5" 91 fill="none" 92 stroke="currentColor" 93 viewBox="0 0 24 24" 94 > 95 <path 96 strokeLinecap="round" 97 strokeLinejoin="round" 98 strokeWidth={2} 99 d="M6 18L18 6M6 6l12 12" 100 /> 101 </svg> 102 </button> 103 </div> 104 {children} 105 </div> 106 </dialog> 107 ); 108}