feat(modal): Ingested changes from rc-dialog to disable modal close button (#50522)

Co-authored-by: Alina Andrieieva <Alina_Andrieieva@epam.com>
Co-authored-by: afc163 <afc163@gmail.com>
This commit is contained in:
Alina Andrieieva 2024-09-18 18:31:06 +03:00 committed by GitHub
parent b5b0918970
commit a75c481ddf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 25 additions and 14 deletions

View File

@ -1,10 +1,10 @@
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import React from 'react'; import React from 'react';
import CloseOutlined from '@ant-design/icons/CloseOutlined'; import CloseOutlined from '@ant-design/icons/CloseOutlined';
import type { DialogProps } from 'rc-dialog';
import pickAttrs from 'rc-util/lib/pickAttrs'; import pickAttrs from 'rc-util/lib/pickAttrs';
export type BaseClosableType = { closeIcon?: React.ReactNode } & React.AriaAttributes; export type ClosableType = DialogProps['closable'];
export type ClosableType = boolean | BaseClosableType;
export type BaseContextClosable = { closable?: ClosableType; closeIcon?: ReactNode }; export type BaseContextClosable = { closable?: ClosableType; closeIcon?: ReactNode };
export type ContextClosable<T extends BaseContextClosable = any> = Partial< export type ContextClosable<T extends BaseContextClosable = any> = Partial<
@ -49,7 +49,7 @@ function useClosableConfig(closableCollection?: ClosableCollection | null) {
return null; return null;
} }
let closableConfig: BaseClosableType = { let closableConfig: ClosableType = {
closeIcon: typeof closeIcon !== 'boolean' && closeIcon !== null ? closeIcon : undefined, closeIcon: typeof closeIcon !== 'boolean' && closeIcon !== null ? closeIcon : undefined,
}; };
if (closable && typeof closable === 'object') { if (closable && typeof closable === 'object') {
@ -104,10 +104,12 @@ export default function useClosable(
*/ */
closeIconRender?: (closeIcon: ReactNode) => ReactNode; closeIconRender?: (closeIcon: ReactNode) => ReactNode;
} = EmptyFallbackCloseCollection, } = EmptyFallbackCloseCollection,
): [closable: boolean, closeIcon: React.ReactNode | null] { ): [closable: boolean, closeIcon: React.ReactNode, closeBtnIsDisabled: boolean] {
// Align the `props`, `context` `fallback` to config object first // Align the `props`, `context` `fallback` to config object first
const propCloseConfig = useClosableConfig(propCloseCollection); const propCloseConfig = useClosableConfig(propCloseCollection);
const contextCloseConfig = useClosableConfig(contextCloseCollection); const contextCloseConfig = useClosableConfig(contextCloseCollection);
const closeBtnIsDisabled =
typeof propCloseConfig !== 'boolean' ? !!propCloseConfig?.disabled : false;
const mergedFallbackCloseCollection = React.useMemo( const mergedFallbackCloseCollection = React.useMemo(
() => ({ () => ({
closeIcon: <CloseOutlined />, closeIcon: <CloseOutlined />,
@ -149,7 +151,7 @@ export default function useClosable(
// Calculate the final closeIcon // Calculate the final closeIcon
return React.useMemo(() => { return React.useMemo(() => {
if (mergedClosableConfig === false) { if (mergedClosableConfig === false) {
return [false, null]; return [false, null, closeBtnIsDisabled];
} }
const { closeIconRender } = mergedFallbackCloseCollection; const { closeIconRender } = mergedFallbackCloseCollection;
@ -173,6 +175,6 @@ export default function useClosable(
} }
} }
return [true, mergedCloseIcon]; return [true, mergedCloseIcon, closeBtnIsDisabled];
}, [mergedClosableConfig, mergedFallbackCloseCollection]); }, [mergedClosableConfig, mergedFallbackCloseCollection]);
} }

View File

@ -107,7 +107,7 @@ const Modal: React.FC<ModalProps> = (props) => {
<Footer {...props} onOk={handleOk} onCancel={handleCancel} /> <Footer {...props} onOk={handleOk} onCancel={handleCancel} />
) : null; ) : null;
const [mergedClosable, mergedCloseIcon] = useClosable( const [mergedClosable, mergedCloseIcon, closeBtnIsDisabled] = useClosable(
pickClosable(props), pickClosable(props),
pickClosable(modalContext), pickClosable(modalContext),
{ {
@ -139,7 +139,11 @@ const Modal: React.FC<ModalProps> = (props) => {
visible={open ?? visible} visible={open ?? visible}
mousePosition={restProps.mousePosition ?? mousePosition} mousePosition={restProps.mousePosition ?? mousePosition}
onClose={handleCancel as any} onClose={handleCancel as any}
closable={mergedClosable} closable={
mergedClosable
? { disabled: closeBtnIsDisabled, closeIcon: mergedCloseIcon }
: mergedClosable
}
closeIcon={mergedCloseIcon} closeIcon={mergedCloseIcon}
focusTriggerAfterClose={focusTriggerAfterClose} focusTriggerAfterClose={focusTriggerAfterClose}
transitionName={getTransitionName(rootPrefixCls, 'zoom', props.transitionName)} transitionName={getTransitionName(rootPrefixCls, 'zoom', props.transitionName)}

View File

@ -41,6 +41,11 @@ describe('Modal', () => {
expect(baseElement.querySelector('.ant-modal-close')).toBeFalsy(); expect(baseElement.querySelector('.ant-modal-close')).toBeFalsy();
}); });
it('support disable close button when setting disable to true', () => {
const { baseElement } = render(<Modal open closable={{ disabled: true }} />);
expect(baseElement.querySelector('.ant-modal-close')).toHaveAttribute('disabled');
});
it('render correctly', () => { it('render correctly', () => {
const { asFragment } = render(<ModalTester />); const { asFragment } = render(<ModalTester />);
expect(asFragment().firstChild).toMatchSnapshot(); expect(asFragment().firstChild).toMatchSnapshot();

View File

@ -53,7 +53,7 @@ Common props ref[Common props](/docs/react/common-props)
| cancelButtonProps | The cancel button props | [ButtonProps](/components/button/#api) | - | | | cancelButtonProps | The cancel button props | [ButtonProps](/components/button/#api) | - | |
| cancelText | Text of the Cancel button | ReactNode | `Cancel` | | | cancelText | Text of the Cancel button | ReactNode | `Cancel` | |
| centered | Centered Modal | boolean | false | | | centered | Centered Modal | boolean | false | |
| closable | Whether a close (x) button is visible on top right or not | boolean \| { closeIcon?: React.ReactNode } | true | | | closable | Whether a close (x) button is visible on top right or not | boolean \| { closeIcon?: React.ReactNode; disabled?: boolean; } | true | |
| closeIcon | Custom close icon. 5.7.0: close button will be hidden when setting to `null` or `false` | ReactNode | &lt;CloseOutlined /> | | | closeIcon | Custom close icon. 5.7.0: close button will be hidden when setting to `null` or `false` | ReactNode | &lt;CloseOutlined /> | |
| confirmLoading | Whether to apply loading visual effect for OK button or not | boolean | false | | | confirmLoading | Whether to apply loading visual effect for OK button or not | boolean | false | |
| destroyOnClose | Whether to unmount child components on onClose | boolean | false | | | destroyOnClose | Whether to unmount child components on onClose | boolean | false | |

View File

@ -54,7 +54,7 @@ demo:
| cancelButtonProps | cancel 按钮 props | [ButtonProps](/components/button-cn#api) | - | | | cancelButtonProps | cancel 按钮 props | [ButtonProps](/components/button-cn#api) | - | |
| cancelText | 取消按钮文字 | ReactNode | `取消` | | | cancelText | 取消按钮文字 | ReactNode | `取消` | |
| centered | 垂直居中展示 Modal | boolean | false | | | centered | 垂直居中展示 Modal | boolean | false | |
| closable | 是否显示右上角的关闭按钮 | boolean \| { closeIcon?: React.ReactNode } | true | | | closable | 是否显示右上角的关闭按钮 | boolean \| { closeIcon?: React.ReactNode; disabled?: boolean; } | true | |
| closeIcon | 自定义关闭图标。5.7.0:设置为 `null``false` 时隐藏关闭按钮 | ReactNode | &lt;CloseOutlined /> | | | closeIcon | 自定义关闭图标。5.7.0:设置为 `null``false` 时隐藏关闭按钮 | ReactNode | &lt;CloseOutlined /> | |
| confirmLoading | 确定按钮 loading | boolean | false | | | confirmLoading | 确定按钮 loading | boolean | false | |
| destroyOnClose | 关闭时销毁 Modal 里的子元素 | boolean | false | | | destroyOnClose | 关闭时销毁 Modal 里的子元素 | boolean | false | |

View File

@ -1,7 +1,6 @@
import type React from 'react'; import type React from 'react';
import type { DialogProps } from 'rc-dialog'; import type { DialogProps } from 'rc-dialog';
import type { ClosableType } from '../_util/hooks/useClosable';
import type { ButtonProps, LegacyButtonType } from '../button/button'; import type { ButtonProps, LegacyButtonType } from '../button/button';
import type { DirectionType } from '../config-provider'; import type { DirectionType } from '../config-provider';
@ -21,8 +20,6 @@ export interface ModalProps extends ModalCommonProps {
confirmLoading?: boolean; confirmLoading?: boolean;
/** The modal dialog's title */ /** The modal dialog's title */
title?: React.ReactNode; title?: React.ReactNode;
/** Whether a close (x) button is visible on top right of the modal dialog or not. Recommend to use closeIcon instead. */
closable?: ClosableType;
/** Specify a function that will be called when a user clicks the OK button */ /** Specify a function that will be called when a user clicks the OK button */
onOk?: (e: React.MouseEvent<HTMLButtonElement>) => void; onOk?: (e: React.MouseEvent<HTMLButtonElement>) => void;
/** Specify a function that will be called when a user clicks mask, close button on top right or Cancel button */ /** Specify a function that will be called when a user clicks mask, close button on top right or Cancel button */
@ -89,7 +86,6 @@ export interface ModalFuncProps extends ModalCommonProps {
/** @deprecated Please use `open` instead. */ /** @deprecated Please use `open` instead. */
visible?: boolean; visible?: boolean;
title?: React.ReactNode; title?: React.ReactNode;
closable?: ClosableType;
content?: React.ReactNode; content?: React.ReactNode;
// TODO: find out exact types // TODO: find out exact types
onOk?: (...args: any[]) => any; onOk?: (...args: any[]) => any;

View File

@ -287,6 +287,10 @@ const genModalStyle: GenerateStyle<ModalToken> = (token) => {
textRendering: 'auto', textRendering: 'auto',
}, },
'&:disabled': {
pointerEvents: 'none',
},
'&:hover': { '&:hover': {
color: token.modalCloseIconHoverColor, color: token.modalCloseIconHoverColor,
backgroundColor: token.colorBgTextHover, backgroundColor: token.colorBgTextHover,