refactor: button

pull/4297/head
tangjinzhou 3 years ago
parent 389b233a40
commit 61c19a81c2

@ -1,4 +1,4 @@
import { defineComponent } from 'vue'; import { computed, defineComponent } from 'vue';
import { flattenChildren } from '../_util/props-util'; import { flattenChildren } from '../_util/props-util';
import PropTypes from '../_util/vue-types'; import PropTypes from '../_util/vue-types';
import useConfigInject from '../_util/hooks/useConfigInject'; import useConfigInject from '../_util/hooks/useConfigInject';
@ -21,10 +21,8 @@ export default defineComponent({
props: buttonGroupProps, props: buttonGroupProps,
setup(props, { slots }) { setup(props, { slots }) {
const { prefixCls, direction } = useConfigInject('btn-group', props); const { prefixCls, direction } = useConfigInject('btn-group', props);
const classes = computed(() => {
return () => {
const { size } = props; const { size } = props;
// large => lg // large => lg
// small => sm // small => sm
let sizeCls = ''; let sizeCls = '';
@ -38,12 +36,14 @@ export default defineComponent({
default: default:
break; break;
} }
const classes = { return {
[`${prefixCls.value}`]: true, [`${prefixCls.value}`]: true,
[`${prefixCls.value}-${sizeCls}`]: sizeCls, [`${prefixCls.value}-${sizeCls}`]: sizeCls,
[`${prefixCls.value}-rtl`]: direction.value === 'rtl', [`${prefixCls.value}-rtl`]: direction.value === 'rtl',
}; };
return <div class={classes}>{flattenChildren(slots.default?.())}</div>; });
return () => {
return <div class={classes.value}>{flattenChildren(slots.default?.())}</div>;
}; };
}, },
}); });

