Skip to content

Commit 61c19a8

Browse files
committed
refactor: button
1 parent 389b233 commit 61c19a8

File tree

8 files changed

+95
-89
lines changed

8 files changed

+95
-89
lines changed

components/button/button-group.tsx

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { defineComponent } from 'vue';
1+
import { computed, defineComponent } from 'vue';
22
import { flattenChildren } from '../_util/props-util';
33
import PropTypes from '../_util/vue-types';
44
import useConfigInject from '../_util/hooks/useConfigInject';
@@ -21,10 +21,8 @@ export default defineComponent({
2121
props: buttonGroupProps,
2222
setup(props, { slots }) {
2323
const { prefixCls, direction } = useConfigInject('btn-group', props);
24-
25-
return () => {
24+
const classes = computed(() => {
2625
const { size } = props;
27-
2826
// large => lg
2927
// small => sm
3028
let sizeCls = '';
@@ -38,12 +36,14 @@ export default defineComponent({
3836
default:
3937
break;
4038
}
41-
const classes = {
39+
return {
4240
[`${prefixCls.value}`]: true,
4341
[`${prefixCls.value}-${sizeCls}`]: sizeCls,
4442
[`${prefixCls.value}-rtl`]: direction.value === 'rtl',
4543
};
46-
return <div class={classes}>{flattenChildren(slots.default?.())}</div>;
44+
});
45+
return () => {
46+
return <div class={classes.value}>{flattenChildren(slots.default?.())}</div>;
4747
};
4848
},
4949
});

components/button/button.tsx

+67-51
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
onBeforeUnmount,
55
onMounted,
66
onUpdated,
7+
Ref,
78
ref,
89
Text,
910
watch,
@@ -19,6 +20,8 @@ import devWarning from '../vc-util/devWarning';
1920
import type { ButtonType } from './buttonTypes';
2021
import type { VNode } from 'vue';
2122

23+
type Loading = boolean | number;
24+
2225
const rxTwoCNChar = /^[\u4e00-\u9fa5]{2}$/;
2326
const isTwoCNChar = rxTwoCNChar.test.bind(rxTwoCNChar);
2427
const props = buttonTypes();
@@ -38,18 +41,41 @@ export default defineComponent({
3841
const { prefixCls, autoInsertSpaceInButton, direction } = useConfigInject('btn', props);
3942

4043
const buttonNodeRef = ref<HTMLElement>(null);
41-
const delayTimeout = ref(undefined);
42-
const iconCom = ref<VNode>(null);
43-
const children = ref<VNode[]>([]);
44+
const delayTimeoutRef = ref(undefined);
45+
let isNeedInserted = false;
4446

45-
const sLoading = ref(props.loading);
47+
const innerLoading: Ref<Loading> = ref(false);
4648
const hasTwoCNChar = ref(false);
4749

4850
const autoInsertSpace = computed(() => autoInsertSpaceInButton.value !== false);
4951

50-
const getClasses = () => {
51-
const { type, shape, size, ghost, block, danger } = props;
52+
// =============== Update Loading ===============
53+
const loadingOrDelay = computed(() =>
54+
typeof props.loading === 'object' && props.loading.delay
55+
? props.loading.delay || true
56+
: !!props.loading,
57+
);
5258

59+
watch(
60+
loadingOrDelay,
61+
(val) => {
62+
clearTimeout(delayTimeoutRef.value);
63+
if (typeof loadingOrDelay.value === 'number') {
64+
delayTimeoutRef.value = window.setTimeout(() => {
65+
innerLoading.value = val;
66+
}, loadingOrDelay.value);
67+
} else {
68+
innerLoading.value = val;
69+
}
70+
},
71+
{
72+
immediate: true,
73+
},
74+
);
75+
76+
const classes = computed(() => {
77+
const { type, shape, size, ghost, block, danger } = props;
78+
const pre = prefixCls.value;
5379
// large => lg
5480
// small => sm
5581
let sizeCls = '';
@@ -63,22 +89,20 @@ export default defineComponent({
6389
default:
6490
break;
6591
}
66-
const iconType = sLoading.value ? 'loading' : iconCom.value;
6792
return {
6893
[attrs.class as string]: attrs.class,
69-
[`${prefixCls.value}`]: true,
70-
[`${prefixCls.value}-${type}`]: type,
71-
[`${prefixCls.value}-${shape}`]: shape,
72-
[`${prefixCls.value}-${sizeCls}`]: sizeCls,
73-
[`${prefixCls.value}-icon-only`]: children.value.length === 0 && !!iconType,
74-
[`${prefixCls.value}-loading`]: sLoading.value,
75-
[`${prefixCls.value}-background-ghost`]: ghost && !isUnborderedButtonType(type),
76-
[`${prefixCls.value}-two-chinese-chars`]: hasTwoCNChar.value && autoInsertSpace.value,
77-
[`${prefixCls.value}-block`]: block,
78-
[`${prefixCls.value}-dangerous`]: !!danger,
79-
[`${prefixCls.value}-rtl`]: direction.value === 'rtl',
94+
[`${pre}`]: true,
95+
[`${pre}-${type}`]: type,
96+
[`${pre}-${shape}`]: shape,
97+
[`${pre}-${sizeCls}`]: sizeCls,
98+
[`${pre}-loading`]: innerLoading.value,
99+
[`${pre}-background-ghost`]: ghost && !isUnborderedButtonType(type),
100+
[`${pre}-two-chinese-chars`]: hasTwoCNChar.value && autoInsertSpace.value,
101+
[`${pre}-block`]: block,
102+
[`${pre}-dangerous`]: !!danger,
103+
[`${pre}-rtl`]: direction.value === 'rtl',
80104
};
81-
};
105+
});
82106

83107
const fixTwoCNChar = () => {
84108
// Fix for HOC usage like <FormatMessage />
@@ -88,7 +112,7 @@ export default defineComponent({
88112
}
89113
const buttonText = node.textContent;
90114

91-
if (isNeedInserted() && isTwoCNChar(buttonText)) {
115+
if (isNeedInserted && isTwoCNChar(buttonText)) {
92116
if (!hasTwoCNChar.value) {
93117
hasTwoCNChar.value = true;
94118
}
@@ -98,7 +122,7 @@ export default defineComponent({
98122
};
99123
const handleClick = (event: Event) => {
100124
// https://github.com/ant-design/ant-design/issues/30207
101-
if (sLoading.value || props.disabled) {
125+
if (innerLoading.value || props.disabled) {
102126
event.preventDefault();
103127
return;
104128
}
@@ -117,9 +141,6 @@ export default defineComponent({
117141
return child;
118142
};
119143

120-
const isNeedInserted = () =>
121-
children.value.length === 1 && !iconCom.value && !isUnborderedButtonType(props.type);
122-
123144
watchEffect(() => {
124145
devWarning(
125146
!(props.ghost && isUnborderedButtonType(props.type)),
@@ -128,50 +149,45 @@ export default defineComponent({
128149
);
129150
});
130151

131-
watch(
132-
() => props.loading,
133-
(val, preVal) => {
134-
if (preVal && typeof preVal !== 'boolean') {
135-
clearTimeout(delayTimeout.value);
136-
}
137-
if (val && typeof val !== 'boolean' && val.delay) {
138-
delayTimeout.value = setTimeout(() => {
139-
sLoading.value = !!val;
140-
}, val.delay);
141-
} else {
142-
sLoading.value = !!val;
143-
}
144-
},
145-
{
146-
immediate: true,
147-
},
148-
);
149-
150152
onMounted(fixTwoCNChar);
151153
onUpdated(fixTwoCNChar);
152154

153155
onBeforeUnmount(() => {
154-
delayTimeout.value && clearTimeout(delayTimeout.value);
156+
delayTimeoutRef.value && clearTimeout(delayTimeoutRef.value);
155157
});
156158

157159
return () => {
158-
iconCom.value = getPropsSlot(slots, props, 'icon');
159-
children.value = flattenChildren(getPropsSlot(slots, props));
160+
const children = flattenChildren(getPropsSlot(slots, props));
161+
162+
const icon = getPropsSlot(slots, props, 'icon');
163+
164+
isNeedInserted = children.length === 1 && !icon && !isUnborderedButtonType(props.type);
160165

161166
const { type, htmlType, disabled, href, title, target } = props;
162-
const classes = getClasses();
163167

168+
const iconType = innerLoading.value ? 'loading' : icon;
164169
const buttonProps = {
165170
...attrs,
166171
title,
167172
disabled,
168-
class: classes,
173+
class: [
174+
classes.value,
175+
attrs.class,
176+
{ [`${prefixCls.value}-icon-only`]: children.length === 0 && !!iconType },
177+
],
169178
onClick: handleClick,
170179
};
171-
const iconNode = sLoading.value ? <LoadingOutlined /> : iconCom.value;
172180

173-
const kids = children.value.map((child) =>
174-
insertSpace(child, isNeedInserted() && autoInsertSpace.value),
181+
const iconNode = innerLoading.value ? (
182+
<span class={`${prefixCls.value}-loading-icon`}>
183+
<LoadingOutlined />
184+
</span>
185+
) : (
186+
icon
187+
);
188+
189+
const kids = children.map((child) =>
190+
insertSpace(child, isNeedInserted && autoInsertSpace.value),
175191
);
176192

177193
if (href !== undefined) {

components/button/buttonTypes.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import type { SizeType } from '../config-provider';
66

77
const ButtonTypes = tuple('default', 'primary', 'ghost', 'dashed', 'link', 'text');
88
export type ButtonType = typeof ButtonTypes[number];
9-
const ButtonShapes = tuple('circle', 'circle-outline', 'round');
9+
const ButtonShapes = tuple('circle', 'round');
1010
export type ButtonShape = typeof ButtonShapes[number];
1111

1212
const ButtonHTMLTypes = tuple('submit', 'button', 'reset');

components/button/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ Button.install = function (app: App) {
1717
return app;
1818
};
1919

20+
export { ButtonGroup };
21+
2022
export default Button as typeof Button &
2123
Plugin & {
2224
readonly Group: typeof ButtonGroup;

components/button/style/index.less

+14-28
Original file line numberDiff line numberDiff line change
@@ -155,38 +155,24 @@
155155
}
156156
}
157157

158-
&&-loading:not(&-circle):not(&-circle-outline):not(&-icon-only) {
159-
padding-left: 29px;
160-
.@{iconfont-css-prefix}:not(:last-child) {
161-
margin-left: -14px;
162-
}
163-
}
158+
& > &-loading-icon {
159+
transition: all 0.3s @ease-in-out;
164160

165-
&-sm&-loading:not(&-circle):not(&-circle-outline):not(&-icon-only) {
166-
padding-left: 24px;
167161
.@{iconfont-css-prefix} {
168-
margin-left: -17px;
162+
padding-right: @padding-xs;
163+
animation: none;
164+
// for smooth button padding transition
165+
svg {
166+
animation: loadingCircle 1s infinite linear;
167+
}
169168
}
170-
}
171169

172-
// & > &-loading-icon {
173-
// transition: all 0.3s @ease-in-out;
174-
175-
// .@{iconfont-css-prefix} {
176-
// padding-right: @padding-xs;
177-
// animation: none;
178-
// // for smooth button padding transition
179-
// svg {
180-
// animation: loadingCircle 1s infinite linear;
181-
// }
182-
// }
183-
184-
// &:only-child {
185-
// .@{iconfont-css-prefix} {
186-
// padding-right: 0;
187-
// }
188-
// }
189-
// }
170+
&:only-child {
171+
.@{iconfont-css-prefix} {
172+
padding-right: 0;
173+
}
174+
}
175+
}
190176

191177
&-group {
192178
.btn-group(@btn-prefix-cls);

components/modal/Modal.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,9 @@ export interface ModalFuncProps {
136136

137137
type getContainerFunc = () => HTMLElement;
138138

139-
export type ModalFunc = (props: ModalFuncProps) => {
139+
export type ModalFunc = (
140+
props: ModalFuncProps,
141+
) => {
140142
destroy: () => void;
141143
update: (newConfig: ModalFuncProps) => void;
142144
};

components/popconfirm/index.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ const Popconfirm = defineComponent({
139139
<LocaleReceiver
140140
componentName="Popconfirm"
141141
defaultLocale={defaultLocale.Popconfirm}
142-
children={(popconfirmLocale) => this.renderOverlay(prefixCls, popconfirmLocale)}
142+
children={popconfirmLocale => this.renderOverlay(prefixCls, popconfirmLocale)}
143143
/>
144144
);
145145
const tooltipProps = {

0 commit comments

Comments
 (0)