61 lines
1.6 KiB
TypeScript
61 lines
1.6 KiB
TypeScript
import { ReactNode, useEffect } from "react";
|
|
import { createPortal } from "react-dom";
|
|
|
|
type DialogProps = {
|
|
open: boolean;
|
|
title: string;
|
|
description?: string;
|
|
children: ReactNode;
|
|
onClose: () => void;
|
|
footer?: ReactNode;
|
|
size?: "sm" | "lg" | "xl";
|
|
loading?: boolean;
|
|
};
|
|
|
|
const sizeClass = {
|
|
sm: "modal-dialog modal-sm",
|
|
lg: "modal-dialog modal-lg",
|
|
xl: "modal-dialog modal-xl"
|
|
};
|
|
|
|
export default function Dialog({
|
|
open,
|
|
title,
|
|
description,
|
|
children,
|
|
onClose,
|
|
footer,
|
|
size = "sm",
|
|
loading
|
|
}: DialogProps) {
|
|
useEffect(() => {
|
|
if (!open) return;
|
|
const onEsc = (event: KeyboardEvent) => {
|
|
if (event.key === "Escape") onClose();
|
|
};
|
|
window.addEventListener("keydown", onEsc);
|
|
return () => window.removeEventListener("keydown", onEsc);
|
|
}, [open, onClose]);
|
|
|
|
if (!open) return null;
|
|
|
|
return createPortal(
|
|
<div className="modal d-block" style={{ background: "rgba(0,0,0,.4)" }}>
|
|
<div className={sizeClass[size]} style={{ marginTop: "6rem" }}>
|
|
<div className="modal-content">
|
|
<div className="modal-header">
|
|
<h5 className="modal-title">{title}</h5>
|
|
<button className="btn-close" onClick={onClose} aria-label="close" />
|
|
</div>
|
|
<div className="modal-body">
|
|
{description && <p className="text-muted mb-3">{description}</p>}
|
|
{loading ? <div className="text-center text-muted">Loading...</div> : children}
|
|
</div>
|
|
{footer && <div className="modal-footer">{footer}</div>}
|
|
</div>
|
|
</div>
|
|
</div>,
|
|
document.body
|
|
);
|
|
}
|