import React, { ReactElement, ReactNode } from "react" import ReactDOM from "react-dom" import { createRoot } from "react-dom/client" import _ from "lodash" export interface ModalPopupComponentProps { children?: ReactNode /** Header of modal. Any react element */ header?: React.ReactNode /** Footer of modal. Any react element */ footer?: React.ReactNode /** Size of modal. Default is "normal" */ size?: "large" | "full" | "normal" | "small" | "x-large" /** True to show close 'x' at top right */ showCloseX?: boolean /** callback function to be called when close is requested */ onClose?: () => void width?: number /** True to vertically center the modal */ verticallyCentered?: boolean } // Modal popup based on react export default class ModalPopupComponent extends React.Component { modalNode: HTMLDivElement /** Render something into a top-level div */ static show = (modalFunc: (close: () => void) => ReactElement, onClose?: () => void) => { // Create temporary div to render into const tempDiv = document.createElement("div") const root = createRoot(tempDiv) // Create close function const close = () => { root.unmount() tempDiv.remove() if (onClose) { return onClose() } } const popupElem = modalFunc(close) root.render(popupElem) } constructor(props: ModalPopupComponentProps) { super(props) // Add special region to body this.modalNode = document.createElement("div") // append is not supported everywhere https://developer.mozilla.org/en-US/docs/Web/API/ParentNode/append#Browser_compatibility if (document.fullscreenElement) { document.fullscreenElement.appendChild(this.modalNode) } else { document.body.appendChild(this.modalNode) } } componentWillUnmount() { this.modalNode.remove() } render() { return <>{ReactDOM.createPortal(, this.modalNode)} } } export interface InnerModalComponentProps { children?: ReactNode /** Header of modal. Any react element */ header?: any /** Footer of modal. Any react element */ footer?: any /** "large" for large, "full" for full-width */ size?: "large" | "full" | "normal" | "small" | "x-large" /** True to show close 'x' at top right */ showCloseX?: boolean /** callback function to be called when close is requested */ onClose?: any width?: number /** True to vertically center the modal */ verticallyCentered?: boolean } // Content must be rendered at body level to prevent weird behaviour, so this is the inner component class InnerModalComponent extends React.Component { render() { let dialogStyle let dialogClass = "modal-dialog" if (this.props.size === "large") { dialogClass += " modal-lg" } if (this.props.size === "small") { dialogClass += " modal-sm" } else if (this.props.size === "x-large") { dialogClass += " modal-xl" } else if (this.props.size === "full") { dialogStyle = { maxWidth: "95%" } } if (this.props.verticallyCentered) { dialogClass += " modal-dialog-centered" } if (this.props.width) { dialogStyle = { width: this.props.width } } const rootStyle = { display: "block" } const overlayStyle: React.CSSProperties = { position: "absolute", left: 0, right: 0, top: 0, bottom: 0, backgroundColor: "rgba(0, 0, 0, 0.7)" } return (
{this.props.header ? (
{this.props.header}
{this.props.showCloseX ? (
) : undefined}
{this.props.children}
{this.props.footer ?
{this.props.footer}
: undefined} {!this.props.header && this.props.showCloseX ? (
) } }