forked from
slices.network/slices
Highly ambitious ATProtocol AppView service and sdks
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}