feat: CP support Menu expandIcon (#47561)

* feat: CP support Menu expandIcon

* fix: test case fix

* fix: test case fix

* fix: fix

* fix: fix

* chore: rename

* type: add type

---------

Signed-off-by: lijianan <574980606@qq.com>
This commit is contained in:
lijianan 2024-02-26 09:55:08 +08:00 committed by GitHub
parent f46af9366b
commit b28b0d883f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 54 additions and 34 deletions

View File

@ -28,6 +28,7 @@ import Layout from '../../layout';
import List from '../../list';
import Mentions from '../../mentions';
import Menu from '../../menu';
import type { MenuProps } from '../../menu';
import message from '../../message';
import Modal from '../../modal';
import notification from '../../notification';
@ -575,24 +576,32 @@ describe('ConfigProvider support style and className props', () => {
expect(container.querySelector('.ant-list')).toHaveStyle('color: red; font-size: 16px;');
});
it('Should Menu className works', () => {
const menuItems = [
it('Should Menu className & expandIcon works', () => {
const menuItems: MenuProps['items'] = [
{
label: 'Test Label',
label: <span>Test Label</span>,
key: 'test',
children: [
{
label: <span>Test Label children</span>,
key: 'test-children',
},
],
},
];
const { container } = render(
<ConfigProvider
menu={{
className: 'test-class',
}}
>
const App: React.FC<{ expand?: React.ReactNode }> = ({ expand }) => (
<ConfigProvider menu={{ className: 'test-class', expandIcon: expand }}>
<Menu items={menuItems} />
</ConfigProvider>,
</ConfigProvider>
);
expect(container.querySelector('.ant-menu')).toHaveClass('test-class');
const { container, rerender } = render(<App />);
expect(container.querySelector<HTMLElement>('.ant-menu')).toHaveClass('test-class');
rerender(<App expand={<span className="test-cp-icon">test-cp-icon</span>} />);
expect(container.querySelector<HTMLSpanElement>('.ant-menu .test-cp-icon')).toBeTruthy();
rerender(<App expand={null} />);
expect(container.querySelector<HTMLElement>('.ant-menu-submenu-arrow')).toBeFalsy();
rerender(<App expand={false} />);
expect(container.querySelector<HTMLElement>('.ant-menu-submenu-arrow')).toBeFalsy();
});
it('Should Menu style works', () => {

View File

@ -12,6 +12,7 @@ import type { FlexProps } from '../flex/interface';
import type { FormProps } from '../form/Form';
import type { InputProps } from '../input';
import type { Locale } from '../locale';
import type { MenuProps } from '../menu';
import type { ModalProps } from '../modal';
import type { ArgsProps } from '../notification/interface';
import type { PaginationProps } from '../pagination';
@ -85,6 +86,8 @@ export interface ImageConfig extends ComponentStyleConfig {
export type CollapseConfig = ComponentStyleConfig & Pick<CollapseProps, 'expandIcon'>;
export type MenuConfig = ComponentStyleConfig & Pick<MenuProps, 'expandIcon'>;
export type TourConfig = Pick<TourProps, 'closeIcon'>;
export type ModalConfig = ComponentStyleConfig &
@ -167,7 +170,7 @@ export interface ConfigConsumerProps {
result?: ComponentStyleConfig;
slider?: ComponentStyleConfig;
breadcrumb?: ComponentStyleConfig;
menu?: ComponentStyleConfig;
menu?: MenuConfig;
checkbox?: ComponentStyleConfig;
descriptions?: ComponentStyleConfig;
empty?: ComponentStyleConfig;

View File

@ -127,7 +127,7 @@ const {
| input | Set Input common props | { autoComplete?: string, className?: string, style?: React.CSSProperties } | - | 4.2.0 |
| layout | Set Layout common props | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
| list | Set List common props | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
| menu | Set Menu common props | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
| menu | Set Menu common props | { className?: string, style?: React.CSSProperties, expandIcon?: ReactNode \| props => ReactNode } | - | 5.7.0, expandIcon: 5.15.0 |
| mentions | Set Mentions common props | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
| message | Set Message common props | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
| modal | Set Modal common props | { className?: string, style?: React.CSSProperties, classNames?: [ModalProps\["classNames"\]](/components/modal-cn#api), styles?: [ModalProps\["styles"\]](/components/modal-cn#api), closeIcon?: React.ReactNode } | - | 5.7.0, `classNames` and `styles`: 5.10.0, `closeIcon`: 5.14.0 |

View File

@ -32,6 +32,7 @@ import type {
DrawerConfig,
FlexConfig,
ImageConfig,
MenuConfig,
ModalConfig,
NotificationConfig,
PopupOverflow,
@ -161,7 +162,7 @@ export interface ConfigProviderProps {
result?: ComponentStyleConfig;
slider?: ComponentStyleConfig;
breadcrumb?: ComponentStyleConfig;
menu?: ComponentStyleConfig;
menu?: MenuConfig;
checkbox?: ComponentStyleConfig;
descriptions?: ComponentStyleConfig;
empty?: ComponentStyleConfig;

View File

@ -129,7 +129,7 @@ const {
| input | 设置 Input 组件的通用属性 | { autoComplete?: string, className?: string, style?: React.CSSProperties } | - | 5.7.0 |
| layout | 设置 Layout 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
| list | 设置 List 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
| menu | 设置 Menu 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
| menu | 设置 Menu 组件的通用属性 | { className?: string, style?: React.CSSProperties, expandIcon?: ReactNode \| props => ReactNode } | - | 5.7.0, expandIcon: 5.15.0 |
| mentions | 设置 Mentions 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
| message | 设置 Message 组件的通用属性 | { className?: string, style?: React.CSSProperties } | - | 5.7.0 |
| modal | 设置 Modal 组件的通用属性 | { className?: string, style?: React.CSSProperties, classNames?: [ModalProps\["classNames"\]](/components/modal-cn#api), styles?: [ModalProps\["styles"\]](/components/modal-cn#api), closeIcon?: React.ReactNode } | - | 5.7.0, `classNames``styles`: 5.10.0, `closeIcon`: 5.14.0 |

View File

@ -1,3 +1,4 @@
import React, { useMemo, useState } from 'react';
import {
AppstoreOutlined,
InboxOutlined,
@ -5,15 +6,15 @@ import {
PieChartOutlined,
UserOutlined,
} from '@ant-design/icons';
import React, { useMemo, useState } from 'react';
import type { MenuProps, MenuRef } from '..';
import Menu from '..';
import initCollapseMotion from '../../_util/motion';
import { noop } from '../../_util/warning';
import { TriggerMockContext } from '../../../tests/shared/demoTestContext';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import { act, fireEvent, render } from '../../../tests/utils';
import initCollapseMotion from '../../_util/motion';
import { noop } from '../../_util/warning';
import Layout from '../../layout';
import OverrideContext from '../OverrideContext';
@ -1126,7 +1127,7 @@ describe('Menu', () => {
});
it('hide expand icon when pass null or false into expandIcon', () => {
const App = ({ expand }: { expand?: React.ReactNode }) => (
const App: React.FC<{ expand?: React.ReactNode }> = ({ expand }) => (
<Menu
expandIcon={expand}
items={[

View File

@ -20,6 +20,10 @@ import MenuContext from './MenuContext';
import OverrideContext from './OverrideContext';
import useStyle from './style';
function isEmptyIcon(icon?: React.ReactNode) {
return icon === null || icon === false;
}
export interface MenuProps extends Omit<RcMenuProps, 'items'> {
theme?: MenuTheme;
inlineIndent?: number;
@ -114,7 +118,7 @@ const InternalMenu = forwardRef<RcMenuRef, InternalMenuProps>((props, ref) => {
return inlineCollapsed;
}, [inlineCollapsed, siderCollapsed]);
const defaultMotions = {
const defaultMotions: MenuProps['defaultMotions'] = {
horizontal: { motionName: `${rootPrefixCls}-slide-up` },
inline: initCollapseMotion(rootPrefixCls),
other: { motionName: `${rootPrefixCls}-zoom-big` },
@ -125,23 +129,25 @@ const InternalMenu = forwardRef<RcMenuRef, InternalMenuProps>((props, ref) => {
const [wrapCSSVar, hashId, cssVarCls] = useStyle(prefixCls, rootCls, !override);
const menuClassName = classNames(`${prefixCls}-${theme}`, menu?.className, className);
// ====================== Expand Icon ========================
let mergedExpandIcon: MenuProps['expandIcon'];
if (typeof expandIcon === 'function') {
mergedExpandIcon = expandIcon;
} else if (expandIcon === null || expandIcon === false) {
mergedExpandIcon = null;
} else if (overrideObj.expandIcon === null || overrideObj.expandIcon === false) {
mergedExpandIcon = null;
} else {
const beClone: React.ReactNode = (expandIcon ?? overrideObj.expandIcon) as React.ReactNode;
mergedExpandIcon = cloneElement(beClone, {
// ====================== ExpandIcon ========================
const mergedExpandIcon = React.useMemo<MenuProps['expandIcon']>(() => {
if (typeof expandIcon === 'function' || isEmptyIcon(expandIcon)) {
return expandIcon || null;
}
if (typeof overrideObj.expandIcon === 'function' || isEmptyIcon(overrideObj.expandIcon)) {
return overrideObj.expandIcon || null;
}
if (typeof menu?.expandIcon === 'function' || isEmptyIcon(menu?.expandIcon)) {
return menu?.expandIcon || null;
}
const mergedIcon = expandIcon ?? overrideObj?.expandIcon ?? menu?.expandIcon;
return cloneElement(mergedIcon, {
className: classNames(
`${prefixCls}-submenu-expand-icon`,
React.isValidElement(beClone) ? beClone.props?.className : '',
React.isValidElement(mergedIcon) ? mergedIcon.props?.className : undefined,
),
});
}
}, [expandIcon, overrideObj?.expandIcon, menu?.expandIcon, prefixCls]);
// ======================== Context ==========================
const contextValue = React.useMemo<MenuContextProps>(