"use client"; import React, { useEffect, useMemo, useState } from "react"; import useBodyScrollLock from "../../hooks/use-body-scroll-lock"; import { cx } from "../../utils"; import { Button } from "../button"; import { MaterialIcon } from "../material-icon"; import { Animation, ModalProps, Shape, Size } from "./types"; import ReactModal from "react-modal"; import { Transition } from "react-transition-group"; const Modal: React.FC = ({ size = "md", shape = "default", animation = "popper", centered, title, isOpen, children, bodyStyle, className, bodyClassName, portalClassName, overlayClassName, closeButtonClassName, closeWrapperClassName, onRequestClose, parentSelector, hideScrollOnIsOpenFalse = true, hideCloseButton = false, ...modalProps }: ModalProps) => { // Use the custom hook to lock/unlock the body scroll when the modal is open/closed. useBodyScrollLock(isOpen, hideScrollOnIsOpenFalse); // Animation cannot run at the same time as the modal opening const [isTransitioning, setIsTransitioning] = useState(false); useEffect(() => { // Run animation one frame after rendering the modal setIsTransitioning(isOpen); }, [isOpen]); const parent = useMemo(() => { /* istanbul ignore next */ if (typeof document === "undefined") return null; let parent = document.querySelector("main") || document.body; if (parentSelector) parent = document.querySelector(parentSelector) || document.body; return parent; }, [parentSelector]); if (!parent) return null; // Don't render anything when modal is closed if (!isOpen) { return null; } const getSizeClasses = (size: Size) => { const sizeMap = { xl: "inset-[5%] min-h-fit", lg: "inset-[10%] min-h-fit", md: "inset-[15%] min-h-fit", sm: "top-[18%] w-[640px]", xs: "w-[calc(100%-30px)]", }; return sizeMap[size]; }; const getAnimationClasses = (animation: Animation, state: string) => { if (animation === "popper") { const baseClasses = "transition-transform duration-300"; if (state === "entering" || state === "entered") { return `${baseClasses} opacity-100 ${state === "entered" ? "translate-y-0 pointer-events-auto" : ""}`; } if (state === "exiting" || state === "exited") { return `${baseClasses} opacity-0 translate-y-[-18px] duration-[180ms]`; } return `${baseClasses} opacity-0 translate-y-[-18px]`; } if (animation === "bottomSheet") { const baseClasses = "transition-transform duration-200 ease-in-out"; if (state === "entering" || state === "entered") { return `${baseClasses} opacity-100 ${state === "entered" ? "translate-y-0 pointer-events-auto" : ""}`; } if (state === "exiting" || state === "exited") { return `${baseClasses} opacity-0 translate-y-full duration-[180ms]`; } return `${baseClasses} opacity-0 translate-y-full`; } return ""; }; return ( {(state: string) => ( parent} className={cx( "absolute min-w-[150px] max-w-[calc(100vw-30px)] overflow-auto rounded-lg bg-white outline-none", getSizeClasses(size), shape === "rounded" && "rounded-3xl", centered && "top-0 translate-y-[calc(50vh-50%)]", getAnimationClasses(animation, state), className )} portalClassName={cx("z-80", portalClassName)} overlayClassName={cx( "z-80 fixed inset-0 bg-black/50 flex items-center justify-center pb-[300px]", overlayClassName )} {...modalProps} > {!hideCloseButton && (
)} {title ? (
{title}
) : null}
{children}
)}
); }; export { Modal }; Modal.displayName = "Modal"; export type { ModalProps, Size, Shape, Animation };