| 1 | "use client"; |
| 2 | |
| 3 | import { useRef, useEffect } from "react"; |
| 4 | |
| 5 | export interface DialogProps { |
| 6 | open: boolean; |
| 7 | onClose: () => void; |
| 8 | title: string; |
| 9 | children: React.ReactNode; |
| 10 | actions?: React.ReactNode; |
| 11 | className?: string; |
| 12 | } |
| 13 | |
| 14 | export function Dialog({ |
| 15 | open, |
| 16 | onClose, |
| 17 | title, |
| 18 | children, |
| 19 | actions, |
| 20 | className = "", |
| 21 | }: DialogProps) { |
| 22 | const ref = useRef<HTMLDialogElement>(null); |
| 23 | |
| 24 | useEffect(() => { |
| 25 | const dialog = ref.current; |
| 26 | if (!dialog) return; |
| 27 | |
| 28 | if (open && !dialog.open) { |
| 29 | dialog.showModal(); |
| 30 | } else if (!open && dialog.open) { |
| 31 | dialog.close(); |
| 32 | } |
| 33 | }, [open]); |
| 34 | |
| 35 | return ( |
| 36 | <dialog |
| 37 | ref={ref} |
| 38 | onClose={onClose} |
| 39 | onClick={(e) => { |
| 40 | if (e.target === ref.current) onClose(); |
| 41 | }} |
| 42 | className={className} |
| 43 | style={{ |
| 44 | backgroundColor: "var(--bg-card)", |
| 45 | border: "1px solid var(--border-subtle)", |
| 46 | color: "var(--text-primary)", |
| 47 | width: "100%", |
| 48 | maxWidth: "28rem", |
| 49 | margin: "auto", |
| 50 | boxShadow: "0 8px 30px rgba(0, 0, 0, 0.12), 0 2px 8px rgba(0, 0, 0, 0.06)", |
| 51 | overflow: "hidden", |
| 52 | }} |
| 53 | > |
| 54 | <div |
| 55 | className="px-6 pt-6 pb-4" |
| 56 | style={{ borderBottom: "1px solid var(--border-subtle)" }} |
| 57 | > |
| 58 | <h2 className="text-lg">{title}</h2> |
| 59 | </div> |
| 60 | <div className="px-6 py-5">{children}</div> |
| 61 | {actions && ( |
| 62 | <div |
| 63 | className="flex gap-3 px-6 py-4 justify-end" |
| 64 | style={{ |
| 65 | borderTop: "1px solid var(--border-subtle)", |
| 66 | backgroundColor: "var(--bg-inset)", |
| 67 | }} |
| 68 | > |
| 69 | {actions} |
| 70 | </div> |
| 71 | )} |
| 72 | </dialog> |
| 73 | ); |
| 74 | } |
| 75 | |