mirror of
https://github.com/ant-design/ant-design.git
synced 2024-11-24 11:10:01 +08:00
feat(module:popconfirm): support closing based on promise (#30871)
* feat(module:popconfirm): support closing based on promise fix: typos fix: fix tests fix: fix ActionButton for Popconfirm * chore: cleanup * test: add test
This commit is contained in:
parent
2e65f276de
commit
5ddd9e6b97
@ -5,10 +5,16 @@ import { LegacyButtonType, ButtonProps, convertLegacyProps } from '../button/but
|
||||
export interface ActionButtonProps {
|
||||
type?: LegacyButtonType;
|
||||
actionFn?: (...args: any[]) => any | PromiseLike<any>;
|
||||
closeModal: Function;
|
||||
close: Function;
|
||||
autoFocus?: boolean;
|
||||
prefixCls: string;
|
||||
buttonProps?: ButtonProps;
|
||||
emitEvent?: boolean;
|
||||
quitOnNullishReturnValue?: boolean;
|
||||
}
|
||||
|
||||
function isThenable(thing?: PromiseLike<any>): boolean {
|
||||
return !!(thing && !!thing.then);
|
||||
}
|
||||
|
||||
const ActionButton: React.FC<ActionButtonProps> = props => {
|
||||
@ -30,16 +36,16 @@ const ActionButton: React.FC<ActionButtonProps> = props => {
|
||||
}, []);
|
||||
|
||||
const handlePromiseOnOk = (returnValueOfOnOk?: PromiseLike<any>) => {
|
||||
const { closeModal } = props;
|
||||
if (!returnValueOfOnOk || !returnValueOfOnOk.then) {
|
||||
const { close } = props;
|
||||
if (!isThenable(returnValueOfOnOk)) {
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
returnValueOfOnOk.then(
|
||||
returnValueOfOnOk!.then(
|
||||
(...args: any[]) => {
|
||||
// It's unnecessary to set loading=false, for the Modal will be unmounted after close.
|
||||
// setState({ loading: false });
|
||||
closeModal(...args);
|
||||
setLoading(false);
|
||||
close(...args);
|
||||
clickedRef.current = false;
|
||||
},
|
||||
(e: Error) => {
|
||||
// Emit error when catch promise reject
|
||||
@ -52,25 +58,32 @@ const ActionButton: React.FC<ActionButtonProps> = props => {
|
||||
);
|
||||
};
|
||||
|
||||
const onClick = () => {
|
||||
const { actionFn, closeModal } = props;
|
||||
const onClick = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
const { actionFn, close } = props;
|
||||
if (clickedRef.current) {
|
||||
return;
|
||||
}
|
||||
clickedRef.current = true;
|
||||
if (!actionFn) {
|
||||
closeModal();
|
||||
close();
|
||||
return;
|
||||
}
|
||||
let returnValueOfOnOk;
|
||||
if (actionFn.length) {
|
||||
returnValueOfOnOk = actionFn(closeModal);
|
||||
if (props.emitEvent) {
|
||||
returnValueOfOnOk = actionFn(e);
|
||||
if (props.quitOnNullishReturnValue && !isThenable(returnValueOfOnOk)) {
|
||||
clickedRef.current = false;
|
||||
close(e);
|
||||
return;
|
||||
}
|
||||
} else if (actionFn.length) {
|
||||
returnValueOfOnOk = actionFn(close);
|
||||
// https://github.com/ant-design/ant-design/issues/23358
|
||||
clickedRef.current = false;
|
||||
} else {
|
||||
returnValueOfOnOk = actionFn();
|
||||
if (!returnValueOfOnOk) {
|
||||
closeModal();
|
||||
close();
|
||||
return;
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import Dialog, { ModalFuncProps } from './Modal';
|
||||
import ActionButton from './ActionButton';
|
||||
import ActionButton from '../_util/ActionButton';
|
||||
import devWarning from '../_util/devWarning';
|
||||
import ConfigProvider from '../config-provider';
|
||||
import { getTransitionName } from '../_util/motion';
|
||||
@ -68,7 +68,7 @@ const ConfirmDialog = (props: ConfirmDialogProps) => {
|
||||
const cancelButton = okCancel && (
|
||||
<ActionButton
|
||||
actionFn={onCancel}
|
||||
closeModal={close}
|
||||
close={close}
|
||||
autoFocus={autoFocusButton === 'cancel'}
|
||||
buttonProps={cancelButtonProps}
|
||||
prefixCls={`${rootPrefixCls}-btn`}
|
||||
@ -118,7 +118,7 @@ const ConfirmDialog = (props: ConfirmDialogProps) => {
|
||||
<ActionButton
|
||||
type={okType}
|
||||
actionFn={onOk}
|
||||
closeModal={close}
|
||||
close={close}
|
||||
autoFocus={autoFocusButton === 'ok'}
|
||||
buttonProps={okButtonProps}
|
||||
prefixCls={`${rootPrefixCls}-btn`}
|
||||
|
@ -11,7 +11,7 @@ title:
|
||||
|
||||
## en-US
|
||||
|
||||
Asynchronously close a modal dialog when a the OK button is pressed. For example, you can use this pattern when you submit a form.
|
||||
Asynchronously close a modal dialog when the OK button is pressed. For example, you can use this pattern when you submit a form.
|
||||
|
||||
```jsx
|
||||
import { Modal, Button } from 'antd';
|
||||
|
@ -9,7 +9,6 @@ import confirm, {
|
||||
modalGlobalConfig,
|
||||
} from './confirm';
|
||||
|
||||
export { ActionButtonProps } from './ActionButton';
|
||||
export { ModalProps, ModalFuncProps } from './Modal';
|
||||
|
||||
function modalWarn(props: ModalFuncProps) {
|
||||
|
@ -179,3 +179,14 @@ exports[`renders ./components/popconfirm/demo/placement.md correctly 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders ./components/popconfirm/demo/promise.md correctly 1`] = `
|
||||
<button
|
||||
class="ant-btn ant-btn-primary"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Open Popconfirm with Promise
|
||||
</span>
|
||||
</button>
|
||||
`;
|
||||
|
@ -131,6 +131,24 @@ describe('Popconfirm', () => {
|
||||
expect(onVisibleChange).toHaveBeenLastCalledWith(false, eventObject);
|
||||
});
|
||||
|
||||
it('should support onConfirm to return Promise', async () => {
|
||||
const confirm = () => new Promise(res => setTimeout(res, 300));
|
||||
const onVisibleChange = jest.fn();
|
||||
const popconfirm = mount(
|
||||
<Popconfirm title="code" onConfirm={confirm} onVisibleChange={onVisibleChange}>
|
||||
<span>show me your code</span>
|
||||
</Popconfirm>,
|
||||
);
|
||||
|
||||
const triggerNode = popconfirm.find('span').at(0);
|
||||
triggerNode.simulate('click');
|
||||
expect(onVisibleChange).toHaveBeenCalledTimes(1);
|
||||
|
||||
popconfirm.find('.ant-btn').at(0).simulate('click');
|
||||
await sleep(400);
|
||||
expect(onVisibleChange).toHaveBeenCalledWith(false, eventObject);
|
||||
});
|
||||
|
||||
it('should support customize icon', () => {
|
||||
const wrapper = mount(
|
||||
<Popconfirm title="code" icon={<span className="customize-icon">custom-icon</span>}>
|
||||
|
37
components/popconfirm/demo/promise.md
Normal file
37
components/popconfirm/demo/promise.md
Normal file
@ -0,0 +1,37 @@
|
||||
---
|
||||
order: 7
|
||||
title:
|
||||
zh-CN: 基于 Promise 的异步关闭
|
||||
en-US: Asynchronously close on Promise
|
||||
---
|
||||
|
||||
## zh-CN
|
||||
|
||||
点击确定后异步关闭 Popconfirm,例如提交表单。
|
||||
|
||||
## en-US
|
||||
|
||||
Asynchronously close a popconfirm when the OK button is pressed. For example, you can use this pattern when you submit a form.
|
||||
|
||||
```jsx
|
||||
import { Button, Popconfirm } from 'antd';
|
||||
|
||||
const App = () => {
|
||||
const confirm = () =>
|
||||
new Promise(resolve => {
|
||||
setTimeout(() => resolve(), 3000);
|
||||
});
|
||||
|
||||
return (
|
||||
<Popconfirm
|
||||
title="Title"
|
||||
onConfirm={confirm}
|
||||
onVisibleChange={() => console.log('visible change')}
|
||||
>
|
||||
<Button type="primary">Open Popconfirm with Promise</Button>
|
||||
</Popconfirm>
|
||||
);
|
||||
};
|
||||
|
||||
ReactDOM.render(<App />, mountNode);
|
||||
```
|
@ -12,6 +12,7 @@ import { ConfigContext } from '../config-provider';
|
||||
import { getRenderPropValue, RenderFunction } from '../_util/getRenderPropValue';
|
||||
import { cloneElement } from '../_util/reactNode';
|
||||
import { getTransitionName } from '../_util/motion';
|
||||
import ActionButton from '../_util/ActionButton';
|
||||
|
||||
export interface PopconfirmProps extends AbstractTooltipProps {
|
||||
title: React.ReactNode | RenderFunction;
|
||||
@ -40,6 +41,7 @@ export interface PopconfirmLocale {
|
||||
}
|
||||
|
||||
const Popconfirm = React.forwardRef<unknown, PopconfirmProps>((props, ref) => {
|
||||
const { getPrefixCls } = React.useContext(ConfigContext);
|
||||
const [visible, setVisible] = useMergedState(false, {
|
||||
value: props.visible,
|
||||
defaultValue: props.defaultVisible,
|
||||
@ -54,11 +56,12 @@ const Popconfirm = React.forwardRef<unknown, PopconfirmProps>((props, ref) => {
|
||||
props.onVisibleChange?.(value, e);
|
||||
};
|
||||
|
||||
const onConfirm = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
const close = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
settingVisible(false, e);
|
||||
props.onConfirm?.call(this, e);
|
||||
};
|
||||
|
||||
const onConfirm = (e: React.MouseEvent<HTMLButtonElement>) => props.onConfirm?.call(this, e);
|
||||
|
||||
const onCancel = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
settingVisible(false, e);
|
||||
props.onCancel?.call(this, e);
|
||||
@ -90,21 +93,21 @@ const Popconfirm = React.forwardRef<unknown, PopconfirmProps>((props, ref) => {
|
||||
<Button onClick={onCancel} size="small" {...cancelButtonProps}>
|
||||
{cancelText || popconfirmLocale.cancelText}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={onConfirm}
|
||||
{...convertLegacyProps(okType)}
|
||||
size="small"
|
||||
{...okButtonProps}
|
||||
<ActionButton
|
||||
buttonProps={{ size: 'small', ...convertLegacyProps(okType), ...okButtonProps }}
|
||||
actionFn={onConfirm}
|
||||
close={close}
|
||||
prefixCls={getPrefixCls('btn')}
|
||||
quitOnNullishReturnValue
|
||||
emitEvent
|
||||
>
|
||||
{okText || popconfirmLocale.okText}
|
||||
</Button>
|
||||
</ActionButton>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const { getPrefixCls } = React.useContext(ConfigContext);
|
||||
|
||||
const {
|
||||
prefixCls: customizePrefixCls,
|
||||
placement,
|
||||
|
Loading…
Reference in New Issue
Block a user