File size: 1,734 Bytes
9308228
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import { useEffect } from "react";
import { createPortal } from "react-dom";
import "./Modal.css";

/**
 * Shared foundation for both centered dialogs and side drawers.
 * variant: "dialog" | "drawer"
 */
export function Modal({
    open,
    onClose,
    title,
    children,
    footer,
    variant = "dialog",
    width = 480,
}) {
    useEffect(() => {
        if (!open) return;
        const onKeyDown = (e) => {
            if (e.key === "Escape") onClose?.();
        };
        window.addEventListener("keydown", onKeyDown);
        return () => window.removeEventListener("keydown", onKeyDown);
    }, [open, onClose]);

    if (!open) return null;

    return createPortal(
        <div className="dw-modal-backdrop" onMouseDown={onClose}>
            <div
                className={`dw-modal dw-modal--${variant}`}
                style={variant === "dialog" ? { width } : undefined}
                onMouseDown={(e) => e.stopPropagation()}
                role="dialog"
                aria-modal="true"
            >
                {title && (
                    <div className="dw-modal__header">
                        <h3>{title}</h3>
                        <button className="dw-modal__close" onClick={onClose} aria-label="Close">
                            ×
                        </button>
                    </div>
                )}
                <div className="dw-modal__body">{children}</div>
                {footer && <div className="dw-modal__footer">{footer}</div>}
            </div>
        </div>,
        document.body
    );
}

/** Convenience wrapper — same foundation, right-side drawer defaults. */
export function Drawer(props) {
    return <Modal variant="drawer" {...props} />;
}