import * as React from 'react'; import RcMenu, { Divider, ItemGroup } from 'rc-menu'; import * as PropTypes from 'prop-types'; import classNames from 'classnames'; import animation from '../_util/openAnimation'; import warning from '../_util/warning'; import SubMenu from './SubMenu'; import Item from './MenuItem'; import { SiderContext } from '../layout/Sider'; export interface SelectParam { key: string; keyPath: Array; item: any; domEvent: any; selectedKeys: Array; } export interface ClickParam { key: string; keyPath: Array; item: any; domEvent: any; } export type MenuMode = 'vertical' | 'vertical-left' | 'vertical-right' | 'horizontal' | 'inline'; export type MenuTheme = 'light' | 'dark'; export interface MenuProps { id?: string; theme?: MenuTheme; mode?: MenuMode; selectable?: boolean; selectedKeys?: Array; defaultSelectedKeys?: Array; openKeys?: Array; defaultOpenKeys?: Array; onOpenChange?: (openKeys: string[]) => void; onSelect?: (param: SelectParam) => void; onDeselect?: (param: SelectParam) => void; onClick?: (param: ClickParam) => void; style?: React.CSSProperties; openAnimation?: string | Object; openTransitionName?: string | Object; className?: string; prefixCls?: string; multiple?: boolean; inlineIndent?: number; inlineCollapsed?: boolean; subMenuCloseDelay?: number; subMenuOpenDelay?: number; getPopupContainer?: (triggerNode: Element) => HTMLElement; focusable?: boolean; } export interface MenuState { openKeys: string[]; } export default class Menu extends React.Component { static Divider = Divider; static Item = Item; static SubMenu = SubMenu; static ItemGroup = ItemGroup; static defaultProps: Partial = { prefixCls: 'ant-menu', className: '', theme: 'light', // or dark focusable: false, }; static childContextTypes = { inlineCollapsed: PropTypes.bool, antdMenuTheme: PropTypes.string, }; static contextTypes = { siderCollapsed: PropTypes.bool, collapsedWidth: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), }; context: any; switchingModeFromInline: boolean; inlineOpenKeys: string[] = []; constructor(props: MenuProps) { super(props); warning( !('onOpen' in props || 'onClose' in props), '`onOpen` and `onClose` are removed, please use `onOpenChange` instead, ' + 'see: https://u.ant.design/menu-on-open-change.', ); warning( !('inlineCollapsed' in props && props.mode !== 'inline'), '`inlineCollapsed` should only be used when Menu\'s `mode` is inline.', ); let openKeys; if ('openKeys' in props) { openKeys = props.openKeys; } else if ('defaultOpenKeys' in props) { openKeys = props.defaultOpenKeys; } this.state = { openKeys: openKeys || [], }; } getChildContext() { return { inlineCollapsed: this.getInlineCollapsed(), antdMenuTheme: this.props.theme, }; } componentWillReceiveProps(nextProps: MenuProps, nextContext: SiderContext) { if (this.props.mode === 'inline' && nextProps.mode !== 'inline') { this.switchingModeFromInline = true; } if ('openKeys' in nextProps) { this.setState({ openKeys: nextProps.openKeys! }); return; } if ((nextProps.inlineCollapsed && !this.props.inlineCollapsed) || (nextContext.siderCollapsed && !this.context.siderCollapsed)) { this.switchingModeFromInline = true; this.inlineOpenKeys = this.state.openKeys; this.setState({ openKeys: [] }); } if ((!nextProps.inlineCollapsed && this.props.inlineCollapsed) || (!nextContext.siderCollapsed && this.context.siderCollapsed)) { this.setState({ openKeys: this.inlineOpenKeys }); this.inlineOpenKeys = []; } } restoreModeVerticalFromInline() { if (this.switchingModeFromInline) { this.switchingModeFromInline = false; this.setState({}); } } handleTransitionEnd = (e: TransitionEvent) => { // when inlineCollapsed menu width animation finished // https://github.com/ant-design/ant-design/issues/12864 const widthCollapsed = e.propertyName === 'width' && e.target === e.currentTarget; // Fix for , the width transition won't trigger when menu is collapsed // https://github.com/ant-design/ant-design-pro/issues/2783 const iconScaled = e.propertyName === 'font-size' && (e.target as HTMLElement).className.indexOf('anticon') >= 0; if (widthCollapsed || iconScaled) { this.restoreModeVerticalFromInline(); } } handleClick = (e: ClickParam) => { this.handleOpenChange([]); const { onClick } = this.props; if (onClick) { onClick(e); } } handleOpenChange = (openKeys: string[]) => { this.setOpenKeys(openKeys); const { onOpenChange } = this.props; if (onOpenChange) { onOpenChange(openKeys); } } setOpenKeys(openKeys: string[]) { if (!('openKeys' in this.props)) { this.setState({ openKeys }); } } getRealMenuMode() { const inlineCollapsed = this.getInlineCollapsed(); if (this.switchingModeFromInline && inlineCollapsed) { return 'inline'; } const { mode } = this.props; return inlineCollapsed ? 'vertical' : mode; } getInlineCollapsed() { const { inlineCollapsed } = this.props; if (this.context.siderCollapsed !== undefined) { return this.context.siderCollapsed; } return inlineCollapsed; } getMenuOpenAnimation(menuMode: MenuMode) { const { openAnimation, openTransitionName } = this.props; let menuOpenAnimation = openAnimation || openTransitionName; if (openAnimation === undefined && openTransitionName === undefined) { switch (menuMode) { case 'horizontal': menuOpenAnimation = 'slide-up'; break; case 'vertical': case 'vertical-left': case 'vertical-right': // When mode switch from inline // submenu should hide without animation if (this.switchingModeFromInline) { menuOpenAnimation = ''; this.switchingModeFromInline = false; } else { menuOpenAnimation = 'zoom-big'; } break; case 'inline': menuOpenAnimation = animation; break; default: } } return menuOpenAnimation; } render() { const { prefixCls, className, theme } = this.props; const menuMode = this.getRealMenuMode(); const menuOpenAnimation = this.getMenuOpenAnimation(menuMode!); const menuClassName = classNames(className, `${prefixCls}-${theme}`, { [`${prefixCls}-inline-collapsed`]: this.getInlineCollapsed(), }); const menuProps: MenuProps = { openKeys: this.state.openKeys, onOpenChange: this.handleOpenChange, className: menuClassName, mode: menuMode, }; if (menuMode !== 'inline') { // closing vertical popup submenu after click it menuProps.onClick = this.handleClick; menuProps.openTransitionName = menuOpenAnimation; } else { menuProps.openAnimation = menuOpenAnimation; } // https://github.com/ant-design/ant-design/issues/8587 const { collapsedWidth } = this.context; if ( this.getInlineCollapsed() && (collapsedWidth === 0 || collapsedWidth === '0' || collapsedWidth === '0px') ) { return null; } return ( ); } }