Skip to content

Commit

Permalink
feat: Modal footer support custom render function (ant-design#44318)
Browse files Browse the repository at this point in the history
  • Loading branch information
RedJue authored Aug 28, 2023
1 parent ef61160 commit e7c7601
Show file tree
Hide file tree
Showing 17 changed files with 482 additions and 76 deletions.
96 changes: 48 additions & 48 deletions components/modal/ConfirmDialog.tsx
Original file line number Diff line number Diff line change
@@ -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,
};
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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();
});
});
Loading

0 comments on commit e7c7601

Please sign in to comment.