import React, { useEffect, useState } from 'react' import styles from './_menu.module.scss' import { hasValue } from '../../services/HelperServiceTyped' import Icon from '../Icons/Icon' import { type IconStringList } from '../Icons/Icon.models' import { type TooltipProps } from '../Tooltip/Tooltip' import { type ConfirmationPopoverContentProps } from '../ConfirmationPopover/ConfirmationPopoverContent' import MenuCsvRow from './MenuComponent/MenuCsvRow' import MenuToggleRow from './MenuComponent/MenuToggleRow' import MenuStandardRow from './MenuComponent/MenuStandardRow' import MenuConfirmation from './MenuComponent/MenuConfirmation' import { c } from '../../translations/LibraryTranslationService' import type SingleCsv from '../CsvExport/SingleCsv' import SubMenu from './MenuComponent/SubMenu' export type MenuActionPropsBase = { /** Optionally style the action `text` and `icon` as red */ destructive?: boolean /** Optionally add a divider above an action. This should only be used for a single action. If there are multiple actions that should appear after the divider, make sure those actions appear after the action that has this prop. */ hasDivider?: boolean /** Optionally disable an action */ disabled?: { /** The current value of the disabled prop */ value: boolean /** Optionally show a tooltip when an action is disabled */ tooltip?: Omit } /** Optionally show a confirmation when selecting an option */ confirmation?: Omit /** Optional prop to add a test id to the Menu actions for QA testing */ qaTestId?: string } type MenuActionPropsStandard = MenuActionPropsBase & { /** Optional icon to appear to the left of the `text` */ icon?: IconStringList /** Text displayed for an action */ text: string /** Function callout for an action */ callout?: () => void /** Callback function triggered when a CSV action is performed. */ handleCsvAction?: never /** Secondary content to be displayed on the right side of the row */ secondaryContent?: React.ReactNode toggle?: never csv?: never subMenuTriggerActions?: never } export type MenuActionPropsWithSubMenu = MenuActionPropsBase & { text: string icon?: IconStringList /** Actions to render in a new submenu, triggered by this item. */ subMenuTriggerActions: MenuActionProps[] callout?: never link?: never routerComponent?: never routerProp?: never toggle?: never csv?: never secondaryContent?: never handleCsvAction?: never setLoading?: never loading?: never } type MenuActionPropsWithLink = MenuActionPropsStandard & { /** If a `link` is provided, then the Button will render as='link' */ link: string /** Router component to use for the link */ routerComponent: React.ElementType /** Router prop to use for the link */ routerProp: string setLoading?: never loading?: never secondaryContent?: never /** Callback function triggered when a CSV action is performed. */ handleCsvAction?: never } type MenuActionPropsWithoutLink = MenuActionPropsStandard & { link?: never routerComponent?: never routerProp?: never setLoading?: never loading?: never /** Callback function triggered when a CSV action is performed. */ handleCsvAction?: never } export type MenuActionPropsWithToggle = MenuActionPropsBase & { /** Optional icon to appear to the left of the `text` */ icon?: IconStringList /** Text displayed for an action */ text: string callout?: never handleCsvAction?: never /** `Toggle` to appear to the right of the `text` */ toggle: { checked: boolean callout: (value: boolean) => void } csv?: never link?: never routerComponent?: never routerProp?: never setLoading?: never loading?: never secondaryContent?: never subMenuTriggerActions?: never } type SingleCsvProps = React.ComponentProps type MenuActionPropsWithCSV = MenuActionPropsBase & { /** CSV action */ csv: SingleCsvProps['csv'] /** Callback function triggered when a CSV action is performed. */ handleCsvAction?: SingleCsvProps['handleCsvAction'] /** Callback to update the state indicating change in download. */ setLoading?: SingleCsvProps['setLoading'] /** State indicating download status */ loading?: SingleCsvProps['loading'] text?: never callout?: never toggle?: never icon?: never link?: never routerComponent?: never routerProp?: never secondaryContent?: never subMenuTriggerActions?: never } export type MenuActionProps = | MenuActionPropsWithToggle | MenuActionPropsWithCSV | MenuActionPropsWithLink | MenuActionPropsWithSubMenu | MenuActionPropsWithoutLink export interface MenuProps { /** Array of actions to used for the Menu */ actions: MenuActionProps[] /** A close function is often required to close a popover where the Menu is being used. */ close?: () => void /** This tells the consuming component when the form state is being changed so that component can re-render correctly. Only used when there is a confirmation option. */ reRenderView?: () => void /** This is used to close the confirmation within a popover. */ closeConfirmation?: boolean /** Optional prop to add a test id to the Menu for QA testing */ qaTestId?: string } const Menu = ({ actions, close, reRenderView, closeConfirmation, qaTestId = 'menu', }: MenuProps): React.JSX.Element => { const [currentConfirmation, setCurrentConfirmation] = useState() const confirmationCallout = (isConfirm: boolean) => { setCurrentConfirmation(undefined) reRenderView?.() isConfirm && close?.() } actions.forEach((action) => { if ( !action.csv && !action.toggle && !action.callout && !action.link && !action.confirmation && !action.subMenuTriggerActions ) { throw new Error(c('errorMenuActionProps')) } }) useEffect(() => { setCurrentConfirmation(undefined) }, [closeConfirmation]) return currentConfirmation ? ( ) : (
{actions.map((action, index) => { if (actions.filter((act) => act.hasDivider).length > 1) { throw new Error(c('errorMenuDuplicateDivider')) } const keyText = action.text let keyCsv: string | undefined if ('csv' in action && action.csv) { keyCsv = action.csv.csvName } let itemKey: string | undefined if ('subMenuTriggerActions' in action && action.subMenuTriggerActions) { itemKey = keyText } else { itemKey = keyText ?? keyCsv } return ( { setCurrentConfirmation(confirmation) reRenderView?.() }} close={close} /> ) })}
) } export default Menu const MenuRow = ({ text, icon, destructive, hasDivider, disabled, confirmation, callout, link, routerComponent, routerProp, toggle, csv, setCurrentConfirmation, close, setLoading, loading, secondaryContent, handleCsvAction, subMenuTriggerActions, qaTestId, }: MenuActionProps & { setCurrentConfirmation: ( confirmation: MenuActionProps['confirmation'], ) => void close?: () => void }) => { const CONTAINER_CLASSES = `${styles.menuItem} ${ destructive ? styles.darkRed : '' } ${hasDivider ? styles.highlighted : ''} ${ disabled?.value ? styles.disabled : '' }` let rowType = 'standard' switch (true) { case hasValue(subMenuTriggerActions): rowType = 'subMenu' break case hasValue(toggle): rowType = 'toggle' break case hasValue(csv): rowType = 'csv' break case hasValue(link): rowType = 'link' break default: break } return { subMenu: rowType === 'subMenu' && subMenuTriggerActions ? ( ) : null, toggle: rowType === 'toggle' && toggle ? ( ) : null, csv: rowType === 'csv' && csv ? ( ) : null, link: rowType === 'link' && link ? ( ) : null, standard: rowType === 'standard' ? ( { callout?.() close?.() }} setCurrentConfirmation={setCurrentConfirmation} text={text!} icon={icon} destructive={destructive} secondaryContent={secondaryContent} qaTestId={qaTestId} /> ) : null, }[rowType] } export const IconAndName = ({ icon, text, destructive, }: Pick) => { return ( {icon ? ( ) : null} {text} ) }