Files
karma/ui/src/Components/Modal/index.tsx
2026-03-09 15:41:44 +00:00

108 lines
2.7 KiB
TypeScript

import React, { use, FC, useEffect, useRef } from "react";
import ReactDOM from "react-dom";
import { CSSTransition } from "react-transition-group";
import { disableBodyScroll, enableBodyScroll } from "body-scroll-lock";
import { useHotkeys } from "react-hotkeys-hook";
import { ThemeContext } from "Components/Theme";
const ModalInner: FC<{
size: "modal-lg" | "modal-xl";
isUpper: boolean;
toggleOpen: () => void;
children: React.ReactNode;
}> = ({ size, isUpper, toggleOpen, children }) => {
// needed for tests to spy on useRef
const ref = React.useRef<HTMLDivElement | null>(null);
useEffect(() => {
if (ref.current !== null) {
document.body.classList.add("modal-open");
disableBodyScroll(ref.current, { reserveScrollBarGap: true });
const modal = ref.current;
return () => {
if (!isUpper) document.body.classList.remove("modal-open");
enableBodyScroll(modal);
};
}
}, [isUpper]);
useHotkeys("esc", toggleOpen);
return (
<div className="modal-open">
<div ref={ref} className="modal d-block" role="dialog">
<div
className={`modal-dialog ${size} ${
isUpper ? "modal-upper shadow" : ""
}`}
role="document"
>
<div className="modal-content">{children}</div>
</div>
</div>
</div>
);
};
const Modal: FC<{
size?: "modal-lg" | "modal-xl";
isOpen: boolean;
isUpper?: boolean;
toggleOpen: () => void;
onExited?: () => void;
children: React.ReactNode;
}> = ({
size = "modal-lg",
isOpen,
isUpper = false,
toggleOpen,
onExited,
children,
}) => {
const context = use(ThemeContext);
const modalRef = useRef<HTMLDivElement>(null);
const backdropRef = useRef<HTMLDivElement>(null);
return ReactDOM.createPortal(
<>
<CSSTransition
in={isOpen}
classNames={
context.animations.duration ? "components-animation-modal" : ""
}
timeout={context.animations.duration ? 300 : 0}
onExited={onExited}
enter
exit
unmountOnExit
nodeRef={modalRef}
>
<div ref={modalRef}>
<ModalInner size={size} isUpper={isUpper} toggleOpen={toggleOpen}>
{children}
</ModalInner>
</div>
</CSSTransition>
<CSSTransition
in={isOpen && !isUpper}
classNames="components-animation-backdrop"
timeout={context.animations.duration ? 300 : 0}
enter
exit
unmountOnExit
nodeRef={backdropRef}
>
<div ref={backdropRef} className="modal-backdrop d-block" />
</CSSTransition>
</>,
document.body,
);
};
export { Modal, ModalInner };