@ -4,6 +4,7 @@ import {
onBeforeUnmount, onBeforeUnmount,
onMounted, onMounted,
onUpdated, onUpdated,
Ref,
ref, ref,
Text, Text,
watch, watch,
@ -19,6 +20,8 @@ import devWarning from '../vc-util/devWarning';
import type { ButtonType } from './buttonTypes'; import type { ButtonType } from './buttonTypes';
import type { VNode } from 'vue'; import type { VNode } from 'vue';
type Loading = boolean | number;
const rxTwoCNChar = /^[\u4e00-\u9fa5]{2}$/; const rxTwoCNChar = /^[\u4e00-\u9fa5]{2}$/;
const isTwoCNChar = rxTwoCNChar.test.bind(rxTwoCNChar); const isTwoCNChar = rxTwoCNChar.test.bind(rxTwoCNChar);
const props = buttonTypes(); const props = buttonTypes();
@ -38,18 +41,41 @@ export default defineComponent({
const { prefixCls, autoInsertSpaceInButton, direction } = useConfigInject('btn', props); const { prefixCls, autoInsertSpaceInButton, direction } = useConfigInject('btn', props);
const buttonNodeRef = ref<HTMLElement>(null); const buttonNodeRef = ref<HTMLElement>(null);
const delayTimeout = ref(undefined); const delayTimeoutRef = ref(undefined);
const iconCom = ref<VNode>(null); let isNeedInserted = false;
const children = ref<VNode[]>([]);
const sLoading = ref(props.loading); const innerLoading: Ref<Loading> = ref(false);
const hasTwoCNChar = ref(false); const hasTwoCNChar = ref(false);
const autoInsertSpace = computed(() => autoInsertSpaceInButton.value !== false); const autoInsertSpace = computed(() => autoInsertSpaceInButton.value !== false);
const getClasses = () => { // =============== Update Loading ===============
const { type, shape, size, ghost, block, danger } = props; 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 // large => lg
// small => sm // small => sm
let sizeCls = ''; let sizeCls = '';
@ -63,22 +89,20 @@ export default defineComponent({
default: default:
break; break;
} }
const iconType = sLoading.value ? 'loading' : iconCom.value;
return { return {
[attrs.class as string]: attrs.class, [attrs.class as string]: attrs.class,
[`${prefixCls.value}`]: true, [`${pre}`]: true,
[`${prefixCls.value}-${type}`]: type, [`${pre}-${type}`]: type,
[`${prefixCls.value}-${shape}`]: shape, [`${pre}-${shape}`]: shape,
[`${prefixCls.value}-${sizeCls}`]: sizeCls, [`${pre}-${sizeCls}`]: sizeCls,
[`${prefixCls.value}-icon-only`]: children.value.length === 0 && !!iconType, [`${pre}-loading`]: innerLoading.value,
[`${prefixCls.value}-loading`]: sLoading.value, [`${pre}-background-ghost`]: ghost && !isUnborderedButtonType(type),
[`${prefixCls.value}-background-ghost`]: ghost && !isUnborderedButtonType(type), [`${pre}-two-chinese-chars`]: hasTwoCNChar.value && autoInsertSpace.value,
[`${prefixCls.value}-two-chinese-chars`]: hasTwoCNChar.value && autoInsertSpace.value, [`${pre}-block`]: block,
[`${prefixCls.value}-block`]: block, [`${pre}-dangerous`]: !!danger,
[`${prefixCls.value}-dangerous`]: !!danger, [`${pre}-rtl`]: direction.value === 'rtl',
[`${prefixCls.value}-rtl`]: direction.value === 'rtl',
}; };
}; });
const fixTwoCNChar = () => { const fixTwoCNChar = () => {
// Fix for HOC usage like <FormatMessage /> // Fix for HOC usage like <FormatMessage />
@ -88,7 +112,7 @@ export default defineComponent({
} }
const buttonText = node.textContent; const buttonText = node.textContent;
if (isNeedInserted() && isTwoCNChar(buttonText)) { if (isNeedInserted && isTwoCNChar(buttonText)) {
if (!hasTwoCNChar.value) { if (!hasTwoCNChar.value) {
hasTwoCNChar.value = true; hasTwoCNChar.value = true;
} }
@ -98,7 +122,7 @@ export default defineComponent({
}; };
const handleClick = (event: Event) => { const handleClick = (event: Event) => {
// https://github.com/ant-design/ant-design/issues/30207 // https://github.com/ant-design/ant-design/issues/30207
if (sLoading.value || props.disabled) { if (innerLoading.value || props.disabled) {
event.preventDefault(); event.preventDefault();
return; return;
} }
@ -117,9 +141,6 @@ export default defineComponent({
return child; return child;
}; };
const isNeedInserted = () =>
children.value.length === 1 && !iconCom.value && !isUnborderedButtonType(props.type);
watchEffect(() => { watchEffect(() => {
devWarning( devWarning(
!(props.ghost && isUnborderedButtonType(props.type)), !(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); onMounted(fixTwoCNChar);
onUpdated(fixTwoCNChar); onUpdated(fixTwoCNChar);
onBeforeUnmount(() => { onBeforeUnmount(() => {
delayTimeout.value && clearTimeout(delayTimeout.value); delayTimeoutRef.value && clearTimeout(delayTimeoutRef.value);
}); });
return () => { return () => {
iconCom.value = getPropsSlot(slots, props, 'icon'); const children = flattenChildren(getPropsSlot(slots, props));
children.value = 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 { type, htmlType, disabled, href, title, target } = props;
const classes = getClasses();
const iconType = innerLoading.value ? 'loading' : icon;
const buttonProps = { const buttonProps = {
...attrs, ...attrs,
title, title,
disabled, disabled,
class: classes, class: [
classes.value,
attrs.class,
{ [`${prefixCls.value}-icon-only`]: children.length === 0 && !!iconType },
],
onClick: handleClick, onClick: handleClick,
}; };
const iconNode = sLoading.value ? <LoadingOutlined /> : iconCom.value;
const kids = children.value.map((child) => const iconNode = innerLoading.value ? (
insertSpace(child, isNeedInserted() && autoInsertSpace.value), <span class={`${prefixCls.value}-loading-icon`}>
<LoadingOutlined />
</span>
) : (
icon
);
const kids = children.map((child) =>
insertSpace(child, isNeedInserted && autoInsertSpace.value),
); );
if (href !== undefined) { if (href !== undefined) {

@ -6,7 +6,7 @@ import type { SizeType } from '../config-provider';
const ButtonTypes = tuple('default', 'primary', 'ghost', 'dashed', 'link', 'text'); const ButtonTypes = tuple('default', 'primary', 'ghost', 'dashed', 'link', 'text');
export type ButtonType = typeof ButtonTypes[number]; export type ButtonType = typeof ButtonTypes[number];
const ButtonShapes = tuple('circle', 'circle-outline', 'round'); const ButtonShapes = tuple('circle', 'round');
export type ButtonShape = typeof ButtonShapes[number]; export type ButtonShape = typeof ButtonShapes[number];
const ButtonHTMLTypes = tuple('submit', 'button', 'reset'); const ButtonHTMLTypes = tuple('submit', 'button', 'reset');

@ -17,6 +17,8 @@ Button.install = function (app: App) {
return app; return app;
}; };
export { ButtonGroup };
export default Button as typeof Button & export default Button as typeof Button &
Plugin & { Plugin & {
readonly Group: typeof ButtonGroup; readonly Group: typeof ButtonGroup;

@ -155,38 +155,24 @@
} }
} }
&&-loading:not(&-circle):not(&-circle-outline):not(&-icon-only) { & > &-loading-icon {
padding-left: 29px; transition: all 0.3s @ease-in-out;
.@{iconfont-css-prefix}:not(:last-child) {
margin-left: -14px;
}
}
&-sm&-loading:not(&-circle):not(&-circle-outline):not(&-icon-only) {
padding-left: 24px;
.@{iconfont-css-prefix} { .@{iconfont-css-prefix} {
margin-left: -17px; padding-right: @padding-xs;
animation: none;
// for smooth button padding transition
svg {
animation: loadingCircle 1s infinite linear;
}
} }
}
// & > &-loading-icon { &:only-child {
// transition: all 0.3s @ease-in-out; .@{iconfont-css-prefix} {
padding-right: 0;
// .@{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 { &-group {
.btn-group(@btn-prefix-cls); .btn-group(@btn-prefix-cls);

@ -136,7 +136,9 @@ export interface ModalFuncProps {
type getContainerFunc = () => HTMLElement; type getContainerFunc = () => HTMLElement;
export type ModalFunc = (props: ModalFuncProps) => { export type ModalFunc = (
props: ModalFuncProps,
) => {
destroy: () => void; destroy: () => void;
update: (newConfig: ModalFuncProps) => void; update: (newConfig: ModalFuncProps) => void;
}; };

@ -139,7 +139,7 @@ const Popconfirm = defineComponent({
<LocaleReceiver <LocaleReceiver
componentName="Popconfirm" componentName="Popconfirm"
defaultLocale={defaultLocale.Popconfirm} defaultLocale={defaultLocale.Popconfirm}
children={(popconfirmLocale) => this.renderOverlay(prefixCls, popconfirmLocale)} children={popconfirmLocale => this.renderOverlay(prefixCls, popconfirmLocale)}
/> />
); );
const tooltipProps = { const tooltipProps = {

@ -1 +1 @@
Subproject commit 4c298275518d5790a58d26f2ed9b83ee5ba1dba4 Subproject commit b6ab0fec2cfa378bab8dfe6c8ef6b6a8664b970e
Loading…
Cancel
Save