mirror of
https://github.com/ant-design/ant-design.git
synced 2025-01-18 14:13:37 +08:00
fix: Dropdown with group menu can not close (#36148)
* test: test driven * fix: Dropdown with Menu list group
This commit is contained in:
parent
092bfd7496
commit
9ab8cc44a3
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -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>
|
||||
|
@ -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. */
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user