ContextMenu.tsx 1.8 KB

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