From 61c19a81c20052242d91fcac18f925e09008ab1f Mon Sep 17 00:00:00 2001
From: tangjinzhou <415800467@qq.com>
Date: Wed, 30 Jun 2021 13:48:02 +0800
Subject: [PATCH] refactor: button

---
 components/button/button-group.tsx |  12 +--
 components/button/button.tsx       | 118 ++++++++++++++++-------------
 components/button/buttonTypes.ts   |   2 +-
 components/button/index.ts         |   2 +
 components/button/style/index.less |  42 ++++------
 components/modal/Modal.tsx         |   4 +-
 components/popconfirm/index.tsx    |   2 +-
 v2-doc                             |   2 +-
 8 files changed, 95 insertions(+), 89 deletions(-)

diff --git a/components/button/button-group.tsx b/components/button/button-group.tsx
index f6a897d6d..66494438d 100644
--- a/components/button/button-group.tsx
+++ b/components/button/button-group.tsx
@@ -1,4 +1,4 @@
-import { defineComponent } from 'vue';
+import { computed, defineComponent } from 'vue';
 import { flattenChildren } from '../_util/props-util';
 import PropTypes from '../_util/vue-types';
 import useConfigInject from '../_util/hooks/useConfigInject';
@@ -21,10 +21,8 @@ export default defineComponent({
   props: buttonGroupProps,
   setup(props, { slots }) {
     const { prefixCls, direction } = useConfigInject('btn-group', props);
-
-    return () => {
+    const classes = computed(() => {
       const { size } = props;
-
       // large => lg
       // small => sm
       let sizeCls = '';
@@ -38,12 +36,14 @@ export default defineComponent({
         default:
           break;
       }
-      const classes = {
+      return {
         [`${prefixCls.value}`]: true,
         [`${prefixCls.value}-${sizeCls}`]: sizeCls,
         [`${prefixCls.value}-rtl`]: direction.value === 'rtl',
       };
-      return <div class={classes}>{flattenChildren(slots.default?.())}</div>;
+    });
+    return () => {
+      return <div class={classes.value}>{flattenChildren(slots.default?.())}</div>;
     };
   },
 });
diff --git a/components/button/button.tsx b/components/button/button.tsx
index 7c39538eb..8929d7ace 100644
--- a/components/button/button.tsx
+++ b/components/button/button.tsx
@@ -4,6 +4,7 @@ import {
   onBeforeUnmount,
   onMounted,
   onUpdated,
+  Ref,
   ref,
   Text,
   watch,
@@ -19,6 +20,8 @@ import devWarning from '../vc-util/devWarning';
 import type { ButtonType } from './buttonTypes';
 import type { VNode } from 'vue';
 
+type Loading = boolean | number;
+
 const rxTwoCNChar = /^[\u4e00-\u9fa5]{2}$/;
 const isTwoCNChar = rxTwoCNChar.test.bind(rxTwoCNChar);
 const props = buttonTypes();
@@ -38,18 +41,41 @@ export default defineComponent({
     const { prefixCls, autoInsertSpaceInButton, direction } = useConfigInject('btn', props);
 
     const buttonNodeRef = ref<HTMLElement>(null);
-    const delayTimeout = ref(undefined);
-    const iconCom = ref<VNode>(null);
-    const children = ref<VNode[]>([]);
+    const delayTimeoutRef = ref(undefined);
+    let isNeedInserted = false;
 
-    const sLoading = ref(props.loading);
+    const innerLoading: Ref<Loading> = ref(false);
     const hasTwoCNChar = ref(false);
 
     const autoInsertSpace = computed(() => autoInsertSpaceInButton.value !== false);
 
-    const getClasses = () => {
-      const { type, shape, size, ghost, block, danger } = props;
+    // =============== Update Loading ===============
+    const loadingOrDelay = computed(() =>
+      typeof props.loading === 'object' && props.loading.delay
+        ? props.loading.delay || true
+        : !!props.loading,
+    );
 
+    watch(
+      loadingOrDelay,
+      (val) => {
+        clearTimeout(delayTimeoutRef.value);
+        if (typeof loadingOrDelay.value === 'number') {
+          delayTimeoutRef.value = window.setTimeout(() => {
+            innerLoading.value = val;
+          }, loadingOrDelay.value);
+        } else {
+          innerLoading.value = val;
+        }
+      },
+      {
+        immediate: true,
+      },
+    );
+
+    const classes = computed(() => {
+      const { type, shape, size, ghost, block, danger } = props;
+      const pre = prefixCls.value;
       // large => lg
       // small => sm
       let sizeCls = '';
@@ -63,22 +89,20 @@ export default defineComponent({
         default:
           break;
       }
-      const iconType = sLoading.value ? 'loading' : iconCom.value;
       return {
         [attrs.class as string]: attrs.class,
-        [`${prefixCls.value}`]: true,
-        [`${prefixCls.value}-${type}`]: type,
-        [`${prefixCls.value}-${shape}`]: shape,
-        [`${prefixCls.value}-${sizeCls}`]: sizeCls,
-        [`${prefixCls.value}-icon-only`]: children.value.length === 0 && !!iconType,
-        [`${prefixCls.value}-loading`]: sLoading.value,
-        [`${prefixCls.value}-background-ghost`]: ghost && !isUnborderedButtonType(type),
-        [`${prefixCls.value}-two-chinese-chars`]: hasTwoCNChar.value && autoInsertSpace.value,
-        [`${prefixCls.value}-block`]: block,
-        [`${prefixCls.value}-dangerous`]: !!danger,
-        [`${prefixCls.value}-rtl`]: direction.value === 'rtl',
+        [`${pre}`]: true,
+        [`${pre}-${type}`]: type,
+        [`${pre}-${shape}`]: shape,
+        [`${pre}-${sizeCls}`]: sizeCls,
+        [`${pre}-loading`]: innerLoading.value,
+        [`${pre}-background-ghost`]: ghost && !isUnborderedButtonType(type),
+        [`${pre}-two-chinese-chars`]: hasTwoCNChar.value && autoInsertSpace.value,
+        [`${pre}-block`]: block,
+        [`${pre}-dangerous`]: !!danger,
+        [`${pre}-rtl`]: direction.value === 'rtl',
       };
-    };
+    });
 
     const fixTwoCNChar = () => {
       // Fix for HOC usage like <FormatMessage />
@@ -88,7 +112,7 @@ export default defineComponent({
       }
       const buttonText = node.textContent;
 
-      if (isNeedInserted() && isTwoCNChar(buttonText)) {
+      if (isNeedInserted && isTwoCNChar(buttonText)) {
         if (!hasTwoCNChar.value) {
           hasTwoCNChar.value = true;
         }
@@ -98,7 +122,7 @@ export default defineComponent({
     };
     const handleClick = (event: Event) => {
       // https://github.com/ant-design/ant-design/issues/30207
-      if (sLoading.value || props.disabled) {
+      if (innerLoading.value || props.disabled) {
         event.preventDefault();
         return;
       }
@@ -117,9 +141,6 @@ export default defineComponent({
       return child;
     };
 
-    const isNeedInserted = () =>
-      children.value.length === 1 && !iconCom.value && !isUnborderedButtonType(props.type);
-
     watchEffect(() => {
       devWarning(
         !(props.ghost && isUnborderedButtonType(props.type)),
@@ -128,50 +149,45 @@ export default defineComponent({
       );
     });
 
-    watch(
-      () => props.loading,
-      (val, preVal) => {
-        if (preVal && typeof preVal !== 'boolean') {
-          clearTimeout(delayTimeout.value);
-        }
-        if (val && typeof val !== 'boolean' && val.delay) {
-          delayTimeout.value = setTimeout(() => {
-            sLoading.value = !!val;
-          }, val.delay);
-        } else {
-          sLoading.value = !!val;
-        }
-      },
-      {
-        immediate: true,
-      },
-    );
-
     onMounted(fixTwoCNChar);
     onUpdated(fixTwoCNChar);
 
     onBeforeUnmount(() => {
-      delayTimeout.value && clearTimeout(delayTimeout.value);
+      delayTimeoutRef.value && clearTimeout(delayTimeoutRef.value);
     });
 
     return () => {
-      iconCom.value = getPropsSlot(slots, props, 'icon');
-      children.value = flattenChildren(getPropsSlot(slots, props));
+      const children = flattenChildren(getPropsSlot(slots, props));
+
+      const icon = getPropsSlot(slots, props, 'icon');
+
+      isNeedInserted = children.length === 1 && !icon && !isUnborderedButtonType(props.type);
 
       const { type, htmlType, disabled, href, title, target } = props;
-      const classes = getClasses();
 
+      const iconType = innerLoading.value ? 'loading' : icon;
       const buttonProps = {
         ...attrs,
         title,
         disabled,
-        class: classes,
+        class: [
+          classes.value,
+          attrs.class,
+          { [`${prefixCls.value}-icon-only`]: children.length === 0 && !!iconType },
+        ],
         onClick: handleClick,
       };
-      const iconNode = sLoading.value ? <LoadingOutlined /> : iconCom.value;
 
-      const kids = children.value.map((child) =>
-        insertSpace(child, isNeedInserted() && autoInsertSpace.value),
+      const iconNode = innerLoading.value ? (
+        <span class={`${prefixCls.value}-loading-icon`}>
+          <LoadingOutlined />
+        </span>
+      ) : (
+        icon
+      );
+
+      const kids = children.map((child) =>
+        insertSpace(child, isNeedInserted && autoInsertSpace.value),
       );
 
       if (href !== undefined) {
diff --git a/components/button/buttonTypes.ts b/components/button/buttonTypes.ts
index e71a0b696..87b1f498d 100644
--- a/components/button/buttonTypes.ts
+++ b/components/button/buttonTypes.ts
@@ -6,7 +6,7 @@ import type { SizeType } from '../config-provider';
 
 const ButtonTypes = tuple('default', 'primary', 'ghost', 'dashed', 'link', 'text');
 export type ButtonType = typeof ButtonTypes[number];
-const ButtonShapes = tuple('circle', 'circle-outline', 'round');
+const ButtonShapes = tuple('circle', 'round');
 export type ButtonShape = typeof ButtonShapes[number];
 
 const ButtonHTMLTypes = tuple('submit', 'button', 'reset');
diff --git a/components/button/index.ts b/components/button/index.ts
index 4b9364911..b5136a9e2 100644
--- a/components/button/index.ts
+++ b/components/button/index.ts
@@ -17,6 +17,8 @@ Button.install = function (app: App) {
   return app;
 };
 
+export { ButtonGroup };
+
 export default Button as typeof Button &
   Plugin & {
     readonly Group: typeof ButtonGroup;
diff --git a/components/button/style/index.less b/components/button/style/index.less
index 97fbeb2ed..2ea7e01a3 100644
--- a/components/button/style/index.less
+++ b/components/button/style/index.less
@@ -155,39 +155,25 @@
     }
   }
 
-  &&-loading:not(&-circle):not(&-circle-outline):not(&-icon-only) {
-    padding-left: 29px;
-    .@{iconfont-css-prefix}:not(:last-child) {
-      margin-left: -14px;
-    }
-  }
+  & > &-loading-icon {
+    transition: all 0.3s @ease-in-out;
 
-  &-sm&-loading:not(&-circle):not(&-circle-outline):not(&-icon-only) {
-    padding-left: 24px;
     .@{iconfont-css-prefix} {
-      margin-left: -17px;
+      padding-right: @padding-xs;
+      animation: none;
+      // for smooth button padding transition
+      svg {
+        animation: loadingCircle 1s infinite linear;
+      }
+    }
+
+    &:only-child {
+      .@{iconfont-css-prefix} {
+        padding-right: 0;
+      }
     }
   }
 
-  // & > &-loading-icon {
-  //   transition: all 0.3s @ease-in-out;
-
-  //   .@{iconfont-css-prefix} {
-  //     padding-right: @padding-xs;
-  //     animation: none;
-  //     // for smooth button padding transition
-  //     svg {
-  //       animation: loadingCircle 1s infinite linear;
-  //     }
-  //   }
-
-  //   &:only-child {
-  //     .@{iconfont-css-prefix} {
-  //       padding-right: 0;
-  //     }
-  //   }
-  // }
-
   &-group {
     .btn-group(@btn-prefix-cls);
   }
diff --git a/components/modal/Modal.tsx b/components/modal/Modal.tsx
index c0995dac0..66d06a74a 100644
--- a/components/modal/Modal.tsx
+++ b/components/modal/Modal.tsx
@@ -136,7 +136,9 @@ export interface ModalFuncProps {
 
 type getContainerFunc = () => HTMLElement;
 
-export type ModalFunc = (props: ModalFuncProps) => {
+export type ModalFunc = (
+  props: ModalFuncProps,
+) => {
   destroy: () => void;
   update: (newConfig: ModalFuncProps) => void;
 };
diff --git a/components/popconfirm/index.tsx b/components/popconfirm/index.tsx
index 5521f9038..f4d739f3c 100644
--- a/components/popconfirm/index.tsx
+++ b/components/popconfirm/index.tsx
@@ -139,7 +139,7 @@ const Popconfirm = defineComponent({
       <LocaleReceiver
         componentName="Popconfirm"
         defaultLocale={defaultLocale.Popconfirm}
-        children={(popconfirmLocale) => this.renderOverlay(prefixCls, popconfirmLocale)}
+        children={popconfirmLocale => this.renderOverlay(prefixCls, popconfirmLocale)}
       />
     );
     const tooltipProps = {
diff --git a/v2-doc b/v2-doc
index 4c2982755..b6ab0fec2 160000
--- a/v2-doc
+++ b/v2-doc
@@ -1 +1 @@
-Subproject commit 4c298275518d5790a58d26f2ed9b83ee5ba1dba4
+Subproject commit b6ab0fec2cfa378bab8dfe6c8ef6b6a8664b970e