From 6ec259a0e71174651bae95d4628138bf6fd68742 Mon Sep 17 00:00:00 2001
From: kj_sh604
Date: Sun, 15 Mar 2026 16:19:35 -0400
Subject: refactor: packages/

---
 .../dropdownMenu/DropdownMenuContent.tsx           | 88 ++++++++++++++++++++++
 1 file changed, 88 insertions(+)
 create mode 100644 packages/excalidraw/components/dropdownMenu/DropdownMenuContent.tsx

(limited to 'packages/excalidraw/components/dropdownMenu/DropdownMenuContent.tsx')

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;
-- 
cgit v1.2.3

