Browse Source

feat: popconfirm onOk,onCancel support promise return, async close, c… (#1059)

* feat: popconfirm onOk,onCancel support promise return, async close, close #1056

* docs: update api define of popconfirm

Co-authored-by: 代强 <[email protected]>
pointhalo 3 years ago
parent
commit
f58ab903ff

+ 44 - 2
content/feedback/popconfirm/index-en-US.md

@@ -109,6 +109,48 @@ function TypesConfirmDemo(props = {}) {
 }
 ```
 
+
+### Delay hide
+
+`onOk` and `onCancel` can be closed after click through return Promise (supported after v2.19). When onCancel and onOk are triggered, the corresponding Button will automatically switch to `loading: true`  
+promise solve will close the bubble confirmation box, the bubble will remain when promise reject, and button loading will automatically switch to false  
+
+```jsx live=true
+import React from 'react';
+import { Popconfirm, Button, Toast } from '@douyinfe/semi-ui';
+
+() => {
+    const onConfirm = () => {
+        return new Promise((resolve, reject) => {
+            setTimeout(() => {
+                console.log('resolve, close popconfirm');
+                resolve();
+            }, 2000);
+        });
+    };
+
+    const onCancel = () => {
+        return new Promise((resolve, reject) => {
+            setTimeout(() => {
+                console.log('reject, popconfirm still exist');
+                reject();
+            }, 2000);
+        });
+    };
+
+    return (
+        <Popconfirm
+            title="Are you sure to save this modification?"
+            content="This modification will be irreversible"
+            onConfirm={onConfirm}
+            onCancel={onCancel}
+        >
+            <Button>Save</Button>
+        </Popconfirm>
+    );
+};
+```
+
 ### Use with Tooltip or Popover
 
 Please refer to [Use with Tooltip/Popover](/en-US/show/tooltip#Use-with-Popver-or-Popconfirm)
@@ -138,8 +180,8 @@ Please refer to [Use with Tooltip/Popover](/en-US/show/tooltip#Use-with-Popver-o
 | trigger            | Timing to trigger the display, optional value:hover / focus / click / custom                                                                                         | string                |   'click'                  |
 | visible            | Whether the bubble box displays controlled attributes                                                                                                                   | boolean                          |                     | **0.19.0**        |
 | zIndex             | Floating layer z-index value                                                                                                                                          | number                     | 1030                |
-| onConfirm          | Click the confirmation button to call back.                                                                                                                           | (e) => void                |                     |
-| onCancel           | Click the Cancel button to call back.                                                                                                                                 | (e) => void                |                     |
+| onConfirm          | Click the confirmation button to call back. Promise support after v2.19                                                                                                                           | (e) => void \| Promise                |                     |
+| onCancel           | Click the Cancel button to call back. Promise support after v2.19                                                                                                                          | (e) => void \| Promise                |                     |
 | onVisibleChange    | Bubble box toggle shows hidden callbacks                                                                                                                              | (visible: boolean) => void | () => {}            | **0.19.0**        |
 | onClickOutSide     | Callback when the pop-up layer is in the display state and the non-Children, non-floating layer inner area is clicked                                                 | (e: event) => void         |                     | **2.1.0**        |
 ## Design Tokens

+ 44 - 2
content/feedback/popconfirm/index.md

@@ -108,6 +108,48 @@ function TypesConfirmDemo(props = {}) {
 }
 ```
 
+### 延时关闭
+
+onOk、onCancel 可以通过 return Promise 实现点击后延时关闭 (v2.19后支持)。 onCancel、onOk 被触发时,对应的 Button 会自动切换为 loading: true  
+promise solve 会关闭气泡确认框, promise reject时气泡依然保留,同时 button loading 自动切换为 false
+
+```jsx live=true
+import React from 'react';
+import { Popconfirm, Button, Toast } from '@douyinfe/semi-ui';
+
+() => {
+    const onConfirm = () => {
+        return new Promise((resolve, reject) => {
+            setTimeout(() => {
+                console.log('resolve, close popconfirm');
+                resolve();
+            }, 2000);
+        });
+    };
+
+    const onCancel = () => {
+        return new Promise((resolve, reject) => {
+            setTimeout(() => {
+                console.log('reject, popconfirm still exist');
+                reject();
+            }, 2000);
+        });
+    };
+
+    return (
+        <Popconfirm
+            title="确定是否要保存此修改?"
+            content="此修改将不可逆"
+            onConfirm={onConfirm}
+            onCancel={onCancel}
+        >
+            <Button>保存</Button>
+        </Popconfirm>
+    );
+};
+```
+
+
 ### 搭配 Tooltip 或 Popover 使用
 
 请参考[搭配使用](/zh-CN/show/tooltip#%E6%90%AD%E9%85%8D-popover-%E6%88%96-popconfirm-%E4%BD%BF%E7%94%A8)
@@ -136,8 +178,8 @@ function TypesConfirmDemo(props = {}) {
 | trigger            | 触发展示的时机,可选值:hover / focus / click / custom                                                                                         | string                |   'click'                  |
 | visible            | 气泡框是否展示的受控属性                                                                                                                    | boolean                          |                     | **0.19.0**        |
 | zIndex             | 浮层 z-index 值                                                                                                                             | number                           | 1030                |
-| onConfirm          | 点击确认按钮回调                                                                                                                            | Function(e)                      |                     |
-| onCancel           | 点击取消按钮回调                                                                                                                            | Function(e)                      |                     |
+| onConfirm          | 点击确认按钮回调,  Promise类型于 v 2.19后支持                                                                                                                          | Function(e): void \| Promise                      |                     |
+| onCancel           | 点击取消按钮回调,Promise类型于 v 2.19后支持                                                                                                                            | Function(e): void \| Promise                      |                     |
 | onClickOutSide     | 当弹出层处于展示状态,点击非Children、非浮层内部区域时的回调                                                                                      | Function(e)                      |  **2.1.0**      |
 | onVisibleChange    | 气泡框切换显示隐藏的回调                                                                                                               | Function(visible: boolean): void | () => {}            | **0.19.0**        |
 

+ 35 - 6
packages/semi-foundation/popconfirm/popconfirmFoundation.ts

@@ -1,11 +1,14 @@
 /* eslint-disable @typescript-eslint/no-empty-function */
 
 import BaseFoundation, { DefaultAdapter } from '../base/foundation';
+import isPromise from '../utils/isPromise';
 
 export interface PopconfirmAdapter<P = Record<string, any>, S = Record<string, any>> extends DefaultAdapter<P, S> {
     setVisible: (visible: boolean) => void;
-    notifyConfirm: (e: any) => void;
-    notifyCancel: (e: any) => void;
+    updateConfirmLoading: (loading: boolean) => void;
+    updateCancelLoading: (loading: boolean) => void;
+    notifyConfirm: (e: any) => Promise<any> | void;
+    notifyCancel: (e: any) => Promise<any> | void;
     notifyVisibleChange: (visible: boolean) => void;
     notifyClickOutSide: (e: any) => void;
 }
@@ -17,13 +20,39 @@ export default class PopConfirmFoundation<P = Record<string, any>, S = Record<st
     destroy(): void {}
 
     handleCancel(e: any): void {
-        this._adapter.notifyCancel(e);
-        this.handleVisibleChange(false);
+        const maybePromise = this._adapter.notifyCancel(e) as Promise<any>;
+        if (isPromise(maybePromise)) {
+            this._adapter.updateCancelLoading(true);
+            maybePromise.then(
+                (result: any) => {
+                    this.handleVisibleChange(false);
+                    this._adapter.updateCancelLoading(false);
+                },
+                (errors: any) => {
+                    this._adapter.updateCancelLoading(false);
+                }
+            );
+        } else {
+            this.handleVisibleChange(false);
+        }
     }
 
     handleConfirm(e: any): void {
-        this._adapter.notifyConfirm(e);
-        this.handleVisibleChange(false);
+        const maybePromise = this._adapter.notifyConfirm(e) as Promise<any>;
+        if (isPromise(maybePromise)) {
+            this._adapter.updateConfirmLoading(true);
+            maybePromise.then(
+                (result: any) => {
+                    this._adapter.updateConfirmLoading(false);
+                    this.handleVisibleChange(false);
+                },
+                (errors: any) => {
+                    this._adapter.updateConfirmLoading(false);
+                }
+            );
+        } else {
+            this.handleVisibleChange(false);
+        }
     }
 
     handleClickOutSide(e: any): void {

+ 37 - 1
packages/semi-ui/popconfirm/_story/popconfirm.stories.js

@@ -167,4 +167,40 @@ export const ClickOutSideDemo = () => {
 
 ClickOutSideDemo.story = {
   name: 'ClickOutSideDemo',
-};
+};
+
+export const PromiseCallback = () => {
+  const onConfirm = () => {
+    return new Promise((resolve, reject) => {
+      setTimeout(() => {
+        console.log('ccc');
+        resolve(1);
+      }, 2000)
+    })
+  };
+
+  const onCancel = () => {
+    return new Promise((resolve, reject) => {
+      setTimeout(() => {
+        console.log('ccc');
+        reject(1);
+      }, 2000)
+    })
+  };
+
+  return (
+    <Popconfirm
+      title="确定是否要保存此修改?"
+      content="此修改将不可逆"
+      onConfirm={onConfirm}
+      onCancel={onCancel}
+    >
+      <Button>保存</Button>
+    </Popconfirm>
+  );
+};
+
+PromiseCallback.story = {
+  name: 'PromiseCallbackDemo',
+};
+

+ 14 - 6
packages/semi-ui/popconfirm/index.tsx

@@ -35,14 +35,16 @@ export interface PopconfirmProps extends PopoverProps {
     zIndex?: number;
     trigger?: Trigger;
     position?: Position;
-    onCancel?: (e: React.MouseEvent) => void;
-    onConfirm?: (e: React.MouseEvent) => void;
+    onCancel?: (e: React.MouseEvent) => Promise<any> | void;
+    onConfirm?: (e: React.MouseEvent) => Promise<any> | void;
     onVisibleChange?: (visible: boolean) => void;
     onClickOutSide?: (e: React.MouseEvent) => void;
 }
 
 export interface PopconfirmState {
     visible: boolean;
+    cancelLoading: boolean;
+    confirmLoading: boolean;
 }
 
 interface PopProps {
@@ -99,6 +101,8 @@ export default class Popconfirm extends BaseComponent<PopconfirmProps, Popconfir
         super(props);
 
         this.state = {
+            cancelLoading: false,
+            confirmLoading: false,
             visible: props.defaultVisible || false,
         };
 
@@ -122,8 +126,10 @@ export default class Popconfirm extends BaseComponent<PopconfirmProps, Popconfir
         return {
             ...super.adapter,
             setVisible: (visible: boolean): void => this.setState({ visible }),
-            notifyConfirm: (e: React.MouseEvent): void => this.props.onConfirm(e),
-            notifyCancel: (e: React.MouseEvent): void => this.props.onCancel(e),
+            updateConfirmLoading: (loading: boolean): void => this.setState({ confirmLoading: loading }),
+            updateCancelLoading: (loading: boolean): void => this.setState({ cancelLoading: loading }),
+            notifyConfirm: (e: React.MouseEvent): Promise<any> | void => this.props.onConfirm(e),
+            notifyCancel: (e: React.MouseEvent): Promise<any> | void => this.props.onCancel(e),
             notifyVisibleChange: (visible: boolean): void => this.props.onVisibleChange(visible),
             notifyClickOutSide: (e: React.MouseEvent) => this.props.onClickOutSide(e),
         };
@@ -141,14 +147,15 @@ export default class Popconfirm extends BaseComponent<PopconfirmProps, Popconfir
 
     renderControls() {
         const { okText, cancelText, okType, cancelType, cancelButtonProps, okButtonProps } = this.props;
+        const { cancelLoading, confirmLoading } = this.state;
         return (
             <LocaleConsumer componentName="Popconfirm">
                 {(locale: LocaleObject['Popconfirm'], localeCode: string) => (
                     <>
-                        <Button type={cancelType} onClick={this.handleCancel} {...cancelButtonProps}>
+                        <Button type={cancelType} onClick={this.handleCancel} loading={cancelLoading} {...cancelButtonProps}>
                             {cancelText || get(locale, 'cancel')}
                         </Button>
-                        <Button type={okType} theme="solid" onClick={this.handleConfirm} {...okButtonProps}>
+                        <Button type={okType} theme="solid" onClick={this.handleConfirm} loading={confirmLoading} {...okButtonProps}>
                             {okText || get(locale, 'confirm')}
                         </Button>
                     </>
@@ -171,6 +178,7 @@ export default class Popconfirm extends BaseComponent<PopconfirmProps, Popconfir
         const showContent = content !== null || typeof content !== 'undefined';
 
         return (
+            // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
             <div className={popCardCls} onClick={this.stopImmediatePropagation} style={style}>
                 <div className={`${prefixCls}-inner`}>
                     <div className={`${prefixCls}-header`}>