♻️(react) migrate Modals to react modal
We encoutered an issue where stacked modal backdrop were not rendered above the modal below. It was caused by the dialog element that is natively rendered on the top layer regardless where it is create in the DOM. So we decided to use react modal that provides hand crafted dialog, and implementing a11y features. Closes #314
This commit is contained in:
@@ -1,10 +1,13 @@
|
||||
import React, { PropsWithChildren, ReactNode, useEffect } from "react";
|
||||
import classNames from "classnames";
|
||||
import { createPortal } from "react-dom";
|
||||
import ReactModal from "react-modal";
|
||||
import { Button } from ":/components/Button";
|
||||
import { NOSCROLL_CLASS } from ":/components/Modal/ModalProvider";
|
||||
|
||||
export type ModalHandle = {};
|
||||
|
||||
export const MODAL_CLASS = "c__modal";
|
||||
|
||||
export enum ModalSize {
|
||||
SMALL = "small",
|
||||
MEDIUM = "medium",
|
||||
@@ -69,91 +72,47 @@ export const Modal = (props: ModalProps) => {
|
||||
};
|
||||
|
||||
export const ModalInner = (props: ModalProps) => {
|
||||
const ref = React.useRef<HTMLDialogElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (props.isOpen) {
|
||||
ref.current?.showModal();
|
||||
} else {
|
||||
ref.current?.close();
|
||||
}
|
||||
}, [props.isOpen]);
|
||||
|
||||
useEffect(() => {
|
||||
ref.current?.addEventListener("close", () => props.onClose(), {
|
||||
once: true,
|
||||
});
|
||||
|
||||
const onClick = (event: MouseEvent) => {
|
||||
const rect = ref.current!.getBoundingClientRect();
|
||||
const isInDialog =
|
||||
rect.top <= event.clientY &&
|
||||
event.clientY <= rect.top + rect.height &&
|
||||
rect.left <= event.clientX &&
|
||||
event.clientX <= rect.left + rect.width;
|
||||
if (!isInDialog) {
|
||||
props.onClose();
|
||||
}
|
||||
};
|
||||
|
||||
if (props.closeOnClickOutside) {
|
||||
ref.current?.addEventListener("click", onClick);
|
||||
}
|
||||
|
||||
const preventClose = (event: Event) => {
|
||||
event.preventDefault();
|
||||
};
|
||||
|
||||
if (props.preventClose) {
|
||||
ref.current?.addEventListener("cancel", preventClose);
|
||||
}
|
||||
|
||||
return () => {
|
||||
ref.current?.removeEventListener("click", onClick);
|
||||
ref.current?.removeEventListener("click", preventClose);
|
||||
};
|
||||
}, [props.isOpen]);
|
||||
const { modalParentSelector } = useModals();
|
||||
|
||||
if (!props.isOpen) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{createPortal(
|
||||
<>
|
||||
<div aria-hidden={true} className="c__modal__backdrop" />
|
||||
<dialog
|
||||
ref={ref}
|
||||
className={classNames("c__modal", "c__modal--" + props.size)}
|
||||
>
|
||||
{!props.hideCloseButton && !props.preventClose && (
|
||||
<div className="c__modal__close">
|
||||
<Button
|
||||
icon={<span className="material-icons">close</span>}
|
||||
color="tertiary-text"
|
||||
size="small"
|
||||
onClick={() => props.onClose()}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{props.titleIcon && (
|
||||
<div className="c__modal__title-icon">{props.titleIcon}</div>
|
||||
)}
|
||||
{props.title && (
|
||||
<div className="c__modal__title">{props.title}</div>
|
||||
)}
|
||||
|
||||
{/* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex */}
|
||||
<div className="c__modal__content" tabIndex={0}>
|
||||
{props.children}
|
||||
</div>
|
||||
<ModalFooter {...props} />
|
||||
</dialog>
|
||||
</>,
|
||||
document.getElementById("c__modals-portal")!,
|
||||
<ReactModal
|
||||
isOpen={props.isOpen}
|
||||
onRequestClose={() => {
|
||||
if (!props.preventClose) {
|
||||
props.onClose();
|
||||
}
|
||||
}}
|
||||
parentSelector={modalParentSelector}
|
||||
overlayClassName="c__modal__backdrop"
|
||||
className={classNames(MODAL_CLASS, `${MODAL_CLASS}--${props.size}`)}
|
||||
shouldCloseOnOverlayClick={!!props.closeOnClickOutside}
|
||||
bodyOpenClassName={classNames("c__modals--opened", NOSCROLL_CLASS)}
|
||||
>
|
||||
{!props.hideCloseButton && !props.preventClose && (
|
||||
<div className="c__modal__close">
|
||||
<Button
|
||||
icon={<span className="material-icons">close</span>}
|
||||
color="tertiary-text"
|
||||
size="small"
|
||||
onClick={() => props.onClose()}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
{props.titleIcon && (
|
||||
<div className="c__modal__title-icon">{props.titleIcon}</div>
|
||||
)}
|
||||
{props.title && <div className="c__modal__title">{props.title}</div>}
|
||||
|
||||
{/* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex */}
|
||||
<div className="c__modal__content" tabIndex={0}>
|
||||
{props.children}
|
||||
</div>
|
||||
<ModalFooter {...props} />
|
||||
</ReactModal>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user