import React, { useEffect, useRef, useState } from 'react' import { createPortal } from 'react-dom' import styles from './_modal.module.scss' import Button from '../Button/Button' import { type ConfirmationPopoverContentProps } from '../ConfirmationPopover/ConfirmationPopoverContent' import FormFooter from '../Form/FormFooter' import Icon from '../Icons/Icon' import isClient from '../../services/isClient' import { SideDrawer } from '../SideDrawer/SideDrawer' import { type SideDrawerProps } from '../SideDrawer/SideDrawer' import { useMediaQuery } from '../../hooks/responsiveHooks' import BackgroundOverlay from '../BackgroundOverlay/BackgroundOverlay' import { c } from '../../translations/LibraryTranslationService' type BaseModalProps = Omit & { size?: 'sm' | 'md' | 'lg' | 'xl' /** Optional z-index value for the modal container. Useful for controlling stacking order when multiple modals or overlays are present. Defaults to 9000. */ zIndex?: number } type WithConfirmation = BaseModalProps & { /** Optionally hide the close icon in the header */ hideCloseIcon?: never /** Optionally disable the outside click "close" functionality */ disableOutsideClick?: never /** The content to display in the confirmation popover */ confirmation: Omit & { close?: () => void } /** Optionally hide the header. Defaults to false */ hideHeader?: never } type WithoutConfirmation = BaseModalProps & { confirmation?: never /** Optionally hide the close icon in the header */ hideCloseIcon?: boolean /** Optionally disable the outside click "close" functionality */ disableOutsideClick?: boolean /** Optionally hide the header. Defaults to false */ hideHeader?: boolean } export type ModalProps = WithConfirmation | WithoutConfirmation const ConfirmationModal = ({ header, body, cancelCallout, confirmCallout, confirmButtonText, cancelButtonText, type = 'black', close, }: Omit & { type?: 'red' | 'blue' | 'green' | 'black' } & { close?: () => void }): React.JSX.Element => { let buttonColor: 'primary' | 'primary-blue' = 'primary' switch (type) { // We want to use the primary blue button for green and blue. Eventually, we will have only one button color for "primary" and clean this up. case 'green': case 'blue': buttonColor = 'primary-blue' break default: break } return ( <>
{header}
{body}
{ cancelCallout?.() close?.() }, children: cancelButtonText ?? c('cancel'), styleType: 'secondary', }, } : {})} saveButtonProps={{ onClick: () => { confirmCallout() close?.() }, children: confirmButtonText ?? c('confirm'), styleType: buttonColor, destructive: type === 'red', }} />
) } const Modal = (props: ModalProps): React.JSX.Element => { const { children, isOpen, closeCallout, headerContent, footerContent, hideCloseIcon = false, disableOutsideClick = false, confirmation, hideHeader = false, noContentPadding, size = 'md', fullWidth = false, qaTestId = 'modal', zIndex = 9000, } = props const isMobileView = useMediaQuery({ type: 'max', breakpoint: 'md' }) const isMobileOpen = isMobileView && isOpen const isDesktopOpen = !isMobileView && isOpen const [isTransitioning, setIsTransitioning] = useState(false) const timeoutRef = useRef>(undefined) const close = () => { setIsTransitioning(true) timeoutRef.current = setTimeout(() => { closeCallout() setIsTransitioning(false) }, 250) } const determineClose = () => { if (disableOutsideClick || confirmation) { return } close() } useEffect(() => { return () => { clearTimeout(timeoutRef.current) } }, []) const sideDrawerProps = { ...props, isOpen: isMobileOpen, } as SideDrawerProps return isClient ? ( <> {isDesktopOpen && createPortal(
{confirmation ? ( ) : ( <>
{!hideHeader && (
{headerContent}
{!hideCloseIcon && ( )}
)}
{typeof children !== 'function' ? children : null}
{footerContent ? (
{typeof footerContent === 'function' ? footerContent({ close, }) : footerContent}
) : null} )}
, document.body, )} ) : ( <> ) } export default Modal