import React from 'react' import Menu, { type MenuActionProps, type MenuActionPropsBase, type MenuActionPropsWithToggle, type MenuProps as OriginalMenuProps, } from '../Menu/Menu' import Popover from '../Popover/Popover' import { type PopoverProps } from '../Popover/Popover' // Type for menu items that CANNOT have their own subMenu (i.e., items in a subMenu) export type NoSubMenuAction = MenuActionProps & { subMenu?: never popoverSubMenuActions?: never } // Type for top-level menu items that CAN have a subMenu // The subMenu itself will contain items of type NoSubMenuAction export type DropdownAction = MenuActionProps & { subMenu?: MenuActionProps[] } export interface DropdownMenuProps { actions: DropdownAction[] trigger: React.JSX.Element popoverContentClassName?: string mainPopoverClassName?: string submenuPopoverClassName?: string reRenderView?: OriginalMenuProps['reRenderView'] closeConfirmation?: OriginalMenuProps['closeConfirmation'] popoverProps?: Omit /** Optional prop to add a test id to the DropdownMenu for QA testing */ qaTestId?: string } interface MenuLevelContentProps { // Actions can be top-level (DropdownAction) or second-level (NoSubMenuAction) // Both are compatible with MenuActionProps for rendering the Menu itself. // The logic inside MenuLevelContent will determine if a subMenu can be opened. actions: Array closeMenuLevelAndParent?: () => void closeSelf: () => void depth: number baseProps: Pick< DropdownMenuProps, | 'reRenderView' | 'closeConfirmation' | 'popoverContentClassName' | 'submenuPopoverClassName' | 'qaTestId' > } const MenuLevelContent: React.FC = ({ actions, closeSelf, depth, baseProps, }) => { const menuActionsForMenuComponent: MenuActionProps[] = actions.map( (action) => { const { subMenu, callout: originalCallout, link, routerComponent, routerProp, toggle, csv, handleCsvAction: actionHandleCsvAction, setLoading: actionSetLoading, loading: actionLoading, text, icon, destructive, hasDivider, disabled, confirmation, qaTestId, } = action as DropdownAction const commonProps: MenuActionPropsBase = { destructive, hasDivider, disabled, confirmation, qaTestId, } if (depth === 0 && subMenu && subMenu.length > 0) { return { text: text!, icon, destructive: commonProps.destructive, hasDivider: commonProps.hasDivider, disabled: commonProps.disabled, subMenuTriggerActions: subMenu, qaTestId, } as MenuActionProps } else if (csv) { return { ...commonProps, csv, handleCsvAction: actionHandleCsvAction, setLoading: actionSetLoading, loading: actionLoading, } as MenuActionProps } else if (toggle) { return { ...commonProps, text, icon, toggle: { checked: toggle.checked, callout: toggle.callout, }, } as MenuActionPropsWithToggle } else if (link) { return { ...commonProps, text, icon, link, routerComponent, routerProp, callout: originalCallout, secondaryContent: action.secondaryContent, } as MenuActionProps } else if (originalCallout) { return { ...commonProps, text, icon, callout: originalCallout, secondaryContent: action.secondaryContent, } as MenuActionProps } else if (confirmation) { return { ...commonProps, text, icon, confirmation, secondaryContent: action.secondaryContent, } as MenuActionProps } return { ...commonProps, text: text || '', icon, callout: closeSelf, secondaryContent: action.secondaryContent, } as MenuActionProps }, ) return (
) } const DropdownMenu: React.FC = ({ actions, trigger, popoverContentClassName, mainPopoverClassName, submenuPopoverClassName, reRenderView, closeConfirmation, popoverProps, qaTestId = 'dropdown-menu', }) => { return ( ( setVisible(false)} closeMenuLevelAndParent={() => setVisible(false)} depth={0} baseProps={{ reRenderView, closeConfirmation, popoverContentClassName, submenuPopoverClassName, qaTestId, }} /> )} className={mainPopoverClassName} qaTestId={qaTestId} > {({ setVisible, visible }) => { return React.cloneElement(trigger, { onClick: (e: React.MouseEvent) => { setVisible((v: boolean) => !v) trigger.props.onClick?.(e) }, 'aria-haspopup': 'true', 'aria-expanded': visible, }) }} ) } export default DropdownMenu