Skip to content

Commit 770e9cc

Browse files
authored
refactor: breadcrumb (#4137)
1 parent 772ac3c commit 770e9cc

File tree

7 files changed

+155
-119
lines changed

7 files changed

+155
-119
lines changed

components/breadcrumb/Breadcrumb.tsx

+59-49
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
1-
import { inject, cloneVNode, defineComponent, PropType } from 'vue';
1+
import { cloneVNode, defineComponent, PropType, ExtractPropTypes } from 'vue';
22
import PropTypes from '../_util/vue-types';
3-
import { filterEmpty, getComponent, getSlot } from '../_util/props-util';
3+
import { filterEmpty, getPropsSlot } from '../_util/props-util';
44
import warning from '../_util/warning';
5-
import { defaultConfigProvider } from '../config-provider';
65
import BreadcrumbItem from './BreadcrumbItem';
76
import Menu from '../menu';
87
import { Omit, VueNode } from '../_util/type';
8+
import useConfigInject from '../_util/hooks/useConfigInject';
99

1010
export interface Route {
1111
path: string;
1212
breadcrumbName: string;
1313
children?: Omit<Route, 'children'>[];
1414
}
1515

16-
const BreadcrumbProps = {
16+
const breadcrumbProps = {
1717
prefixCls: PropTypes.string,
1818
routes: { type: Array as PropType<Route[]> },
1919
params: PropTypes.any,
@@ -25,6 +25,8 @@ const BreadcrumbProps = {
2525
},
2626
};
2727

28+
export type BreadcrumbProps = Partial<ExtractPropTypes<typeof breadcrumbProps>>;
29+
2830
function getBreadcrumbName(route: Route, params: unknown) {
2931
if (!route.breadcrumbName) {
3032
return null;
@@ -50,34 +52,36 @@ function defaultItemRender(opt: {
5052

5153
export default defineComponent({
5254
name: 'ABreadcrumb',
53-
props: BreadcrumbProps,
54-
setup() {
55-
return {
56-
configProvider: inject('configProvider', defaultConfigProvider),
57-
};
58-
},
59-
methods: {
60-
getPath(path: string, params: unknown) {
55+
props: breadcrumbProps,
56+
setup(props, { slots }) {
57+
const { prefixCls, direction } = useConfigInject('breadcrumb', props);
58+
59+
const getPath = (path: string, params: unknown) => {
6160
path = (path || '').replace(/^\//, '');
6261
Object.keys(params).forEach(key => {
6362
path = path.replace(`:${key}`, params[key]);
6463
});
6564
return path;
66-
},
65+
};
6766

68-
addChildPath(paths: string[], childPath = '', params: unknown) {
67+
const addChildPath = (paths: string[], childPath = '', params: unknown) => {
6968
const originalPaths = [...paths];
70-
const path = this.getPath(childPath, params);
69+
const path = getPath(childPath, params);
7170
if (path) {
7271
originalPaths.push(path);
7372
}
7473
return originalPaths;
75-
},
74+
};
7675

77-
genForRoutes({ routes = [], params = {}, separator, itemRender = defaultItemRender }: any) {
76+
const genForRoutes = ({
77+
routes = [],
78+
params = {},
79+
separator,
80+
itemRender = defaultItemRender,
81+
}: any) => {
7882
const paths = [];
7983
return routes.map((route: Route) => {
80-
const path = this.getPath(route.path, params);
84+
const path = getPath(route.path, params);
8185

8286
if (path) {
8387
paths.push(path);
@@ -94,7 +98,7 @@ export default defineComponent({
9498
route: child,
9599
params,
96100
routes,
97-
paths: this.addChildPath(tempPaths, child.path, params),
101+
paths: addChildPath(tempPaths, child.path, params),
98102
})}
99103
</Menu.Item>
100104
))}
@@ -112,36 +116,42 @@ export default defineComponent({
112116
</BreadcrumbItem>
113117
);
114118
});
115-
},
116-
},
117-
render() {
118-
let crumbs: VueNode[];
119-
const { prefixCls: customizePrefixCls, routes, params = {}, $slots } = this;
120-
const getPrefixCls = this.configProvider.getPrefixCls;
121-
const prefixCls = getPrefixCls('breadcrumb', customizePrefixCls);
119+
};
120+
return () => {
121+
let crumbs: VueNode[];
122122

123-
const children = filterEmpty(getSlot(this));
124-
const separator = getComponent(this, 'separator');
125-
const itemRender = this.itemRender || $slots.itemRender || defaultItemRender;
126-
if (routes && routes.length > 0) {
127-
// generated by route
128-
crumbs = this.genForRoutes({
129-
routes,
130-
params,
131-
separator,
132-
itemRender,
133-
});
134-
} else if (children.length) {
135-
crumbs = children.map((element, index) => {
136-
warning(
137-
typeof element.type === 'object' &&
138-
(element.type.__ANT_BREADCRUMB_ITEM || element.type.__ANT_BREADCRUMB_SEPARATOR),
139-
'Breadcrumb',
140-
"Only accepts Breadcrumb.Item and Breadcrumb.Separator as it's children",
141-
);
142-
return cloneVNode(element, { separator, key: index });
143-
});
144-
}
145-
return <div class={prefixCls}>{crumbs}</div>;
123+
const { routes, params = {} } = props;
124+
125+
const children = filterEmpty(getPropsSlot(slots, props));
126+
const separator = getPropsSlot(slots, props, 'separator');
127+
128+
const itemRender = getPropsSlot(slots, props, 'itemRender') || defaultItemRender;
129+
if (routes && routes.length > 0) {
130+
// generated by route
131+
crumbs = genForRoutes({
132+
routes,
133+
params,
134+
separator,
135+
itemRender,
136+
});
137+
} else if (children.length) {
138+
crumbs = children.map((element, index) => {
139+
warning(
140+
typeof element.type === 'object' &&
141+
(element.type.__ANT_BREADCRUMB_ITEM || element.type.__ANT_BREADCRUMB_SEPARATOR),
142+
'Breadcrumb',
143+
"Only accepts Breadcrumb.Item and Breadcrumb.Separator as it's children",
144+
);
145+
return cloneVNode(element, { separator, key: index });
146+
});
147+
}
148+
149+
const breadcrumbClassName = {
150+
[prefixCls.value]: true,
151+
[`${prefixCls.value}-rtl`]: direction.value === 'rtl',
152+
};
153+
return <div class={breadcrumbClassName}>{crumbs}</div>;
154+
};
146155
},
156+
methods: {},
147157
});
+42-44
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,30 @@
1-
import { defineComponent, inject } from 'vue';
1+
import { defineComponent, ExtractPropTypes } from 'vue';
22
import PropTypes from '../_util/vue-types';
3-
import { hasProp, getComponent, getSlot } from '../_util/props-util';
4-
import { defaultConfigProvider } from '../config-provider';
3+
import { getPropsSlot } from '../_util/props-util';
54
import DropDown from '../dropdown/dropdown';
65
import DownOutlined from '@ant-design/icons-vue/DownOutlined';
6+
import useConfigInject from '../_util/hooks/useConfigInject';
77

8+
const breadcrumbItemProps = {
9+
prefixCls: PropTypes.string,
10+
href: PropTypes.string,
11+
separator: PropTypes.VNodeChild.def('/'),
12+
overlay: PropTypes.VNodeChild,
13+
};
14+
15+
export type BreadcrumbItemProps = Partial<ExtractPropTypes<typeof breadcrumbItemProps>>;
816
export default defineComponent({
917
name: 'ABreadcrumbItem',
1018
__ANT_BREADCRUMB_ITEM: true,
11-
props: {
12-
prefixCls: PropTypes.string,
13-
href: PropTypes.string,
14-
separator: PropTypes.VNodeChild.def('/'),
15-
overlay: PropTypes.VNodeChild,
16-
},
17-
setup() {
18-
return {
19-
configProvider: inject('configProvider', defaultConfigProvider),
20-
};
21-
},
22-
methods: {
19+
props: breadcrumbItemProps,
20+
setup(props, { slots }) {
21+
const { prefixCls } = useConfigInject('breadcrumb', props);
2322
/**
2423
* if overlay is have
2524
* Wrap a DropDown
2625
*/
27-
renderBreadcrumbNode(breadcrumbItem: JSX.Element, prefixCls: string) {
28-
const overlay = getComponent(this, 'overlay');
26+
const renderBreadcrumbNode = (breadcrumbItem: JSX.Element, prefixCls: string) => {
27+
const overlay = getPropsSlot(slots, props, 'overlay');
2928
if (overlay) {
3029
return (
3130
<DropDown overlay={overlay} placement="bottomCenter">
@@ -37,32 +36,31 @@ export default defineComponent({
3736
);
3837
}
3938
return breadcrumbItem;
40-
},
41-
},
42-
render() {
43-
const { prefixCls: customizePrefixCls } = this;
44-
const getPrefixCls = this.configProvider.getPrefixCls;
45-
const prefixCls = getPrefixCls('breadcrumb', customizePrefixCls);
46-
const separator = getComponent(this, 'separator');
47-
const children = getSlot(this);
48-
let link: JSX.Element;
49-
if (hasProp(this, 'href')) {
50-
link = <a class={`${prefixCls}-link`}>{children}</a>;
51-
} else {
52-
link = <span class={`${prefixCls}-link`}>{children}</span>;
53-
}
54-
// wrap to dropDown
55-
link = this.renderBreadcrumbNode(link, prefixCls);
56-
if (children) {
57-
return (
58-
<span>
59-
{link}
60-
{separator && separator !== '' && (
61-
<span class={`${prefixCls}-separator`}>{separator}</span>
62-
)}
63-
</span>
64-
);
65-
}
66-
return null;
39+
};
40+
41+
return () => {
42+
const separator = getPropsSlot(slots, props, 'separator');
43+
const children = getPropsSlot(slots, props);
44+
let link: JSX.Element;
45+
46+
if ('href' in props) {
47+
link = <a class={`${prefixCls.value}-link`}>{children}</a>;
48+
} else {
49+
link = <span class={`${prefixCls.value}-link`}>{children}</span>;
50+
}
51+
// wrap to dropDown
52+
link = renderBreadcrumbNode(link, prefixCls.value);
53+
if (children) {
54+
return (
55+
<span>
56+
{link}
57+
{separator && separator !== '' && (
58+
<span class={`${prefixCls.value}-separator`}>{separator}</span>
59+
)}
60+
</span>
61+
);
62+
}
63+
return null;
64+
};
6765
},
6866
});
+21-22
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,30 @@
1-
import { defineComponent, inject } from 'vue';
2-
import { defaultConfigProvider } from '../config-provider';
1+
import { defineComponent, ExtractPropTypes } from 'vue';
32
import PropTypes from '../_util/vue-types';
4-
import { getSlot } from '../_util/props-util';
3+
import { getPropsSlot } from '../_util/props-util';
4+
import useConfigInject from '../_util/hooks/useConfigInject';
5+
6+
const breadcrumbSeparator = {
7+
prefixCls: PropTypes.string,
8+
};
9+
export type BreadcrumbSeparator = Partial<ExtractPropTypes<typeof breadcrumbSeparator>>;
510

611
export default defineComponent({
712
name: 'ABreadcrumbSeparator',
813
__ANT_BREADCRUMB_SEPARATOR: true,
914
inheritAttrs: false,
10-
props: {
11-
prefixCls: PropTypes.string,
12-
},
13-
setup() {
14-
return {
15-
configProvider: inject('configProvider', defaultConfigProvider),
16-
};
17-
},
18-
render() {
19-
const { prefixCls: customizePrefixCls } = this;
20-
const { separator, class: className, ...restAttrs } = this.$attrs;
21-
const getPrefixCls = this.configProvider.getPrefixCls;
22-
const prefixCls = getPrefixCls('breadcrumb', customizePrefixCls);
15+
props: breadcrumbSeparator,
16+
setup(props, { slots, attrs }) {
17+
const { prefixCls } = useConfigInject('breadcrumb', props);
18+
19+
return () => {
20+
const { separator, class: className, ...restAttrs } = attrs;
21+
const children = getPropsSlot(slots, props) || [];
2322

24-
const children = getSlot(this);
25-
return (
26-
<span class={[`${prefixCls}-separator`, className]} {...restAttrs}>
27-
{children.length > 0 ? children : '/'}
28-
</span>
29-
);
23+
return (
24+
<span class={[`${prefixCls.value}-separator`, className]} {...restAttrs}>
25+
{children.length > 0 ? children : '/'}
26+
</span>
27+
);
28+
};
3029
},
3130
});

components/breadcrumb/__tests__/Breadcrumb.test.js

+21
Original file line numberDiff line numberDiff line change
@@ -107,4 +107,25 @@ describe('Breadcrumb', () => {
107107
});
108108
expect(wrapper.html()).toMatchSnapshot();
109109
});
110+
111+
// https://github.com/ant-design/ant-design/issues/25975
112+
it('should support Breadcrumb.Item default separator', () => {
113+
const MockComponent = () => (
114+
<span>
115+
<Breadcrumb.Item>Mock Node</Breadcrumb.Item>
116+
</span>
117+
);
118+
const wrapper = mount({
119+
render() {
120+
return (
121+
<Breadcrumb>
122+
<Breadcrumb.Item>Location</Breadcrumb.Item>
123+
<MockComponent />
124+
<Breadcrumb.Item>Application Center</Breadcrumb.Item>
125+
</Breadcrumb>
126+
);
127+
},
128+
});
129+
expect(wrapper.html()).toMatchSnapshot();
130+
});
110131
});

components/breadcrumb/__tests__/__snapshots__/Breadcrumb.test.js.snap

+7-1
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,14 @@
22

33
exports[`Breadcrumb should allow Breadcrumb.Item is null or undefined 1`] = `<div class="ant-breadcrumb"><span><span class="ant-breadcrumb-link">Home</span><span class="ant-breadcrumb-separator">/</span></span></div>`;
44

5-
exports[`Breadcrumb should not display Breadcrumb Item when its children is falsy 1`] = `<div class="ant-breadcrumb"><span><span class="ant-breadcrumb-link"></span><span class="ant-breadcrumb-separator">/</span></span><span><span class="ant-breadcrumb-link">xxx</span><span class="ant-breadcrumb-separator">/</span></span><span><span class="ant-breadcrumb-link">yyy</span><span class="ant-breadcrumb-separator">/</span></span></div>`;
5+
exports[`Breadcrumb should not display Breadcrumb Item when its children is falsy 1`] = `
6+
<div class="ant-breadcrumb">
7+
<!----><span><span class="ant-breadcrumb-link">xxx</span><span class="ant-breadcrumb-separator">/</span></span><span><span class="ant-breadcrumb-link">yyy</span><span class="ant-breadcrumb-separator">/</span></span>
8+
</div>
9+
`;
610

711
exports[`Breadcrumb should render a menu 1`] = `<div class="ant-breadcrumb"><span><span class="ant-breadcrumb-link"><a href="#/index">home</a></span><span class="ant-breadcrumb-separator">/</span></span><span><!----><span class="ant-breadcrumb-overlay-link ant-dropdown-trigger"><span class="ant-breadcrumb-link"><a href="#/index/first">first</a></span><span role="img" aria-label="down" class="anticon anticon-down"><svg class="" data-icon="down" width="1em" height="1em" fill="currentColor" aria-hidden="true" viewBox="64 64 896 896" focusable="false"><path d="M884 256h-75c-5.1 0-9.9 2.5-12.9 6.6L512 654.2 227.9 262.6c-3-4.1-7.8-6.6-12.9-6.6h-75c-6.5 0-10.3 7.4-6.5 12.7l352.6 486.1c12.8 17.6 39 17.6 51.7 0l352.6-486.1c3.9-5.3.1-12.7-6.4-12.7z"></path></svg></span></span><span class="ant-breadcrumb-separator">/</span></span><span><span class="ant-breadcrumb-link"><span>second</span></span><span class="ant-breadcrumb-separator">/</span></span></div>`;
812

13+
exports[`Breadcrumb should support Breadcrumb.Item default separator 1`] = `<div class="ant-breadcrumb"><span><span class="ant-breadcrumb-link">Location</span><span class="ant-breadcrumb-separator">/</span></span><span><span><span class="ant-breadcrumb-link">Mock Node</span><span class="ant-breadcrumb-separator">/</span></span></span><span><span class="ant-breadcrumb-link">Application Center</span><span class="ant-breadcrumb-separator">/</span></span></div>`;
14+
915
exports[`Breadcrumb should support custom attribute 1`] = `<div class="ant-breadcrumb" data-custom="custom"><span data-custom="custom-item"><span class="ant-breadcrumb-link">xxx</span><span class="ant-breadcrumb-separator">/</span></span><span><span class="ant-breadcrumb-link">yyy</span><span class="ant-breadcrumb-separator">/</span></span></div>`;

components/breadcrumb/index.ts

+4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@ import Breadcrumb from './Breadcrumb';
33
import BreadcrumbItem from './BreadcrumbItem';
44
import BreadcrumbSeparator from './BreadcrumbSeparator';
55

6+
export { BreadcrumbProps } from './Breadcrumb';
7+
export { BreadcrumbItemProps } from './BreadcrumbItem';
8+
export { BreadcrumbSeparator } from './BreadcrumbSeparator';
9+
610
Breadcrumb.Item = BreadcrumbItem;
711
Breadcrumb.Separator = BreadcrumbSeparator;
812

components/vc-mentions/src/DropdownMenu.jsx

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
import Menu from '../../menu';
1+
import Menu, { Item as MenuItem } from '../../menu';
22
import PropTypes from '../../_util/vue-types';
33
import { OptionProps } from './Option';
44
import { inject } from 'vue';
55

6-
const MenuItem = Menu.Item;
7-
86
function noop() {}
97
export default {
108
name: 'DropdownMenu',

0 commit comments

Comments
 (0)