Skip to content

Commit 389b233

Browse files
authored
refactor(3.x/button): use composition api (#4291)
* refactor(button): use composition api * refactor: add rtl * refactor: sync antd * refactor: update css * refactor: add dev warning * test: add test case * refactor: export component type * refactor: optimize
1 parent 9ce43be commit 389b233

File tree

16 files changed

+715
-284
lines changed

16 files changed

+715
-284
lines changed

components/_util/hooks/useConfigInject.ts

+3
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,15 @@ export default (
2121
form?: ComputedRef<{
2222
requiredMark?: RequiredMark;
2323
}>;
24+
autoInsertSpaceInButton: ComputedRef<Boolean>;
2425
} => {
2526
const configProvider = inject<UnwrapRef<ConfigProviderProps>>(
2627
'configProvider',
2728
defaultConfigProvider,
2829
);
2930
const prefixCls = computed(() => configProvider.getPrefixCls(name, props.prefixCls));
3031
const direction = computed(() => configProvider.direction);
32+
const autoInsertSpaceInButton = computed(() => configProvider.autoInsertSpaceInButton);
3133
const space = computed(() => configProvider.space);
3234
const pageHeader = computed(() => configProvider.pageHeader);
3335
const form = computed(() => configProvider.form);
@@ -42,5 +44,6 @@ export default (
4244
space,
4345
pageHeader,
4446
form,
47+
autoInsertSpaceInButton,
4548
};
4649
};

components/button/__tests__/__snapshots__/index.test.js.snap

+4-4
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ exports[`Button renders Chinese characters correctly 1`] = `
2626

2727
exports[`Button renders Chinese characters correctly 2`] = `
2828
<button class="ant-btn" type="button">
29-
<!----><span role="img" aria-label="search" class="anticon anticon-search"><svg class="" data-icon="search" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896" focusable="false"><path d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"></path></svg></span><span>按钮</span>
29+
<!----><span role="img" aria-label="search" class="anticon anticon-search"><svg focusable="false" class="" data-icon="search" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896"><path d="M909.6 854.5L649.9 594.8C690.2 542.7 712 479 712 412c0-80.2-31.3-155.4-87.9-212.1-56.6-56.7-132-87.9-212.1-87.9s-155.5 31.3-212.1 87.9C143.2 256.5 112 331.8 112 412c0 80.1 31.3 155.5 87.9 212.1C256.5 680.8 331.8 712 412 712c67 0 130.6-21.8 182.7-62l259.7 259.6a8.2 8.2 0 0011.6 0l43.6-43.5a8.2 8.2 0 000-11.6zM570.4 570.4C528 612.7 471.8 636 412 636s-116-23.3-158.4-65.6C211.3 528 188 471.8 188 412s23.3-116.1 65.6-158.4C296 211.3 352.2 188 412 188s116.1 23.2 158.4 65.6S636 352.2 636 412s-23.3 116.1-65.6 158.4z"></path></svg></span><span>按钮</span>
3030
</button>
3131
`;
3232

@@ -36,9 +36,9 @@ exports[`Button renders Chinese characters correctly 3`] = `
3636
</button>
3737
`;
3838

39-
exports[`Button renders Chinese characters correctly 4`] = `<button class="ant-btn ant-btn-loading" type="button"><span role="img" aria-label="loading" class="anticon anticon-loading"><svg class="anticon-spin" data-icon="loading" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="0 0 1024 1024" focusable="false"><path d="M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 00-94.3-139.9 437.71 437.71 0 00-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3.1 19.9-16 36-35.9 36z"></path></svg></span><span>按 钮</span></button>`;
39+
exports[`Button renders Chinese characters correctly 4`] = `<button class="ant-btn ant-btn-loading" type="button"><span role="img" aria-label="loading" class="anticon anticon-loading"><svg focusable="false" class="anticon-spin" data-icon="loading" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="0 0 1024 1024"><path d="M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 00-94.3-139.9 437.71 437.71 0 00-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3.1 19.9-16 36-35.9 36z"></path></svg></span><span>按 钮</span></button>`;
4040

41-
exports[`Button renders Chinese characters correctly 5`] = `<button class="ant-btn ant-btn-loading" type="button"><span role="img" aria-label="loading" class="anticon anticon-loading"><svg class="anticon-spin" data-icon="loading" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="0 0 1024 1024" focusable="false"><path d="M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 00-94.3-139.9 437.71 437.71 0 00-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3.1 19.9-16 36-35.9 36z"></path></svg></span><span>按 钮</span></button>`;
41+
exports[`Button renders Chinese characters correctly 5`] = `<button class="ant-btn ant-btn-loading" type="button"><span role="img" aria-label="loading" class="anticon anticon-loading"><svg focusable="false" class="anticon-spin" data-icon="loading" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="0 0 1024 1024"><path d="M988 548c-19.9 0-36-16.1-36-36 0-59.4-11.6-117-34.6-171.3a440.45 440.45 0 00-94.3-139.9 437.71 437.71 0 00-139.9-94.3C629 83.6 571.4 72 512 72c-19.9 0-36-16.1-36-36s16.1-36 36-36c69.1 0 136.2 13.5 199.3 40.3C772.3 66 827 103 874 150c47 47 83.9 101.8 109.7 162.7 26.7 63.1 40.2 130.2 40.2 199.3.1 19.9-16 36-35.9 36z"></path></svg></span><span>按 钮</span></button>`;
4242

4343
exports[`Button renders Chinese characters correctly 6`] = `
4444
<button class="ant-btn ant-btn-two-chinese-chars" type="button">
@@ -59,7 +59,7 @@ exports[`Button should not render as link button when href is undefined 1`] = `
5959
`;
6060

6161
exports[`Button should support link button 1`] = `
62-
<a target="_blank" class="ant-btn" href="http://ant.design">
62+
<a class="ant-btn" href="http://ant.design" target="_blank">
6363
<!----><span>link button</span>
6464
</a>
6565
`;

components/button/__tests__/index.test.js

+51-4
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,9 @@ import Button from '../index';
22
import SearchOutlined from '@ant-design/icons-vue/SearchOutlined';
33
import { mount } from '@vue/test-utils';
44
import { nextTick } from 'vue';
5-
import { asyncExpect } from '@/tests/utils';
6-
import { sleep } from '../../../tests/utils';
7-
import mountTest from '../../../tests/shared/mountTest';
5+
import { asyncExpect, sleep } from '@/tests/utils';
6+
import mountTest from '@/tests/shared/mountTest';
7+
import { resetWarned } from '../../_util/warning';
88

99
describe('Button', () => {
1010
mountTest(Button);
@@ -27,7 +27,7 @@ describe('Button', () => {
2727
expect(wrapper.find('.ant-btn-primary').exists()).toBe(true);
2828
});
2929

30-
it('renders Chinese characters correctly', done => {
30+
it('renders Chinese characters correctly', (done) => {
3131
const wrapper = mount({
3232
render() {
3333
return <Button>按钮</Button>;
@@ -247,4 +247,51 @@ describe('Button', () => {
247247
wrapper.unmount();
248248
}).not.toThrow();
249249
});
250+
251+
it('should warning when pass type=link and ghost=true', () => {
252+
resetWarned();
253+
const warnSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
254+
mount({
255+
render() {
256+
return <Button type="link" ghost />;
257+
},
258+
});
259+
expect(warnSpy).toHaveBeenCalledWith(
260+
"Warning: [ant-design-vue: Button] `link` or `text` button can't be a `ghost` button.",
261+
);
262+
warnSpy.mockRestore();
263+
});
264+
265+
it('should warning when pass type=text and ghost=true', () => {
266+
resetWarned();
267+
const warnSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
268+
mount({
269+
render() {
270+
return <Button type="text" ghost />;
271+
},
272+
});
273+
expect(warnSpy).toHaveBeenCalledWith(
274+
"Warning: [ant-design-vue: Button] `link` or `text` button can't be a `ghost` button.",
275+
);
276+
warnSpy.mockRestore();
277+
});
278+
279+
it('should not redirect when button is disabled', async () => {
280+
const onClick = jest.fn();
281+
const wrapper = mount({
282+
render() {
283+
return (
284+
<Button href="https://ant.design" onClick={onClick} disabled>
285+
click me
286+
</Button>
287+
);
288+
},
289+
});
290+
await asyncExpect(() => {
291+
wrapper.trigger('click');
292+
});
293+
await asyncExpect(() => {
294+
expect(onClick).not.toHaveBeenCalled();
295+
});
296+
});
250297
});
+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import Button from '../index';
2+
import { mount } from '@vue/test-utils';
3+
import { asyncExpect, sleep } from '@/tests/utils';
4+
5+
describe('click wave effect', () => {
6+
async function clickButton(wrapper) {
7+
await asyncExpect(() => {
8+
wrapper.find('.ant-btn').trigger('click');
9+
});
10+
wrapper.find('.ant-btn').element.dispatchEvent(new Event('transitionstart'));
11+
await sleep(20);
12+
wrapper.find('.ant-btn').element.dispatchEvent(new Event('animationend'));
13+
await sleep(20);
14+
}
15+
16+
it('should have click wave effect for primary button', async () => {
17+
const wrapper = mount({
18+
render() {
19+
return <Button type="primary">button</Button>;
20+
},
21+
});
22+
await clickButton(wrapper);
23+
expect(wrapper.find('.ant-btn').attributes('ant-click-animating-without-extra-node')).toBe(
24+
'true',
25+
);
26+
});
27+
28+
it('should have click wave effect for default button', async () => {
29+
const wrapper = mount({
30+
render() {
31+
return <Button>button</Button>;
32+
},
33+
});
34+
await clickButton(wrapper);
35+
expect(wrapper.find('.ant-btn').attributes('ant-click-animating-without-extra-node')).toBe(
36+
'true',
37+
);
38+
});
39+
40+
it('should not have click wave effect for link type button', async () => {
41+
const wrapper = mount({
42+
render() {
43+
return <Button type="link">button</Button>;
44+
},
45+
});
46+
await clickButton(wrapper);
47+
expect(wrapper.find('.ant-btn').attributes('ant-click-animating-without-extra-node')).toBe(
48+
undefined,
49+
);
50+
});
51+
52+
it('should not have click wave effect for text type button', async () => {
53+
const wrapper = mount({
54+
render() {
55+
return <Button type="text">button</Button>;
56+
},
57+
});
58+
await clickButton(wrapper);
59+
expect(wrapper.find('.ant-btn').attributes('ant-click-animating-without-extra-node')).toBe(
60+
undefined,
61+
);
62+
});
63+
64+
it('should handle transitionstart', async () => {
65+
const wrapper = mount({
66+
render() {
67+
return <Button type="primary">button</Button>;
68+
},
69+
});
70+
await clickButton(wrapper);
71+
const buttonNode = wrapper.find('.ant-btn').element;
72+
buttonNode.dispatchEvent(new Event('transitionstart'));
73+
expect(wrapper.find('.ant-btn').attributes('ant-click-animating-without-extra-node')).toBe(
74+
'true',
75+
);
76+
wrapper.unmount();
77+
buttonNode.dispatchEvent(new Event('transitionstart'));
78+
});
79+
});

components/button/button-group.tsx

+39-43
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,49 @@
1-
import { defineComponent, inject } from 'vue';
2-
import { filterEmpty, getSlot } from '../_util/props-util';
1+
import { defineComponent } from 'vue';
2+
import { flattenChildren } from '../_util/props-util';
33
import PropTypes from '../_util/vue-types';
4-
import { defaultConfigProvider } from '../config-provider';
5-
import { tuple } from '../_util/type';
4+
import useConfigInject from '../_util/hooks/useConfigInject';
65

7-
const ButtonGroupProps = {
6+
import type { ExtractPropTypes, PropType } from 'vue';
7+
import type { SizeType } from '../config-provider';
8+
9+
const buttonGroupProps = {
810
prefixCls: PropTypes.string,
9-
size: PropTypes.oneOf(tuple('small', 'large', 'default')),
11+
size: {
12+
type: String as PropType<SizeType>,
13+
},
1014
};
11-
export { ButtonGroupProps };
15+
export { buttonGroupProps };
16+
17+
export type ButtonGroupProps = Partial<ExtractPropTypes<typeof buttonGroupProps>>;
18+
1219
export default defineComponent({
1320
name: 'AButtonGroup',
14-
props: ButtonGroupProps,
15-
setup() {
16-
const configProvider = inject('configProvider', defaultConfigProvider);
17-
return {
18-
configProvider,
19-
};
20-
},
21-
data() {
22-
return {
23-
sizeMap: {
24-
large: 'lg',
25-
small: 'sm',
26-
},
27-
};
28-
},
29-
render() {
30-
const { prefixCls: customizePrefixCls, size } = this;
31-
const getPrefixCls = this.configProvider.getPrefixCls;
32-
const prefixCls = getPrefixCls('btn-group', customizePrefixCls);
21+
props: buttonGroupProps,
22+
setup(props, { slots }) {
23+
const { prefixCls, direction } = useConfigInject('btn-group', props);
24+
25+
return () => {
26+
const { size } = props;
3327

34-
// large => lg
35-
// small => sm
36-
let sizeCls = '';
37-
switch (size) {
38-
case 'large':
39-
sizeCls = 'lg';
40-
break;
41-
case 'small':
42-
sizeCls = 'sm';
43-
break;
44-
default:
45-
break;
46-
}
47-
const classes = {
48-
[`${prefixCls}`]: true,
49-
[`${prefixCls}-${sizeCls}`]: sizeCls,
28+
// large => lg
29+
// small => sm
30+
let sizeCls = '';
31+
switch (size) {
32+
case 'large':
33+
sizeCls = 'lg';
34+
break;
35+
case 'small':
36+
sizeCls = 'sm';
37+
break;
38+
default:
39+
break;
40+
}
41+
const classes = {
42+
[`${prefixCls.value}`]: true,
43+
[`${prefixCls.value}-${sizeCls}`]: sizeCls,
44+
[`${prefixCls.value}-rtl`]: direction.value === 'rtl',
45+
};
46+
return <div class={classes}>{flattenChildren(slots.default?.())}</div>;
5047
};
51-
return <div class={classes}>{filterEmpty(getSlot(this))}</div>;
5248
},
5349
});

0 commit comments

Comments
 (0)