mirror of
https://github.com/ant-design/ant-design.git
synced 2024-11-24 02:59:58 +08:00
feat: Modal footer support custom render function (#44318)
This commit is contained in:
parent
ef61160942
commit
e7c7601bc0
@ -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;
|
||||
/**
|
||||
@ -23,7 +27,7 @@ interface ConfirmDialogProps extends ModalFuncProps {
|
||||
*/
|
||||
onConfirm?: (confirmed: boolean) => void;
|
||||
autoFocusButton?: null | 'ok' | 'cancel';
|
||||
rootPrefixCls: string;
|
||||
rootPrefixCls?: string;
|
||||
iconPrefixCls?: string;
|
||||
theme?: ThemeConfig;
|
||||
|
||||
@ -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(
|
||||
@ -90,7 +87,6 @@ export function ConfirmContent(
|
||||
}
|
||||
}
|
||||
|
||||
const okType = props.okType || 'primary';
|
||||
// 默认为 true,保持向下兼容
|
||||
const mergedOkCancel = okCancel ?? type === 'confirm';
|
||||
|
||||
@ -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 (
|
||||
@ -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
|
||||
)}
|
||||
@ -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}
|
||||
|
@ -1,7 +1,8 @@
|
||||
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';
|
||||
@ -9,10 +10,10 @@ 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;
|
||||
|
||||
@ -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,
|
||||
|
@ -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');
|
||||
|
||||
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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';
|
||||
@ -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();
|
||||
});
|
||||
});
|
||||
|
54
components/modal/components/ConfirmCancelBtn.tsx
Normal file
54
components/modal/components/ConfirmCancelBtn.tsx
Normal file
@ -0,0 +1,54 @@
|
||||
import type { FC } from 'react';
|
||||
import React, { useContext } from 'react';
|
||||
|
||||
import ActionButton from '../../_util/ActionButton';
|
||||
import type { ConfirmDialogProps } from '../ConfirmDialog';
|
||||
import { ModalContext } from '../context';
|
||||
|
||||
export interface ConfirmCancelBtnProps
|
||||
extends Pick<
|
||||
ConfirmDialogProps,
|
||||
'cancelButtonProps' | 'isSilent' | 'rootPrefixCls' | 'close' | 'onConfirm' | 'onCancel'
|
||||
> {
|
||||
autoFocusButton?: false | 'ok' | 'cancel' | null;
|
||||
cancelTextLocale?:
|
||||
| string
|
||||
| number
|
||||
| true
|
||||
| React.ReactElement<any, string | React.JSXElementConstructor<any>>
|
||||
| Iterable<React.ReactNode>;
|
||||
mergedOkCancel?: boolean;
|
||||
}
|
||||
|
||||
const ConfirmCancelBtn: FC = () => {
|
||||
const {
|
||||
autoFocusButton,
|
||||
cancelButtonProps,
|
||||
cancelTextLocale,
|
||||
isSilent,
|
||||
mergedOkCancel,
|
||||
rootPrefixCls,
|
||||
close,
|
||||
onCancel,
|
||||
onConfirm,
|
||||
} = useContext(ModalContext);
|
||||
return (
|
||||
mergedOkCancel && (
|
||||
<ActionButton
|
||||
isSilent={isSilent}
|
||||
actionFn={onCancel}
|
||||
close={(...args: any[]) => {
|
||||
close?.(...args);
|
||||
onConfirm?.(false);
|
||||
}}
|
||||
autoFocus={autoFocusButton === 'cancel'}
|
||||
buttonProps={cancelButtonProps}
|
||||
prefixCls={`${rootPrefixCls}-btn`}
|
||||
>
|
||||
{cancelTextLocale}
|
||||
</ActionButton>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
export default ConfirmCancelBtn;
|
52
components/modal/components/ConfirmOkBtn.tsx
Normal file
52
components/modal/components/ConfirmOkBtn.tsx
Normal file
@ -0,0 +1,52 @@
|
||||
import type { FC } from 'react';
|
||||
import React, { useContext } from 'react';
|
||||
|
||||
import ActionButton from '../../_util/ActionButton';
|
||||
import type { ConfirmDialogProps } from '../ConfirmDialog';
|
||||
import { ModalContext } from '../context';
|
||||
|
||||
export interface ConfirmOkBtnProps
|
||||
extends Pick<
|
||||
ConfirmDialogProps,
|
||||
'close' | 'isSilent' | 'okType' | 'okButtonProps' | 'rootPrefixCls' | 'onConfirm' | 'onOk'
|
||||
> {
|
||||
autoFocusButton?: false | 'ok' | 'cancel' | null;
|
||||
okTextLocale?:
|
||||
| string
|
||||
| number
|
||||
| true
|
||||
| React.ReactElement<any, string | React.JSXElementConstructor<any>>
|
||||
| Iterable<React.ReactNode>;
|
||||
}
|
||||
|
||||
const ConfirmOkBtn: FC = () => {
|
||||
const {
|
||||
autoFocusButton,
|
||||
close,
|
||||
isSilent,
|
||||
okButtonProps,
|
||||
rootPrefixCls,
|
||||
okTextLocale,
|
||||
okType,
|
||||
onConfirm,
|
||||
onOk,
|
||||
} = useContext(ModalContext);
|
||||
return (
|
||||
<ActionButton
|
||||
isSilent={isSilent}
|
||||
type={okType || 'primary'}
|
||||
actionFn={onOk}
|
||||
close={(...args: any[]) => {
|
||||
close?.(...args);
|
||||
onConfirm?.(true);
|
||||
}}
|
||||
autoFocus={autoFocusButton === 'ok'}
|
||||
buttonProps={okButtonProps}
|
||||
prefixCls={`${rootPrefixCls}-btn`}
|
||||
>
|
||||
{okTextLocale}
|
||||
</ActionButton>
|
||||
);
|
||||
};
|
||||
|
||||
export default ConfirmOkBtn;
|
26
components/modal/components/NormalCancelBtn.tsx
Normal file
26
components/modal/components/NormalCancelBtn.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import type { FC } from 'react';
|
||||
import React, { useContext } from 'react';
|
||||
|
||||
import Button from '../../button';
|
||||
import { ModalContext } from '../context';
|
||||
import type { ModalProps } from '../interface';
|
||||
|
||||
export interface NormalCancelBtnProps extends Pick<ModalProps, 'cancelButtonProps' | 'onCancel'> {
|
||||
cancelTextLocale?:
|
||||
| string
|
||||
| number
|
||||
| true
|
||||
| React.ReactElement<any, string | React.JSXElementConstructor<any>>
|
||||
| Iterable<React.ReactNode>;
|
||||
}
|
||||
|
||||
const NormalCancelBtn: FC = () => {
|
||||
const { cancelButtonProps, cancelTextLocale, onCancel } = useContext(ModalContext);
|
||||
return (
|
||||
<Button onClick={onCancel} {...cancelButtonProps}>
|
||||
{cancelTextLocale}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export default NormalCancelBtn;
|
33
components/modal/components/NormalOkBtn.tsx
Normal file
33
components/modal/components/NormalOkBtn.tsx
Normal file
@ -0,0 +1,33 @@
|
||||
import type { FC } from 'react';
|
||||
import React, { useContext } from 'react';
|
||||
|
||||
import Button from '../../button';
|
||||
import { convertLegacyProps } from '../../button/button';
|
||||
import { ModalContext } from '../context';
|
||||
import type { ModalProps } from '../interface';
|
||||
|
||||
export interface NormalOkBtnProps
|
||||
extends Pick<ModalProps, 'confirmLoading' | 'okType' | 'okButtonProps' | 'onOk'> {
|
||||
okTextLocale?:
|
||||
| string
|
||||
| number
|
||||
| true
|
||||
| React.ReactElement<any, string | React.JSXElementConstructor<any>>
|
||||
| Iterable<React.ReactNode>;
|
||||
}
|
||||
|
||||
const NormalOkBtn: FC = () => {
|
||||
const { confirmLoading, okButtonProps, okType, okTextLocale, onOk } = useContext(ModalContext);
|
||||
return (
|
||||
<Button
|
||||
{...convertLegacyProps(okType)}
|
||||
loading={confirmLoading}
|
||||
onClick={onOk}
|
||||
{...okButtonProps}
|
||||
>
|
||||
{okTextLocale}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export default NormalOkBtn;
|
15
components/modal/context.ts
Normal file
15
components/modal/context.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
|
||||
import type { ConfirmCancelBtnProps } from './components/ConfirmCancelBtn';
|
||||
import type { ConfirmOkBtnProps } from './components/ConfirmOkBtn';
|
||||
import type { NormalCancelBtnProps } from './components/NormalCancelBtn';
|
||||
import type { NormalOkBtnProps } from './components/NormalOkBtn';
|
||||
|
||||
export type ModalContextProps = NormalCancelBtnProps &
|
||||
NormalOkBtnProps &
|
||||
ConfirmOkBtnProps &
|
||||
ConfirmCancelBtnProps;
|
||||
|
||||
export const ModalContext = React.createContext<ModalContextProps>({} as ModalContextProps);
|
||||
|
||||
export const { Provider: ModalContextProvider } = ModalContext;
|
7
components/modal/demo/footer-render.md
Normal file
7
components/modal/demo/footer-render.md
Normal file
@ -0,0 +1,7 @@
|
||||
## zh-CN
|
||||
|
||||
自定义页脚渲染函数,支持在原有基础上进行扩展。
|
||||
|
||||
## en-US
|
||||
|
||||
Customize the footer rendering function to support extensions on top of the original.
|
65
components/modal/demo/footer-render.tsx
Normal file
65
components/modal/demo/footer-render.tsx
Normal file
@ -0,0 +1,65 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Button, Modal, Space } from 'antd';
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
const showModal = () => {
|
||||
setOpen(true);
|
||||
};
|
||||
const handleOk = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
setOpen(false);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<Space>
|
||||
<Button type="primary" onClick={showModal}>
|
||||
Open Modal
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
Modal.confirm({
|
||||
title: 'Confirm',
|
||||
content: 'Bla bla ...',
|
||||
footer: (_, { OkBtn, CancelBtn }) => (
|
||||
<>
|
||||
<Button>Custom Button</Button>
|
||||
<CancelBtn />
|
||||
<OkBtn />
|
||||
</>
|
||||
),
|
||||
});
|
||||
}}
|
||||
>
|
||||
Open Modal Confirm
|
||||
</Button>
|
||||
</Space>
|
||||
<Modal
|
||||
open={open}
|
||||
title="Title"
|
||||
onOk={handleOk}
|
||||
onCancel={handleCancel}
|
||||
footer={(_, { OkBtn, CancelBtn }) => (
|
||||
<>
|
||||
<Button>Custom Button</Button>
|
||||
<CancelBtn />
|
||||
<OkBtn />
|
||||
</>
|
||||
)}
|
||||
>
|
||||
<p>Some contents...</p>
|
||||
<p>Some contents...</p>
|
||||
<p>Some contents...</p>
|
||||
<p>Some contents...</p>
|
||||
<p>Some contents...</p>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
@ -22,6 +22,7 @@ Additionally, if you need show a simple confirmation dialog, you can use [`App.u
|
||||
<code src="./demo/basic.tsx">Basic</code>
|
||||
<code src="./demo/async.tsx">Asynchronously close</code>
|
||||
<code src="./demo/footer.tsx">Customized Footer</code>
|
||||
<code src="./demo/footer-render.tsx">Customized Footer render function</code>
|
||||
<code src="./demo/hooks.tsx">Use hooks to get context</code>
|
||||
<code src="./demo/locale.tsx">Internationalization</code>
|
||||
<code src="./demo/manual.tsx">Manual to update destroy</code>
|
||||
@ -53,7 +54,7 @@ Common props ref:[Common props](/docs/react/common-props)
|
||||
| confirmLoading | Whether to apply loading visual effect for OK button or not | boolean | false | |
|
||||
| destroyOnClose | Whether to unmount child components on onClose | boolean | false | |
|
||||
| focusTriggerAfterClose | Whether need to focus trigger element after dialog is closed | boolean | true | 4.9.0 |
|
||||
| footer | Footer content, set as `footer={null}` when you don't need default buttons | ReactNode | (OK and Cancel buttons) | |
|
||||
| footer | Footer content, set as `footer={null}` when you don't need default buttons | (params:[footerRenderParams](/components/modal-cn#footerrenderparams))=> React.ReactNode \| React.ReactNode | (OK and Cancel buttons) | |
|
||||
| forceRender | Force render Modal | boolean | false | |
|
||||
| getContainer | The mounted node for Modal but still display at fullscreen | HTMLElement \| () => HTMLElement \| Selectors \| false | document.body | |
|
||||
| keyboard | Whether support press esc to close | boolean | true | |
|
||||
@ -103,7 +104,7 @@ The items listed above are all functions, expecting a settings object as paramet
|
||||
| className | The className of container | string | - | |
|
||||
| closeIcon | Custom close icon. 5.7.0: close button will be hidden when setting to `null` or `false` | boolean \| ReactNode | <CloseOutlined /> | |
|
||||
| content | Content | ReactNode | - | |
|
||||
| footer | Footer content, set as `footer: null` when you don't need default buttons | ReactNode | - | 5.1.0 |
|
||||
| footer | Footer content, set as `footer: null` when you don't need default buttons | (params:[footerRenderParams](/components/modal-cn#footerrenderparams))=> React.ReactNode \| React.ReactNode | - | 5.9.0 |
|
||||
| getContainer | Return the mount node for Modal | HTMLElement \| () => HTMLElement \| Selectors \| false | document.body | |
|
||||
| icon | Custom icon | ReactNode | <ExclamationCircleFilled /> | |
|
||||
| keyboard | Whether support press esc to close | boolean | true | |
|
||||
@ -180,6 +181,14 @@ return <div>{contextHolder}</div>;
|
||||
const confirmed = await modal.confirm({ ... });
|
||||
```
|
||||
|
||||
## footerRenderParams
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
| Property | Description | Type | Default |
|
||||
| --- | --- | --- | --- |
|
||||
| originNode | default node | React.ReactNode | - |
|
||||
| extra | extended options | { OkBtn: FC; CancelBtn: FC } | - |
|
||||
|
||||
## Design Token
|
||||
|
||||
<ComponentTokenTable component="Modal"></ComponentTokenTable>
|
||||
|
@ -23,6 +23,7 @@ demo:
|
||||
<code src="./demo/basic.tsx">基本</code>
|
||||
<code src="./demo/async.tsx">异步关闭</code>
|
||||
<code src="./demo/footer.tsx">自定义页脚</code>
|
||||
<code src="./demo/footer-render.tsx">自定义页脚渲染函数</code>
|
||||
<code src="./demo/hooks.tsx">使用 hooks 获得上下文</code>
|
||||
<code src="./demo/locale.tsx">国际化</code>
|
||||
<code src="./demo/manual.tsx">手动更新和移除</code>
|
||||
@ -54,7 +55,7 @@ demo:
|
||||
| confirmLoading | 确定按钮 loading | boolean | false | |
|
||||
| destroyOnClose | 关闭时销毁 Modal 里的子元素 | boolean | false | |
|
||||
| focusTriggerAfterClose | 对话框关闭后是否需要聚焦触发元素 | boolean | true | 4.9.0 |
|
||||
| footer | 底部内容,当不需要默认底部按钮时,可以设为 `footer={null}` | ReactNode | (确定取消按钮) | |
|
||||
| footer | 底部内容,当不需要默认底部按钮时,可以设为 `footer={null}` | (params:[footerRenderParams](/components/modal-cn#footerrenderparams))=> React.ReactNode \| React.ReactNode | (确定取消按钮) | 5.9.0 |
|
||||
| forceRender | 强制渲染 Modal | boolean | false | |
|
||||
| getContainer | 指定 Modal 挂载的节点,但依旧为全屏展示,`false` 为挂载在当前位置 | HTMLElement \| () => HTMLElement \| Selectors \| false | document.body | |
|
||||
| keyboard | 是否支持键盘 esc 关闭 | boolean | true | |
|
||||
@ -104,7 +105,7 @@ demo:
|
||||
| className | 容器类名 | string | - | |
|
||||
| closeIcon | 自定义关闭图标。5.7.0:设置为 `null` 或 `false` 时隐藏关闭按钮 | boolean \| ReactNode | <CloseOutlined /> | |
|
||||
| content | 内容 | ReactNode | - | |
|
||||
| footer | 底部内容,当不需要默认底部按钮时,可以设为 `footer: null` | ReactNode | - | 5.1.0 |
|
||||
| footer | 底部内容,当不需要默认底部按钮时,可以设为 `footer: null` | (params:[footerRenderParams](/components/modal-cn#footerrenderparams))=> React.ReactNode \| React.ReactNode | - | 5.9.0 |
|
||||
| getContainer | 指定 Modal 挂载的 HTML 节点, false 为挂载在当前 dom | HTMLElement \| () => HTMLElement \| Selectors \| false | document.body | |
|
||||
| icon | 自定义图标 | ReactNode | <ExclamationCircleFilled /> | |
|
||||
| keyboard | 是否支持键盘 esc 关闭 | boolean | true | |
|
||||
@ -181,6 +182,14 @@ return <div>{contextHolder}</div>;
|
||||
const confirmed = await modal.confirm({ ... });
|
||||
```
|
||||
|
||||
## footerRenderParams
|
||||
|
||||
<!-- prettier-ignore -->
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
| --- | --- | --- | --- |
|
||||
| originNode | 默认节点 | React.ReactNode | - |
|
||||
| extra | 扩展选项 | { OkBtn: FC; CancelBtn: FC } | - |
|
||||
|
||||
## Design Token
|
||||
|
||||
<ComponentTokenTable component="Modal"></ComponentTokenTable>
|
||||
|
@ -1,6 +1,12 @@
|
||||
import type { FC } from 'react';
|
||||
|
||||
import type { ButtonProps, LegacyButtonType } from '../button/button';
|
||||
import type { DirectionType } from '../config-provider';
|
||||
|
||||
export type ModalFooterRender = (
|
||||
originNode: React.ReactNode,
|
||||
extra: { OkBtn: FC; CancelBtn: FC },
|
||||
) => React.ReactNode;
|
||||
export interface ModalProps {
|
||||
/** Whether the modal dialog is visible or not */
|
||||
open?: boolean;
|
||||
@ -22,7 +28,7 @@ export interface ModalProps {
|
||||
/** Width of the modal dialog */
|
||||
width?: string | number;
|
||||
/** Footer content */
|
||||
footer?: React.ReactNode;
|
||||
footer?: ModalFooterRender | React.ReactNode;
|
||||
/** Text of the OK button */
|
||||
okText?: React.ReactNode;
|
||||
/** Button `type` of the OK button */
|
||||
@ -101,7 +107,7 @@ export interface ModalFuncProps {
|
||||
direction?: DirectionType;
|
||||
bodyStyle?: React.CSSProperties;
|
||||
closeIcon?: React.ReactNode;
|
||||
footer?: React.ReactNode;
|
||||
footer?: ModalProps['footer'];
|
||||
modalRender?: (node: React.ReactNode) => React.ReactNode;
|
||||
focusTriggerAfterClose?: boolean;
|
||||
}
|
||||
|
@ -1,9 +1,12 @@
|
||||
import CloseOutlined from '@ant-design/icons/CloseOutlined';
|
||||
import React from 'react';
|
||||
import Button from '../button';
|
||||
import { convertLegacyProps } from '../button/button';
|
||||
import CloseOutlined from '@ant-design/icons/CloseOutlined';
|
||||
|
||||
import { DisabledContextProvider } from '../config-provider/DisabledContext';
|
||||
import { useLocale } from '../locale';
|
||||
import NormalCancelBtn from './components/NormalCancelBtn';
|
||||
import NormalOkBtn from './components/NormalOkBtn';
|
||||
import type { ModalContextProps } from './context';
|
||||
import { ModalContextProvider } from './context';
|
||||
import type { ModalProps } from './interface';
|
||||
import { getConfirmLocale } from './locale';
|
||||
|
||||
@ -42,23 +45,48 @@ export const Footer: React.FC<
|
||||
onCancel,
|
||||
okButtonProps,
|
||||
cancelButtonProps,
|
||||
footer,
|
||||
} = props;
|
||||
|
||||
const [locale] = useLocale('Modal', getConfirmLocale());
|
||||
|
||||
return (
|
||||
// ================== Locale Text ==================
|
||||
const okTextLocale = okText || locale?.okText;
|
||||
const cancelTextLocale = cancelText || locale?.cancelText;
|
||||
|
||||
// ================= Context Value =================
|
||||
const btnCtxValue: ModalContextProps = {
|
||||
confirmLoading,
|
||||
okButtonProps,
|
||||
cancelButtonProps,
|
||||
okTextLocale,
|
||||
cancelTextLocale,
|
||||
okType,
|
||||
onOk,
|
||||
onCancel,
|
||||
};
|
||||
|
||||
const btnCtxValueMemo = React.useMemo(() => btnCtxValue, [...Object.values(btnCtxValue)]);
|
||||
|
||||
const footerOriginNode = (
|
||||
<>
|
||||
<NormalCancelBtn />
|
||||
<NormalOkBtn />
|
||||
</>
|
||||
);
|
||||
|
||||
return footer === undefined || typeof footer === 'function' ? (
|
||||
<DisabledContextProvider disabled={false}>
|
||||
<Button onClick={onCancel} {...cancelButtonProps}>
|
||||
{cancelText || locale?.cancelText}
|
||||
</Button>
|
||||
<Button
|
||||
{...convertLegacyProps(okType)}
|
||||
loading={confirmLoading}
|
||||
onClick={onOk}
|
||||
{...okButtonProps}
|
||||
>
|
||||
{okText || locale?.okText}
|
||||
</Button>
|
||||
<ModalContextProvider value={btnCtxValueMemo}>
|
||||
{typeof footer === 'function'
|
||||
? footer(footerOriginNode, {
|
||||
OkBtn: NormalOkBtn,
|
||||
CancelBtn: NormalCancelBtn,
|
||||
})
|
||||
: footerOriginNode}
|
||||
</ModalContextProvider>
|
||||
</DisabledContextProvider>
|
||||
) : (
|
||||
footer
|
||||
);
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user