Modal.tsx 1.7 KB

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