From 14ad745d0038a828f91560ef15f660a423fdc488 Mon Sep 17 00:00:00 2001 From: barnabasmolnar Date: Tue, 18 Apr 2023 01:04:02 +0200 Subject: [PATCH] Initial attempt @ submenu implementation. --- src/components/dropdownMenu/DropdownMenu.tsx | 3 +- .../dropdownMenu/DropdownMenuSub.tsx | 26 +++++++++++++ .../dropdownMenu/DropdownMenuSubContent.tsx | 38 ++++++++++++++++++ .../dropdownMenu/DropdownMenuSubItem.tsx | 39 +++++++++++++++++++ .../dropdownMenu/DropdownMenuSubTrigger.tsx | 29 ++++++++++++++ .../dropdownMenu/dropdownMenuUtils.ts | 28 +++++-------- src/components/main-menu/MainMenu.tsx | 2 + src/packages/excalidraw/example/App.tsx | 10 +++++ 8 files changed, 156 insertions(+), 19 deletions(-) create mode 100644 src/components/dropdownMenu/DropdownMenuSub.tsx create mode 100644 src/components/dropdownMenu/DropdownMenuSubContent.tsx create mode 100644 src/components/dropdownMenu/DropdownMenuSubItem.tsx create mode 100644 src/components/dropdownMenu/DropdownMenuSubTrigger.tsx 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 + + +