Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: CP support Menu expandIcon #47561

Merged
merged 9 commits into from
Feb 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
35 changes: 22 additions & 13 deletions components/config-provider/__tests__/style.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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>,
);

expect(container.querySelector('.ant-menu')).toHaveClass('test-class');
</ConfigProvider>
);
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', () => {
Expand Down
5 changes: 4 additions & 1 deletion components/config-provider/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 &
Expand Down Expand Up @@ -167,7 +170,7 @@ export interface ConfigConsumerProps {
result?: ComponentStyleConfig;
slider?: ComponentStyleConfig;
breadcrumb?: ComponentStyleConfig;
menu?: ComponentStyleConfig;
menu?: MenuConfig;
checkbox?: ComponentStyleConfig;
descriptions?: ComponentStyleConfig;
empty?: ComponentStyleConfig;
Expand Down
2 changes: 1 addition & 1 deletion components/config-provider/index.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
3 changes: 2 additions & 1 deletion components/config-provider/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import type {
DrawerConfig,
FlexConfig,
ImageConfig,
MenuConfig,
ModalConfig,
NotificationConfig,
PopupOverflow,
Expand Down Expand Up @@ -161,7 +162,7 @@ export interface ConfigProviderProps {
result?: ComponentStyleConfig;
slider?: ComponentStyleConfig;
breadcrumb?: ComponentStyleConfig;
menu?: ComponentStyleConfig;
menu?: MenuConfig;
checkbox?: ComponentStyleConfig;
descriptions?: ComponentStyleConfig;
empty?: ComponentStyleConfig;
Expand Down
2 changes: 1 addition & 1 deletion components/config-provider/index.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down
9 changes: 5 additions & 4 deletions components/menu/__tests__/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import React, { useMemo, useState } from 'react';
import {
AppstoreOutlined,
InboxOutlined,
MailOutlined,
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';

Expand Down Expand Up @@ -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={[
Expand Down
34 changes: 20 additions & 14 deletions components/menu/menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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` },
Expand All @@ -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>(
Expand Down