import * as React from 'react'; import classNames from 'classnames'; import type { MenuItemProps as RcMenuItemProps } from 'rc-menu'; import { Item } from 'rc-menu'; import toArray from 'rc-util/lib/Children/toArray'; import omit from 'rc-util/lib/omit'; import { cloneElement } from '../_util/reactNode'; import type { SiderContextProps } from '../layout/Sider'; import { SiderContext } from '../layout/Sider'; import type { TooltipProps } from '../tooltip'; import Tooltip from '../tooltip'; import type { MenuContextProps } from './MenuContext'; import MenuContext from './MenuContext'; export interface MenuItemProps extends Omit<RcMenuItemProps, 'title'> { icon?: React.ReactNode; danger?: boolean; title?: React.ReactNode; } type MenuItemComponent = React.FC<MenuItemProps>; type RestArgs<T> = T extends (arg: any, ...args: infer P) => any ? P : never; type GenericProps<T = unknown> = T extends infer U extends MenuItemProps ? unknown extends U ? MenuItemProps : U : MenuItemProps; type GenericComponent = Omit<MenuItemComponent, ''> & (<T extends MenuItemProps>( props: GenericProps<T>, ...args: RestArgs<MenuItemComponent> ) => ReturnType<MenuItemComponent>); const MenuItem: GenericComponent = (props) => { const { className, children, icon, title, danger, extra } = props; const { prefixCls, firstLevel, direction, disableMenuItemTitleTooltip, inlineCollapsed: isInlineCollapsed, } = React.useContext<MenuContextProps>(MenuContext); const renderItemChildren = (inlineCollapsed: boolean) => { const label = (children as React.ReactNode[])?.[0]; const wrapNode = ( <span className={classNames(`${prefixCls}-title-content`, { [`${prefixCls}-title-content-with-extra`]: !!extra || extra === 0, })} > {children} </span> ); // inline-collapsed.md demo 依赖 span 来隐藏文字,有 icon 属性,则内部包裹一个 span // ref: https://github.com/ant-design/ant-design/pull/23456 if (!icon || (React.isValidElement(children) && children.type === 'span')) { if (children && inlineCollapsed && firstLevel && typeof label === 'string') { return <div className={`${prefixCls}-inline-collapsed-noicon`}>{label.charAt(0)}</div>; } } return wrapNode; }; const { siderCollapsed } = React.useContext<SiderContextProps>(SiderContext); let tooltipTitle = title; if (typeof title === 'undefined') { tooltipTitle = firstLevel ? children : ''; } else if (title === false) { tooltipTitle = ''; } const tooltipProps: TooltipProps = { title: tooltipTitle }; if (!siderCollapsed && !isInlineCollapsed) { tooltipProps.title = null; // Reset `open` to fix control mode tooltip display not correct // ref: https://github.com/ant-design/ant-design/issues/16742 tooltipProps.open = false; } const childrenLength = toArray(children).length; let returnNode = ( <Item {...omit(props, ['title', 'icon', 'danger'])} className={classNames( { [`${prefixCls}-item-danger`]: danger, [`${prefixCls}-item-only-child`]: (icon ? childrenLength + 1 : childrenLength) === 1, }, className, )} title={typeof title === 'string' ? title : undefined} > {cloneElement(icon, { className: classNames( React.isValidElement(icon) ? icon.props?.className : '', `${prefixCls}-item-icon`, ), })} {renderItemChildren(isInlineCollapsed)} </Item> ); if (!disableMenuItemTitleTooltip) { returnNode = ( <Tooltip {...tooltipProps} placement={direction === 'rtl' ? 'left' : 'right'} overlayClassName={`${prefixCls}-inline-collapsed-tooltip`} > {returnNode} </Tooltip> ); } return returnNode; }; export default MenuItem;