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: Modal footer support custom render function #44318

Merged
merged 9 commits into from Aug 28, 2023
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
96 changes: 48 additions & 48 deletions components/modal/ConfirmDialog.tsx
@@ -1,19 +1,23 @@
import * as React from 'react';
import CheckCircleFilled from '@ant-design/icons/CheckCircleFilled';
import CloseCircleFilled from '@ant-design/icons/CloseCircleFilled';
import ExclamationCircleFilled from '@ant-design/icons/ExclamationCircleFilled';
import InfoCircleFilled from '@ant-design/icons/InfoCircleFilled';
import classNames from 'classnames';
import * as React from 'react';
import ActionButton from '../_util/ActionButton';

import { getTransitionName } from '../_util/motion';
import warning from '../_util/warning';
import type { ThemeConfig } from '../config-provider';
import ConfigProvider from '../config-provider';
import { useLocale } from '../locale';
import Dialog from './Modal';
import CancelBtn from './components/ConfirmCancelBtn';
import OkBtn from './components/ConfirmOkBtn';
import type { ModalContextProps } from './context';
import { ModalContextProvider } from './context';
import type { ModalFuncProps, ModalLocale } from './interface';
import Dialog from './Modal';

interface ConfirmDialogProps extends ModalFuncProps {
export interface ConfirmDialogProps extends ModalFuncProps {
afterClose?: () => void;
close?: (...args: any[]) => void;
/**
Expand All @@ -23,7 +27,7 @@ interface ConfirmDialogProps extends ModalFuncProps {
*/
onConfirm?: (confirmed: boolean) => void;
autoFocusButton?: null | 'ok' | 'cancel';
rootPrefixCls: string;
rootPrefixCls?: string;
iconPrefixCls?: string;
theme?: ThemeConfig;

Expand All @@ -43,22 +47,15 @@ export function ConfirmContent(
) {
const {
icon,
onCancel,
onOk,
close,
onConfirm,
isSilent,
okText,
okButtonProps,
cancelText,
cancelButtonProps,
confirmPrefixCls,
rootPrefixCls,
type,
okCancel,
footer,
// Legacy for static function usage
locale: staticLocale,
...resetProps
} = props;

warning(
Expand Down Expand Up @@ -90,7 +87,6 @@ export function ConfirmContent(
}
}

const okType = props.okType || 'primary';
// 默认为 true,保持向下兼容
const mergedOkCancel = okCancel ?? type === 'confirm';

Expand All @@ -100,20 +96,26 @@ export function ConfirmContent(

const mergedLocale = staticLocale || locale;

const cancelButton = mergedOkCancel && (
<ActionButton
isSilent={isSilent}
actionFn={onCancel}
close={(...args: any[]) => {
close?.(...args);
onConfirm?.(false);
}}
autoFocus={autoFocusButton === 'cancel'}
buttonProps={cancelButtonProps}
prefixCls={`${rootPrefixCls}-btn`}
>
{cancelText || mergedLocale?.cancelText}
</ActionButton>
// ================== Locale Text ==================
const okTextLocale = okText || (mergedOkCancel ? mergedLocale?.okText : mergedLocale?.justOkText);
const cancelTextLocale = cancelText || mergedLocale?.cancelText;

// ================= Context Value =================
const btnCtxValue: ModalContextProps = {
autoFocusButton,
cancelTextLocale,
okTextLocale,
mergedOkCancel,
...resetProps,
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这两排属性声明了很多次,蛮占体积,是否是有必要的?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

我尝试去掉吧,再优化一下

const btnCtxValueMemo = React.useMemo(() => btnCtxValue, [...Object.values(btnCtxValue)]);

// ====================== Footer Origin Node ======================
const footerOriginNode = (
<>
<CancelBtn />
<OkBtn />
</>
);

return (
Expand All @@ -125,24 +127,18 @@ export function ConfirmContent(
)}
<div className={`${confirmPrefixCls}-content`}>{props.content}</div>
</div>
{footer === undefined ? (
<div className={`${confirmPrefixCls}-btns`}>
{cancelButton}
<ActionButton
isSilent={isSilent}
type={okType}
actionFn={onOk}
close={(...args: any[]) => {
close?.(...args);
onConfirm?.(true);
}}
autoFocus={autoFocusButton === 'ok'}
buttonProps={okButtonProps}
prefixCls={`${rootPrefixCls}-btn`}
>
{okText || (mergedOkCancel ? mergedLocale?.okText : mergedLocale?.justOkText)}
</ActionButton>
</div>

{footer === undefined || typeof footer === 'function' ? (
<ModalContextProvider value={btnCtxValueMemo}>
<div className={`${confirmPrefixCls}-btns`}>
{typeof footer === 'function'
? footer(footerOriginNode, {
OkBtn,
CancelBtn,
})
: footerOriginNode}
</div>
</ModalContextProvider>
) : (
footer
)}
Expand Down Expand Up @@ -215,8 +211,12 @@ const ConfirmDialog: React.FC<ConfirmDialogProps> = (props) => {
open={open}
title=""
footer={null}
transitionName={getTransitionName(rootPrefixCls, 'zoom', props.transitionName)}
maskTransitionName={getTransitionName(rootPrefixCls, 'fade', props.maskTransitionName)}
transitionName={getTransitionName(rootPrefixCls || '', 'zoom', props.transitionName)}
maskTransitionName={getTransitionName(
rootPrefixCls || '',
'fade',
props.maskTransitionName,
)}
mask={mask}
maskClosable={maskClosable}
maskStyle={maskStyle}
Expand Down
11 changes: 6 additions & 5 deletions components/modal/Modal.tsx
@@ -1,18 +1,19 @@
import * as React from 'react';
import CloseOutlined from '@ant-design/icons/CloseOutlined';
import classNames from 'classnames';
import Dialog from 'rc-dialog';
import * as React from 'react';

import useClosable from '../_util/hooks/useClosable';
import { getTransitionName } from '../_util/motion';
import { canUseDocElement } from '../_util/styleChecker';
import warning from '../_util/warning';
import { ConfigContext } from '../config-provider';
import { NoFormStyle } from '../form/context';
import { NoCompactStyle } from '../space/Compact';
import { usePanelRef } from '../watermark/context';
import type { ModalProps, MousePosition } from './interface';
import { Footer, renderCloseIcon } from './shared';
import useStyle from './style';
import { usePanelRef } from '../watermark/context';

let mousePosition: MousePosition;

Expand Down Expand Up @@ -93,9 +94,9 @@ const Modal: React.FC<ModalProps> = (props) => {
warning(!('visible' in props), 'Modal', '`visible` is deprecated, please use `open` instead.');
}

const dialogFooter =
footer === undefined ? <Footer {...props} onOk={handleOk} onCancel={handleCancel} /> : footer;

const dialogFooter = footer !== null && (
<Footer {...props} onOk={handleOk} onCancel={handleCancel} />
);
const [mergedClosable, mergedCloseIcon] = useClosable(
closable,
closeIcon,
Expand Down
19 changes: 18 additions & 1 deletion components/modal/__tests__/Modal.test.tsx
@@ -1,10 +1,11 @@
import React, { useEffect } from 'react';

import type { ModalProps } from '..';
import Modal from '..';
import { resetWarned } from '../../_util/warning';
import mountTest from '../../../tests/shared/mountTest';
import rtlTest from '../../../tests/shared/rtlTest';
import { fireEvent, render } from '../../../tests/utils';
import { resetWarned } from '../../_util/warning';

jest.mock('rc-util/lib/Portal');

Expand Down Expand Up @@ -133,4 +134,20 @@ describe('Modal', () => {
render(<Modal open footer={<div className="custom-footer">footer</div>} />);
expect(document.querySelector('.custom-footer')).toBeTruthy();
});

it('Should custom footer function work', () => {
render(
<Modal
open
footer={(_, { OkBtn, CancelBtn }) => (
<>
<OkBtn />
<CancelBtn />
<div className="custom-footer-ele">footer-ele</div>
</>
)}
/>,
);
expect(document.querySelector('.custom-footer-ele')).toBeTruthy();
});
});
34 changes: 34 additions & 0 deletions components/modal/__tests__/__snapshots__/demo-extend.test.ts.snap
Expand Up @@ -483,6 +483,40 @@ exports[`renders components/modal/demo/footer.tsx extend context correctly 1`] =

exports[`renders components/modal/demo/footer.tsx extend context correctly 2`] = `[]`;

exports[`renders components/modal/demo/footer-render.tsx extend context correctly 1`] = `
<div
class="ant-space ant-space-horizontal ant-space-align-center"
>
<div
class="ant-space-item"
style="margin-right: 8px;"
>
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
Open Modal
</span>
</button>
</div>
<div
class="ant-space-item"
>
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
Open Modal Confirm
</span>
</button>
</div>
</div>
`;

exports[`renders components/modal/demo/footer-render.tsx extend context correctly 2`] = `[]`;

exports[`renders components/modal/demo/hooks.tsx extend context correctly 1`] = `
<div
class="ant-space ant-space-horizontal ant-space-align-center"
Expand Down
32 changes: 32 additions & 0 deletions components/modal/__tests__/__snapshots__/demo.test.tsx.snap
Expand Up @@ -465,6 +465,38 @@ exports[`renders components/modal/demo/footer.tsx correctly 1`] = `
</button>
`;

exports[`renders components/modal/demo/footer-render.tsx correctly 1`] = `
<div
class="ant-space ant-space-horizontal ant-space-align-center"
>
<div
class="ant-space-item"
style="margin-right:8px"
>
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
Open Modal
</span>
</button>
</div>
<div
class="ant-space-item"
>
<button
class="ant-btn ant-btn-primary"
type="button"
>
<span>
Open Modal Confirm
</span>
</button>
</div>
</div>
`;

exports[`renders components/modal/demo/hooks.tsx correctly 1`] = `
<div
class="ant-space ant-space-horizontal ant-space-align-center"
Expand Down
20 changes: 19 additions & 1 deletion components/modal/__tests__/confirm.test.tsx
@@ -1,10 +1,11 @@
import * as React from 'react';
import { SmileOutlined } from '@ant-design/icons';
import CSSMotion from 'rc-motion';
import { genCSSMotion } from 'rc-motion/lib/CSSMotion';
import KeyCode from 'rc-util/lib/KeyCode';
import { resetWarned } from 'rc-util/lib/warning';
import * as React from 'react';
import TestUtils from 'react-dom/test-utils';

import type { ModalFuncProps } from '..';
import Modal from '..';
import { act, waitFakeTimer } from '../../../tests/utils';
Expand Down Expand Up @@ -845,4 +846,21 @@ describe('Modal.confirm triggers callbacks correctly', () => {

warnSpy.mockRestore();
});

it('Should custom footer function work width confirm', async () => {
Modal.confirm({
content: 'hai',
footer: (_, { OkBtn, CancelBtn }) => (
<>
<OkBtn />
<CancelBtn />
<div className="custom-footer-ele">footer-ele</div>
</>
),
});

await waitFakeTimer();

expect(document.querySelector('.custom-footer-ele')).toBeTruthy();
});
});