aboutsummaryrefslogtreecommitdiffstats
path: root/packages/excalidraw/components/dropdownMenu/DropdownMenuContent.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'packages/excalidraw/components/dropdownMenu/DropdownMenuContent.tsx')
-rw-r--r--packages/excalidraw/components/dropdownMenu/DropdownMenuContent.tsx88
1 files changed, 88 insertions, 0 deletions
diff --git a/packages/excalidraw/components/dropdownMenu/DropdownMenuContent.tsx b/packages/excalidraw/components/dropdownMenu/DropdownMenuContent.tsx
new file mode 100644
index 0000000..a203124
--- /dev/null
+++ b/packages/excalidraw/components/dropdownMenu/DropdownMenuContent.tsx
@@ -0,0 +1,88 @@
+import { Island } from "../Island";
+import { useDevice } from "../App";
+import clsx from "clsx";
+import Stack from "../Stack";
+import React, { useEffect, useRef } from "react";
+import { DropdownMenuContentPropsContext } from "./common";
+import { useOutsideClick } from "../../hooks/useOutsideClick";
+import { KEYS } from "../../keys";
+import { EVENT } from "../../constants";
+import { useStable } from "../../hooks/useStable";
+
+const MenuContent = ({
+ children,
+ onClickOutside,
+ className = "",
+ onSelect,
+ style,
+}: {
+ children?: React.ReactNode;
+ onClickOutside?: () => void;
+ className?: string;
+ /**
+ * Called when any menu item is selected (clicked on).
+ */
+ onSelect?: (event: Event) => void;
+ style?: React.CSSProperties;
+}) => {
+ const device = useDevice();
+ const menuRef = useRef<HTMLDivElement>(null);
+
+ const callbacksRef = useStable({ onClickOutside });
+
+ useOutsideClick(menuRef, () => {
+ callbacksRef.onClickOutside?.();
+ });
+
+ useEffect(() => {
+ const onKeyDown = (event: KeyboardEvent) => {
+ if (event.key === KEYS.ESCAPE) {
+ event.stopImmediatePropagation();
+ callbacksRef.onClickOutside?.();
+ }
+ };
+
+ const option = {
+ // so that we can stop propagation of the event before it reaches
+ // event handlers that were bound before this one
+ capture: true,
+ };
+
+ document.addEventListener(EVENT.KEYDOWN, onKeyDown, option);
+ return () => {
+ document.removeEventListener(EVENT.KEYDOWN, onKeyDown, option);
+ };
+ }, [callbacksRef]);
+
+ const classNames = clsx(`dropdown-menu ${className}`, {
+ "dropdown-menu--mobile": device.editor.isMobile,
+ }).trim();
+
+ return (
+ <DropdownMenuContentPropsContext.Provider value={{ onSelect }}>
+ <div
+ ref={menuRef}
+ className={classNames}
+ style={style}
+ data-testid="dropdown-menu"
+ >
+ {/* the zIndex ensures this menu has higher stacking order,
+ see https://github.com/excalidraw/excalidraw/pull/1445 */}
+ {device.editor.isMobile ? (
+ <Stack.Col className="dropdown-menu-container">{children}</Stack.Col>
+ ) : (
+ <Island
+ className="dropdown-menu-container"
+ padding={2}
+ style={{ zIndex: 2 }}
+ >
+ {children}
+ </Island>
+ )}
+ </div>
+ </DropdownMenuContentPropsContext.Provider>
+ );
+};
+MenuContent.displayName = "DropdownMenuContent";
+
+export default MenuContent;