ContextMenu.tsx 1.9 KB

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