diff --git a/src/components/dropdownMenu/DropdownMenu.tsx b/src/components/dropdownMenu/DropdownMenu.tsx
index 08cde9a96..962b67804 100644
--- a/src/components/dropdownMenu/DropdownMenu.tsx
+++ b/src/components/dropdownMenu/DropdownMenu.tsx
@@ -24,10 +24,11 @@ const DropdownMenu = ({
}) => {
const MenuTriggerComp = getMenuTriggerComponent(children);
const MenuContentComp = getMenuContentComponent(children);
+
return (
{MenuTriggerComp}
- {open && MenuContentComp}
+ {MenuContentComp}
);
};
diff --git a/src/components/dropdownMenu/DropdownMenuSub.tsx b/src/components/dropdownMenu/DropdownMenuSub.tsx
new file mode 100644
index 000000000..ffaabaabb
--- /dev/null
+++ b/src/components/dropdownMenu/DropdownMenuSub.tsx
@@ -0,0 +1,26 @@
+import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
+import {
+ getSubMenuContentComponent,
+ getSubMenuTriggerComponent,
+} from "./dropdownMenuUtils";
+import DropdownMenuSubTrigger from "./DropdownMenuSubTrigger";
+import DropdownMenuSubContent from "./DropdownMenuSubContent";
+import DropdownMenuSubItem from "./DropdownMenuSubItem";
+
+const DropdownMenuSub = ({ children }: { children?: React.ReactNode }) => {
+ const MenuTriggerComp = getSubMenuTriggerComponent(children);
+ const MenuContentComp = getSubMenuContentComponent(children);
+ return (
+
+ {MenuTriggerComp}
+ {MenuContentComp}
+
+ );
+};
+
+DropdownMenuSub.Trigger = DropdownMenuSubTrigger;
+DropdownMenuSub.Content = DropdownMenuSubContent;
+DropdownMenuSub.Item = DropdownMenuSubItem;
+
+export default DropdownMenuSub;
+DropdownMenuSub.displayName = "DropdownMenuSub";
diff --git a/src/components/dropdownMenu/DropdownMenuSubContent.tsx b/src/components/dropdownMenu/DropdownMenuSubContent.tsx
new file mode 100644
index 000000000..665933e9a
--- /dev/null
+++ b/src/components/dropdownMenu/DropdownMenuSubContent.tsx
@@ -0,0 +1,38 @@
+import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
+import { useDevice } from "../App";
+import Stack from "../Stack";
+import { Island } from "../Island";
+import clsx from "clsx";
+
+const DropdownMenuSubContent = ({
+ children,
+ className,
+}: {
+ children?: React.ReactNode;
+ className?: string;
+}) => {
+ const device = useDevice();
+
+ const classNames = clsx(`dropdown-menu ${className}`, {
+ "dropdown-menu--mobile": device.isMobile,
+ }).trim();
+
+ return (
+
+ {device.isMobile ? (
+ {children}
+ ) : (
+
+ {children}
+
+ )}
+
+ );
+};
+
+export default DropdownMenuSubContent;
+DropdownMenuSubContent.displayName = "DropdownMenuSubContent";
diff --git a/src/components/dropdownMenu/DropdownMenuSubItem.tsx b/src/components/dropdownMenu/DropdownMenuSubItem.tsx
new file mode 100644
index 000000000..ade5139bc
--- /dev/null
+++ b/src/components/dropdownMenu/DropdownMenuSubItem.tsx
@@ -0,0 +1,39 @@
+import MenuItemContent from "./DropdownMenuItemContent";
+import {
+ getDropdownMenuItemClassName,
+ useHandleDropdownMenuItemClick,
+} from "./common";
+
+const DropdownMenuSubItem = ({
+ icon,
+ onSelect,
+ children,
+ shortcut,
+ className,
+ ...rest
+}: {
+ icon?: JSX.Element;
+ onSelect: (event: Event) => void;
+ children: React.ReactNode;
+ shortcut?: string;
+ className?: string;
+} & Omit, "onSelect">) => {
+ const handleClick = useHandleDropdownMenuItemClick(rest.onClick, onSelect);
+
+ return (
+
+ );
+};
+
+export default DropdownMenuSubItem;
+DropdownMenuSubItem.displayName = "DropdownMenuSubItem";
diff --git a/src/components/dropdownMenu/DropdownMenuSubTrigger.tsx b/src/components/dropdownMenu/DropdownMenuSubTrigger.tsx
new file mode 100644
index 000000000..ab6ef353a
--- /dev/null
+++ b/src/components/dropdownMenu/DropdownMenuSubTrigger.tsx
@@ -0,0 +1,29 @@
+import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
+import React from "react";
+import MenuItemContent from "./DropdownMenuItemContent";
+import { getDropdownMenuItemClassName } from "./common";
+
+const DropdownMenuSubTrigger = ({
+ children,
+ icon,
+ shortcut,
+ className,
+}: {
+ children: React.ReactNode;
+ icon?: JSX.Element;
+ shortcut?: string;
+ className?: string;
+}) => {
+ return (
+
+
+ {children}
+
+
+ );
+};
+
+export default DropdownMenuSubTrigger;
+DropdownMenuSubTrigger.displayName = "DropdownMenuSubTrigger";
diff --git a/src/components/dropdownMenu/dropdownMenuUtils.ts b/src/components/dropdownMenu/dropdownMenuUtils.ts
index 10d91fb85..82e8ccf59 100644
--- a/src/components/dropdownMenu/dropdownMenuUtils.ts
+++ b/src/components/dropdownMenu/dropdownMenuUtils.ts
@@ -1,6 +1,6 @@
import React from "react";
-export const getMenuTriggerComponent = (children: React.ReactNode) => {
+const getMenuComponent = (component: string) => (children: React.ReactNode) => {
const comp = React.Children.toArray(children).find(
(child) =>
React.isValidElement(child) &&
@@ -8,7 +8,7 @@ export const getMenuTriggerComponent = (children: React.ReactNode) => {
//@ts-ignore
child?.type.displayName &&
//@ts-ignore
- child.type.displayName === "DropdownMenuTrigger",
+ child.type.displayName === component,
);
if (!comp) {
return null;
@@ -17,19 +17,11 @@ export const getMenuTriggerComponent = (children: React.ReactNode) => {
return comp;
};
-export const getMenuContentComponent = (children: React.ReactNode) => {
- const comp = React.Children.toArray(children).find(
- (child) =>
- React.isValidElement(child) &&
- typeof child.type !== "string" &&
- //@ts-ignore
- child?.type.displayName &&
- //@ts-ignore
- child.type.displayName === "DropdownMenuContent",
- );
- if (!comp) {
- return null;
- }
- //@ts-ignore
- return comp;
-};
+export const getMenuTriggerComponent = getMenuComponent("DropdownMenuTrigger");
+export const getMenuContentComponent = getMenuComponent("DropdownMenuContent");
+export const getSubMenuTriggerComponent = getMenuComponent(
+ "DropdownMenuSubTrigger",
+);
+export const getSubMenuContentComponent = getMenuComponent(
+ "DropdownMenuSubContent",
+);
diff --git a/src/components/main-menu/MainMenu.tsx b/src/components/main-menu/MainMenu.tsx
index 636ffc6e5..bd0b2f532 100644
--- a/src/components/main-menu/MainMenu.tsx
+++ b/src/components/main-menu/MainMenu.tsx
@@ -14,6 +14,7 @@ import { HamburgerMenuIcon } from "../icons";
import { withInternalFallback } from "../hoc/withInternalFallback";
import { composeEventHandlers } from "../../utils";
import { useTunnels } from "../context/tunnels";
+import DropdownMenuSub from "../dropdownMenu/DropdownMenuSub";
const MainMenu = Object.assign(
withInternalFallback(
@@ -77,6 +78,7 @@ const MainMenu = Object.assign(
ItemCustom: DropdownMenu.ItemCustom,
Group: DropdownMenu.Group,
Separator: DropdownMenu.Separator,
+ Sub: DropdownMenuSub,
DefaultItems,
},
);
diff --git a/src/packages/excalidraw/example/App.tsx b/src/packages/excalidraw/example/App.tsx
index 1f4a6c7fd..4707a63da 100644
--- a/src/packages/excalidraw/example/App.tsx
+++ b/src/packages/excalidraw/example/App.tsx
@@ -506,6 +506,16 @@ export default function App({ appTitle, useCustom, customArgs }: AppProps) {
const renderMenu = () => {
return (
+
+ Trigger
+
+ window.alert("You clicked on sub item")}
+ >
+ Sub item
+
+
+