fix: Dropdown with group menu can not close (#36148)

* test: test driven

* fix: Dropdown with Menu list group
This commit is contained in:
二货机器人 2022-06-21 15:48:29 +08:00 committed by GitHub
parent 092bfd7496
commit 9ab8cc44a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 102 additions and 19 deletions

View File

@ -1,10 +1,10 @@
import React from 'react';
import { mount } from 'enzyme';
import React from 'react';
import Dropdown from '..';
import Menu from '../../menu';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import { sleep } from '../../../tests/utils';
import { act, fireEvent, render, sleep } from '../../../tests/utils';
import Menu from '../../menu';
describe('Dropdown', () => {
mountTest(() => (
@ -99,4 +99,54 @@ describe('Dropdown', () => {
}),
);
});
it('menu item with group', () => {
jest.useFakeTimers();
const { container } = render(
<Dropdown
trigger="click"
overlay={
<Menu
items={[
{
label: 'grp',
type: 'group',
children: [
{
label: '1',
key: 1,
},
],
},
]}
/>
}
>
<a />
</Dropdown>,
);
// Open
fireEvent.click(container.querySelector('a'));
act(() => {
jest.runAllTimers();
});
// Close
fireEvent.click(container.querySelector('.ant-dropdown-menu-item'));
// Force Motion move on
for (let i = 0; i < 10; i += 1) {
act(() => {
jest.runAllTimers();
});
}
// Motion End
fireEvent.animationEnd(container.querySelector('.ant-slide-up-leave-active'));
expect(container.querySelector('.ant-dropdown-hidden')).toBeTruthy();
jest.useRealTimers();
});
});

View File

