ContextMenu.tsx 1.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485
  1. import React from "react";
  2. import { Popover } from "./Popover";
  3. import { render, unmountComponentAtNode } from "react-dom";
  4. import "./ContextMenu.scss";
  5. type ContextMenuOption = {
  6. label: string;
  7. action(): void;
  8. };
  9. type Props = {
  10. options: ContextMenuOption[];
  11. onCloseRequest?(): void;
  12. top: number;
  13. left: number;
  14. };
  15. const ContextMenu = ({ options, onCloseRequest, top, left }: Props) => (
  16. <Popover
  17. onCloseRequest={onCloseRequest}
  18. top={top}
  19. left={left}
  20. fitInViewport={true}
  21. >
  22. <ul
  23. className="context-menu"
  24. onContextMenu={(event) => event.preventDefault()}
  25. >
  26. {options.map((option, idx) => (
  27. <li key={idx} onClick={onCloseRequest}>
  28. <ContextMenuOption {...option} />
  29. </li>
  30. ))}
  31. </ul>
  32. </Popover>
  33. );
  34. const ContextMenuOption = ({ label, action }: ContextMenuOption) => (
  35. <button className="context-menu-option" onClick={action}>
  36. {label}
  37. </button>
  38. );
  39. let contextMenuNode: HTMLDivElement;
  40. const getContextMenuNode = (): HTMLDivElement => {
  41. if (contextMenuNode) {
  42. return contextMenuNode;
  43. }
  44. const div = document.createElement("div");
  45. document.body.appendChild(div);
  46. return (contextMenuNode = div);
  47. };
  48. type ContextMenuParams = {
  49. options: (ContextMenuOption | false | null | undefined)[];
  50. top: number;
  51. left: number;
  52. };
  53. const handleClose = () => {
  54. unmountComponentAtNode(getContextMenuNode());
  55. };
  56. export default {
  57. push(params: ContextMenuParams) {
  58. const options = Array.of<ContextMenuOption>();
  59. params.options.forEach((option) => {
  60. if (option) {
  61. options.push(option);
  62. }
  63. });
  64. if (options.length) {
  65. render(
  66. <ContextMenu
  67. top={params.top}
  68. left={params.left}
  69. options={options}
  70. onCloseRequest={handleClose}
  71. />,
  72. getContextMenuNode(),
  73. );
  74. }
  75. },
  76. };