Source

components/Modal/index.jsx

import { useRef } from "react";
import PropTypes from "prop-types";

import { useClickOutside } from "../../hooks/useClickOutside";
import { useKeypress } from "../../hooks/useKeypress";
import { useTrapFocus } from "../../hooks/useTrapFocus";

import "./styles.scss";

/**
 * Modal component that can be opened and closed by clicking outside of the modal or the close button.
 *
 * @category Components
 * @component
 * @returns {React.Component} - The modal component.
 */
function Modal({
	id,
	modalContent,
	isOpenStateInParent,
	onClose,
	modalClassName = "modal",
	modalBackgroundClassName = "modal-background",
	addCloseButton = true,
	closeButtonClassName = "close-modal",
	closeButtonText = "Close",
	...props
}) {
	// UseRef hook to create a ref for the modal.
	// The useEffect hook is then used to add an event listener to the document.
	// The event listener checks if the user clicked outside of the modal.
	const ref = useRef();
	useClickOutside(ref, isOpenStateInParent, () => onClose(false));
	useKeypress("Escape", isOpenStateInParent, () => onClose(false));
	useTrapFocus(ref, isOpenStateInParent);

	const handleClose = () => {
		if (onClose) {
			onClose();
		}
	};

	return (
		<>
			{isOpenStateInParent && (
				<>
					<div id={id + "-modal-background"} className={modalBackgroundClassName}></div>
					<div id={id + "-modal"} ref={ref} className={modalClassName} {...props} role={"dialog"} aria-modal="true">
						{modalContent}
						{addCloseButton && (
							<button id={id + "-modal-button"} className={closeButtonClassName} type="button" onClick={handleClose} aria-label="Close Modal">
								{closeButtonText}
							</button>
						)}
					</div>
				</>
			)}
		</>
	);
}

Modal.propTypes = {
	/** The id of the modal. */
	id: PropTypes.string.isRequired,

	/** The content of the modal. */
	modalContent: PropTypes.node.isRequired,

	/** A boolean that determines whether or not the modal is open. */
	isOpenStateInParent: PropTypes.bool.isRequired,

	/** A function that is called when the modal is closed. */
	onClose: PropTypes.func.isRequired,

	/** The class name of the modal. */
	modalClassName: PropTypes.string,

	/** The class name of the modal background. */
	modalBackgroundClassName: PropTypes.string,

	/** Determines whether or not to add a close button to the modal. */
	addCloseButton: PropTypes.bool,

	/** The class name of the close button. */
	closeButtonClassName: PropTypes.string,

	/** The text of the close button. */
	closeButtonText: PropTypes.node,
};

export default Modal;