1import React, {useCallback, useEffect, useRef, useState} from "react";
2import {createPortal} from "react-dom";
3import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
4import {faClose} from "@fortawesome/free-solid-svg-icons";
5import "./styles.sass";
6
7export const Dialog = ({open, onClose, className, disableClose, children}) => {
8 const areaRef = useRef();
9 const dialogRef = useRef();
10 const [visible, setVisible] = useState(false);
11 const isClosingRef = useRef(false);
12
13 useEffect(() => {
14 if (open && !visible) {
15 setVisible(true);
16 isClosingRef.current = false;
17 } else if (!open && visible && !isClosingRef.current) {
18 isClosingRef.current = true;
19 areaRef.current?.classList.add("dialog-area-hidden");
20 dialogRef.current?.classList.add("dialog-hidden");
21 }
22 }, [open, visible]);
23
24 const handleClose = useCallback(() => {
25 if (disableClose || isClosingRef.current) return;
26 isClosingRef.current = true;
27 areaRef.current?.classList.add("dialog-area-hidden");
28 dialogRef.current?.classList.add("dialog-hidden");
29 }, [disableClose]);
30
31 const forceClose = useCallback(() => {
32 if (isClosingRef.current) return;
33 isClosingRef.current = true;
34 areaRef.current?.classList.add("dialog-area-hidden");
35 dialogRef.current?.classList.add("dialog-hidden");
36 }, []);
37
38 const handleAnimationEnd = (e) => {
39 if (e.animationName === "fadeOut") {
40 setVisible(false);
41 isClosingRef.current = false;
42 onClose?.();
43 }
44 };
45
46 const handleBackdropClick = (e) => {
47 if (e.target === areaRef.current) handleClose();
48 };
49
50 useEffect(() => {
51 if (!visible) return;
52 const handleKeyDown = (e) => {
53 if (e.key === "Escape" && !disableClose) {
54 e.preventDefault();
55 handleClose();
56 }
57 };
58 document.addEventListener("keydown", handleKeyDown);
59 return () => document.removeEventListener("keydown", handleKeyDown);
60 }, [visible, disableClose, handleClose]);
61
62 if (!visible) return null;
63
64 return createPortal(
65 <div className="dialog-area" ref={areaRef} onClick={handleBackdropClick}>
66 <div className={`dialog${className ? ` ${className}` : ""}`} ref={dialogRef}
67 onAnimationEnd={handleAnimationEnd}>
68 {typeof children === "function" ? children({close: handleClose, forceClose}) : children}
69 </div>
70 </div>,
71 document.body
72 );
73};
74
75export const DialogHeader = ({children, onClose, disableClose}) => (
76 <div className="dialog-header">
77 <h4 className="dialog-text">{children}</h4>
78 {!disableClose && <FontAwesomeIcon icon={faClose} className="dialog-text dialog-icon" onClick={onClose}/>}
79 </div>
80);
81
82export const DialogBody = ({children}) => <div className="dialog-main">{children}</div>;
83
84export const DialogFooter = ({children}) => <div className="dialog-buttons">{children}</div>;
85