Modal.tsx 1.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
  1. import "./Modal.scss";
  2. import React, { useEffect, useState } from "react";
  3. import { createPortal } from "react-dom";
  4. import { KEYS } from "../keys";
  5. export const Modal = (props: {
  6. className?: string;
  7. children: React.ReactNode;
  8. maxWidth?: number;
  9. onCloseRequest(): void;
  10. labelledBy: string;
  11. }) => {
  12. const modalRoot = useBodyRoot();
  13. const handleKeydown = (event: React.KeyboardEvent) => {
  14. if (event.key === KEYS.ESCAPE) {
  15. event.nativeEvent.stopImmediatePropagation();
  16. props.onCloseRequest();
  17. }
  18. };
  19. return createPortal(
  20. <div
  21. className={`Modal ${props.className ?? ""}`}
  22. role="dialog"
  23. aria-modal="true"
  24. onKeyDown={handleKeydown}
  25. aria-labelledby={props.labelledBy}
  26. >
  27. <div className="Modal__background" onClick={props.onCloseRequest}></div>
  28. <div
  29. className="Modal__content"
  30. style={
  31. {
  32. "--max-width": `${props.maxWidth}px`,
  33. maxHeight: "100%",
  34. overflowY: "scroll",
  35. } as any
  36. }
  37. >
  38. {props.children}
  39. </div>
  40. </div>,
  41. modalRoot,
  42. );
  43. };
  44. const useBodyRoot = () => {
  45. const createDiv = () => {
  46. const div = document.createElement("div");
  47. document.body.appendChild(div);
  48. return div;
  49. };
  50. const [div] = useState(createDiv);
  51. useEffect(() => {
  52. return () => {
  53. document.body.removeChild(div);
  54. };
  55. }, [div]);
  56. return div;
  57. };