@ -1,6 +1,8 @@
import RightOutlined from '@ant-design/icons/RightOutlined';
import classNames from 'classnames';
import RcDropdown from 'rc-dropdown';
import useEvent from 'rc-util/lib/hooks/useEvent';
import useMergedState from 'rc-util/lib/hooks/useMergedState';
import * as React from 'react';
import { ConfigContext } from '../config-provider';
import { OverrideProvider } from '../menu/OverrideContext';
@ -116,6 +118,8 @@ const Dropdown: DropdownInterface = props => {
disabled,
getPopupContainer,
overlayClassName,
visible,
onVisibleChange,
} = props;
const prefixCls = getPrefixCls('dropdown', customizePrefixCls);
@ -132,21 +136,36 @@ const Dropdown: DropdownInterface = props => {
disabled,
});
const overlayClassNameCustomized = classNames(overlayClassName, {
[`${prefixCls}-rtl`]: direction === 'rtl',
});
const triggerActions = disabled ? [] : trigger;
let alignPoint;
if (triggerActions && triggerActions.indexOf('contextMenu') !== -1) {
alignPoint = true;
}
// =========================== Visible ============================
const [mergedVisible, setVisible] = useMergedState(false, {
value: visible,
});
const onInnerVisibleChange = useEvent((nextVisible: boolean) => {
onVisibleChange?.(nextVisible);
setVisible(nextVisible);
});
// =========================== Overlay ============================
const overlayClassNameCustomized = classNames(overlayClassName, {
[`${prefixCls}-rtl`]: direction === 'rtl',
});
const builtinPlacements = getPlacements({
arrowPointAtCenter: typeof arrow === 'object' && arrow.pointAtCenter,
autoAdjustOverflow: true,
});
const onMenuClick = React.useCallback(() => {
setVisible(false);
}, []);
const renderOverlay = () => {
// rc-dropdown already can process the function of overlay, but we have check logic here.
// So we need render the element to check and pass back to rc-dropdown.
@ -172,6 +191,7 @@ const Dropdown: DropdownInterface = props => {
}
mode="vertical"
selectable={false}
onClick={onMenuClick}
validator={({ mode }) => {
// Warning if use other mode
warning(
@ -186,10 +206,12 @@ const Dropdown: DropdownInterface = props => {
);
};
// ============================ Render ============================
return (
<RcDropdown
alignPoint={alignPoint}
{...props}
visible={mergedVisible}
builtinPlacements={builtinPlacements}
arrow={!!arrow}
overlayClassName={overlayClassNameCustomized}
@ -199,6 +221,7 @@ const Dropdown: DropdownInterface = props => {
trigger={triggerActions}
overlay={renderOverlay}
placement={getPlacement()}
onVisibleChange={onInnerVisibleChange}
>
{dropdownTrigger}
</RcDropdown>

View File

@ -8,6 +8,7 @@ export interface OverrideContextProps {
mode?: MenuProps['mode'];
selectable?: boolean;
validator?: (menuProps: Pick<MenuProps, 'mode'>) => void;
onClick?: () => void;
}
/** @private Internal Usage. Only used for Dropdown component. Do not use this in your production. */

View File

@ -1,27 +1,28 @@
import * as React from 'react';
import EllipsisOutlined from '@ant-design/icons/EllipsisOutlined';
import classNames from 'classnames';
import type { MenuProps as RcMenuProps, MenuRef } from 'rc-menu';
import RcMenu, { ItemGroup } from 'rc-menu';
import classNames from 'classnames';
import useEvent from 'rc-util/lib/hooks/useEvent';
import omit from 'rc-util/lib/omit';
import EllipsisOutlined from '@ant-design/icons/EllipsisOutlined';
import * as React from 'react';
import { forwardRef } from 'react';
import SubMenu, { SubMenuProps } from './SubMenu';
import Item, { MenuItemProps } from './MenuItem';
import { ConfigContext } from '../config-provider';
import warning from '../_util/warning';
import type { SiderContextProps } from '../layout/Sider';
import { SiderContext } from '../layout/Sider';
import collapseMotion from '../_util/motion';
import { cloneElement } from '../_util/reactNode';
import MenuContext, { MenuTheme } from './MenuContext';
import MenuDivider from './MenuDivider';
import warning from '../_util/warning';
import type { ItemType } from './hooks/useItems';
import useItems from './hooks/useItems';
import MenuContext, { MenuTheme } from './MenuContext';
import MenuDivider from './MenuDivider';
import Item, { MenuItemProps } from './MenuItem';
import OverrideContext from './OverrideContext';
export { MenuDividerProps } from './MenuDivider';
import SubMenu, { SubMenuProps } from './SubMenu';
export { MenuItemGroupProps } from 'rc-menu';
export { MenuDividerProps } from './MenuDivider';
export { MenuTheme, SubMenuProps, MenuItemProps };
export type MenuMode = 'vertical' | 'vertical-left' | 'vertical-right' | 'horizontal' | 'inline';
@ -46,6 +47,7 @@ type InternalMenuProps = MenuProps &
const InternalMenu = forwardRef<MenuRef, InternalMenuProps>((props, ref) => {
const override = React.useContext(OverrideContext) || {};
const { getPrefixCls, getPopupContainer, direction } = React.useContext(ConfigContext);
const rootPrefixCls = getPrefixCls();
@ -62,6 +64,7 @@ const InternalMenu = forwardRef<MenuRef, InternalMenuProps>((props, ref) => {
children,
mode,
selectable,
onClick,
...restProps
} = props;
@ -91,6 +94,13 @@ const InternalMenu = forwardRef<MenuRef, InternalMenuProps>((props, ref) => {
override.validator?.({ mode });
// ========================== Click ==========================
// Tell dropdown that item clicked
const onItemClick = useEvent<Required<MenuProps>['onClick']>((...args) => {
onClick?.(...args);
override?.onClick?.();
});
// ========================== Mode ===========================
const mergedMode = override.mode || mode;
@ -148,6 +158,7 @@ const InternalMenu = forwardRef<MenuRef, InternalMenuProps>((props, ref) => {
overflowedIndicatorPopupClassName={`${prefixCls}-${theme}`}
mode={mergedMode}
selectable={mergedSelectable}
onClick={onItemClick}
{...passedProps}
inlineCollapsed={mergedInlineCollapsed}
className={menuClassName}
@ -197,6 +208,4 @@ class Menu extends React.Component<MenuProps, {}> {
}
}
export { MenuTheme, SubMenuProps, MenuItemProps };
export default Menu;