Modal.tsx 1.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
  1. import "./Modal.scss";
  2. import React, { useState, useLayoutEffect } 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. if (!modalRoot) {
  14. return null;
  15. }
  16. const handleKeydown = (event: React.KeyboardEvent) => {
  17. if (event.key === KEYS.ESCAPE) {
  18. event.nativeEvent.stopImmediatePropagation();
  19. props.onCloseRequest();
  20. }
  21. };
  22. return createPortal(
  23. <div
  24. className={`Modal ${props.className ?? ""}`}
  25. role="dialog"
  26. aria-modal="true"
  27. onKeyDown={handleKeydown}
  28. aria-labelledby={props.labelledBy}
  29. >
  30. <div className="Modal__background" onClick={props.onCloseRequest}></div>
  31. <div
  32. className="Modal__content"
  33. style={
  34. {
  35. "--max-width": `${props.maxWidth}px`,
  36. maxHeight: "100%",
  37. overflowY: "scroll",
  38. } as any
  39. }
  40. >
  41. {props.children}
  42. </div>
  43. </div>,
  44. modalRoot,
  45. );
  46. };
  47. const useBodyRoot = () => {
  48. const [div, setDiv] = useState<HTMLDivElement | null>(null);
  49. useLayoutEffect(() => {
  50. const isDarkTheme = !!document
  51. .querySelector(".excalidraw")
  52. ?.classList.contains("Appearance_dark");
  53. const div = document.createElement("div");
  54. div.classList.add("excalidraw");
  55. if (isDarkTheme) {
  56. div.classList.add("Appearance_dark");
  57. div.classList.add("Appearance_dark-background-none");
  58. }
  59. document.body.appendChild(div);
  60. setDiv(div);
  61. return () => {
  62. document.body.removeChild(div);
  63. };
  64. }, []);
  65. return div;
  66. };