refactor: breadcrumb (#4137)
parent
772ac3c894
commit
770e9cc6f4
|
@ -1,11 +1,11 @@
|
|||
import { inject, cloneVNode, defineComponent, PropType } from 'vue';
|
||||
import { cloneVNode, defineComponent, PropType, ExtractPropTypes } from 'vue';
|
||||
import PropTypes from '../_util/vue-types';
|
||||
import { filterEmpty, getComponent, getSlot } from '../_util/props-util';
|
||||
import { filterEmpty, getPropsSlot } from '../_util/props-util';
|
||||
import warning from '../_util/warning';
|
||||
import { defaultConfigProvider } from '../config-provider';
|
||||
import BreadcrumbItem from './BreadcrumbItem';
|
||||
import Menu from '../menu';
|
||||
import { Omit, VueNode } from '../_util/type';
|
||||
import useConfigInject from '../_util/hooks/useConfigInject';
|
||||
|
||||
export interface Route {
|
||||
path: string;
|
||||
|
@ -13,7 +13,7 @@ export interface Route {
|
|||
children?: Omit<Route, 'children'>[];
|
||||
}
|
||||
|
||||
const BreadcrumbProps = {
|
||||
const breadcrumbProps = {
|
||||
prefixCls: PropTypes.string,
|
||||
routes: { type: Array as PropType<Route[]> },
|
||||
params: PropTypes.any,
|
||||
|
@ -25,6 +25,8 @@ const BreadcrumbProps = {
|
|||
},
|
||||
};
|
||||
|
||||
export type BreadcrumbProps = Partial<ExtractPropTypes<typeof breadcrumbProps>>;
|
||||
|
||||
function getBreadcrumbName(route: Route, params: unknown) {
|
||||
if (!route.breadcrumbName) {
|
||||
return null;
|
||||
|
@ -50,34 +52,36 @@ function defaultItemRender(opt: {
|
|||
|
||||
export default defineComponent({
|
||||
name: 'ABreadcrumb',
|
||||
props: BreadcrumbProps,
|
||||
setup() {
|
||||
return {
|
||||
configProvider: inject('configProvider', defaultConfigProvider),
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
getPath(path: string, params: unknown) {
|
||||
props: breadcrumbProps,
|
||||
setup(props, { slots }) {
|
||||
const { prefixCls, direction } = useConfigInject('breadcrumb', props);
|
||||
|
||||
const getPath = (path: string, params: unknown) => {
|
||||
path = (path || '').replace(/^\//, '');
|
||||
Object.keys(params).forEach(key => {
|
||||
path = path.replace(`:${key}`, params[key]);
|
||||
});
|
||||
return path;
|
||||
},
|
||||
};
|
||||
|
||||
addChildPath(paths: string[], childPath = '', params: unknown) {
|
||||
const addChildPath = (paths: string[], childPath = '', params: unknown) => {
|
||||
const originalPaths = [...paths];
|
||||
const path = this.getPath(childPath, params);
|
||||
const path = getPath(childPath, params);
|
||||
if (path) {
|
||||
originalPaths.push(path);
|
||||
}
|
||||
return originalPaths;
|
||||
},
|
||||
};
|
||||
|
||||
genForRoutes({ routes = [], params = {}, separator, itemRender = defaultItemRender }: any) {
|
||||
const genForRoutes = ({
|
||||
routes = [],
|
||||
params = {},
|
||||
separator,
|
||||
itemRender = defaultItemRender,
|
||||
}: any) => {
|
||||
const paths = [];
|
||||
return routes.map((route: Route) => {
|
||||
const path = this.getPath(route.path, params);
|
||||
const path = getPath(route.path, params);
|
||||
|
||||
if (path) {
|
||||
paths.push(path);
|
||||
|
@ -94,7 +98,7 @@ export default defineComponent({
|
|||
route: child,
|
||||
params,
|
||||
routes,
|
||||
paths: this.addChildPath(tempPaths, child.path, params),
|
||||
paths: addChildPath(tempPaths, child.path, params),
|
||||
})}
|
||||
</Menu.Item>
|
||||
))}
|
||||
|
@ -112,36 +116,42 @@ export default defineComponent({
|
|||
</BreadcrumbItem>
|
||||
);
|
||||
});
|
||||
},
|
||||
},
|
||||
render() {
|
||||
let crumbs: VueNode[];
|
||||
const { prefixCls: customizePrefixCls, routes, params = {}, $slots } = this;
|
||||
const getPrefixCls = this.configProvider.getPrefixCls;
|
||||
const prefixCls = getPrefixCls('breadcrumb', customizePrefixCls);
|
||||
};
|
||||
return () => {
|
||||
let crumbs: VueNode[];
|
||||
|
||||
const children = filterEmpty(getSlot(this));
|
||||
const separator = getComponent(this, 'separator');
|
||||
const itemRender = this.itemRender || $slots.itemRender || defaultItemRender;
|
||||
if (routes && routes.length > 0) {
|
||||
// generated by route
|
||||
crumbs = this.genForRoutes({
|
||||
routes,
|
||||
params,
|
||||
separator,
|
||||
itemRender,
|
||||
});
|
||||
} else if (children.length) {
|
||||
crumbs = children.map((element, index) => {
|
||||
warning(
|
||||
typeof element.type === 'object' &&
|
||||
(element.type.__ANT_BREADCRUMB_ITEM || element.type.__ANT_BREADCRUMB_SEPARATOR),
|
||||
'Breadcrumb',
|
||||
"Only accepts Breadcrumb.Item and Breadcrumb.Separator as it's children",
|
||||
);
|
||||
return cloneVNode(element, { separator, key: index });
|
||||
});
|
||||
}
|
||||
return <div class={prefixCls}>{crumbs}</div>;
|
||||
const { routes, params = {} } = props;
|
||||
|
||||
const children = filterEmpty(getPropsSlot(slots, props));
|
||||
const separator = getPropsSlot(slots, props, 'separator');
|
||||
|
||||
const itemRender = getPropsSlot(slots, props, 'itemRender') || defaultItemRender;
|
||||
if (routes && routes.length > 0) {
|
||||
// generated by route
|
||||
crumbs = genForRoutes({
|
||||
routes,
|
||||
params,
|
||||
separator,
|
||||
itemRender,
|
||||
});
|
||||
} else if (children.length) {
|
||||
crumbs = children.map((element, index) => {
|
||||
warning(
|
||||
typeof element.type === 'object' &&
|
||||
(element.type.__ANT_BREADCRUMB_ITEM || element.type.__ANT_BREADCRUMB_SEPARATOR),
|
||||
'Breadcrumb',
|
||||
"Only accepts Breadcrumb.Item and Breadcrumb.Separator as it's children",
|
||||
);
|
||||
return cloneVNode(element, { separator, key: index });
|
||||
});
|
||||
}
|
||||
|
||||
const breadcrumbClassName = {
|
||||
[prefixCls.value]: true,
|
||||
[`${prefixCls.value}-rtl`]: direction.value === 'rtl',
|
||||
};
|
||||
return <div class={breadcrumbClassName}>{crumbs}</div>;
|
||||
};
|
||||
},
|
||||
methods: {},
|
||||
});
|
||||
|
|
|
@ -1,31 +1,30 @@
|
|||
import { defineComponent, inject } from 'vue';
|
||||
import { defineComponent, ExtractPropTypes } from 'vue';
|
||||
import PropTypes from '../_util/vue-types';
|
||||
import { hasProp, getComponent, getSlot } from '../_util/props-util';
|
||||
import { defaultConfigProvider } from '../config-provider';
|
||||
import { getPropsSlot } from '../_util/props-util';
|
||||
import DropDown from '../dropdown/dropdown';
|
||||
import DownOutlined from '@ant-design/icons-vue/DownOutlined';
|
||||
import useConfigInject from '../_util/hooks/useConfigInject';
|
||||
|
||||
const breadcrumbItemProps = {
|
||||
prefixCls: PropTypes.string,
|
||||
href: PropTypes.string,
|
||||
separator: PropTypes.VNodeChild.def('/'),
|
||||
overlay: PropTypes.VNodeChild,
|
||||
};
|
||||
|
||||
export type BreadcrumbItemProps = Partial<ExtractPropTypes<typeof breadcrumbItemProps>>;
|
||||
export default defineComponent({
|
||||
name: 'ABreadcrumbItem',
|
||||
__ANT_BREADCRUMB_ITEM: true,
|
||||
props: {
|
||||
prefixCls: PropTypes.string,
|
||||
href: PropTypes.string,
|
||||
separator: PropTypes.VNodeChild.def('/'),
|
||||
overlay: PropTypes.VNodeChild,
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
configProvider: inject('configProvider', defaultConfigProvider),
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
props: breadcrumbItemProps,
|
||||
setup(props, { slots }) {
|
||||
const { prefixCls } = useConfigInject('breadcrumb', props);
|
||||
/**
|
||||
* if overlay is have
|
||||
* Wrap a DropDown
|
||||
*/
|
||||
renderBreadcrumbNode(breadcrumbItem: JSX.Element, prefixCls: string) {
|
||||
const overlay = getComponent(this, 'overlay');
|
||||
const renderBreadcrumbNode = (breadcrumbItem: JSX.Element, prefixCls: string) => {
|
||||
const overlay = getPropsSlot(slots, props, 'overlay');
|
||||
if (overlay) {
|
||||
return (
|
||||
<DropDown overlay={overlay} placement="bottomCenter">
|
||||
|
@ -37,32 +36,31 @@ export default defineComponent({
|
|||
);
|
||||
}
|
||||
return breadcrumbItem;
|
||||
},
|
||||
},
|
||||
render() {
|
||||
const { prefixCls: customizePrefixCls } = this;
|
||||
const getPrefixCls = this.configProvider.getPrefixCls;
|
||||
const prefixCls = getPrefixCls('breadcrumb', customizePrefixCls);
|
||||
const separator = getComponent(this, 'separator');
|
||||
const children = getSlot(this);
|
||||
let link: JSX.Element;
|
||||
if (hasProp(this, 'href')) {
|
||||
link = <a class={`${prefixCls}-link`}>{children}</a>;
|
||||
} else {
|
||||
link = <span class={`${prefixCls}-link`}>{children}</span>;
|
||||
}
|
||||
// wrap to dropDown
|
||||
link = this.renderBreadcrumbNode(link, prefixCls);
|
||||
if (children) {
|
||||
return (
|
||||
<span>
|
||||
{link}
|
||||
{separator && separator !== '' && (
|
||||
<span class={`${prefixCls}-separator`}>{separator}</span>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
return () => {
|
||||
const separator = getPropsSlot(slots, props, 'separator');
|
||||
const children = getPropsSlot(slots, props);
|
||||
let link: JSX.Element;
|
||||
|
||||
if ('href' in props) {
|
||||
link = <a class={`${prefixCls.value}-link`}>{children}</a>;
|
||||
} else {
|
||||
link = <span class={`${prefixCls.value}-link`}>{children}</span>;
|
||||
}
|
||||
// wrap to dropDown
|
||||
link = renderBreadcrumbNode(link, prefixCls.value);
|
||||
if (children) {
|
||||
return (
|
||||
<span>
|
||||
{link}
|
||||
{separator && separator !== '' && (
|
||||
<span class={`${prefixCls.value}-separator`}>{separator}</span>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,31 +1,30 @@
|
|||
import { defineComponent, inject } from 'vue';
|
||||
import { defaultConfigProvider } from '../config-provider';
|
||||
import { defineComponent, ExtractPropTypes } from 'vue';
|
||||
import PropTypes from '../_util/vue-types';
|
||||
import { getSlot } from '../_util/props-util';
|
||||
import { getPropsSlot } from '../_util/props-util';
|
||||
import useConfigInject from '../_util/hooks/useConfigInject';
|
||||
|
||||
const breadcrumbSeparator = {
|
||||
prefixCls: PropTypes.string,
|
||||
};
|
||||
export type BreadcrumbSeparator = Partial<ExtractPropTypes<typeof breadcrumbSeparator>>;
|
||||
|
||||
export default defineComponent({
|
||||
name: 'ABreadcrumbSeparator',
|
||||
__ANT_BREADCRUMB_SEPARATOR: true,
|
||||
inheritAttrs: false,
|
||||
props: {
|
||||
prefixCls: PropTypes.string,
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
configProvider: inject('configProvider', defaultConfigProvider),
|
||||
props: breadcrumbSeparator,
|
||||
setup(props, { slots, attrs }) {
|
||||
const { prefixCls } = useConfigInject('breadcrumb', props);
|
||||
|
||||
return () => {
|
||||
const { separator, class: className, ...restAttrs } = attrs;
|
||||
const children = getPropsSlot(slots, props) || [];
|
||||
|
||||
return (
|
||||
<span class={[`${prefixCls.value}-separator`, className]} {...restAttrs}>
|
||||
{children.length > 0 ? children : '/'}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
},
|
||||
render() {
|
||||
const { prefixCls: customizePrefixCls } = this;
|
||||
const { separator, class: className, ...restAttrs } = this.$attrs;
|
||||
const getPrefixCls = this.configProvider.getPrefixCls;
|
||||
const prefixCls = getPrefixCls('breadcrumb', customizePrefixCls);
|
||||
|
||||
const children = getSlot(this);
|
||||
return (
|
||||
<span class={[`${prefixCls}-separator`, className]} {...restAttrs}>
|
||||
{children.length > 0 ? children : '/'}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
|
@ -107,4 +107,25 @@ describe('Breadcrumb', () => {
|
|||
});
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
// https://github.com/ant-design/ant-design/issues/25975
|
||||
it('should support Breadcrumb.Item default separator', () => {
|
||||
const MockComponent = () => (
|
||||
<span>
|
||||
<Breadcrumb.Item>Mock Node</Breadcrumb.Item>
|
||||
</span>
|
||||
);
|
||||
const wrapper = mount({
|
||||
render() {
|
||||
return (
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item>Location</Breadcrumb.Item>
|
||||
<MockComponent />
|
||||
<Breadcrumb.Item>Application Center</Breadcrumb.Item>
|
||||
</Breadcrumb>
|
||||
);
|
||||
},
|
||||
});
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -2,8 +2,14 @@
|
|||
|
||||
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>`;
|
||||
|
||||
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>`;
|
||||
exports[`Breadcrumb should not display Breadcrumb Item when its children is falsy 1`] = `
|
||||
<div class="ant-breadcrumb">
|
||||
<!----><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>
|
||||
`;
|
||||
|
||||
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>`;
|
||||
|
||||
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>`;
|
||||
|
||||
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>`;
|
||||
|
|
|
@ -3,6 +3,10 @@ import Breadcrumb from './Breadcrumb';
|
|||
import BreadcrumbItem from './BreadcrumbItem';
|
||||
import BreadcrumbSeparator from './BreadcrumbSeparator';
|
||||
|
||||
export { BreadcrumbProps } from './Breadcrumb';
|
||||
export { BreadcrumbItemProps } from './BreadcrumbItem';
|
||||
export { BreadcrumbSeparator } from './BreadcrumbSeparator';
|
||||
|
||||
Breadcrumb.Item = BreadcrumbItem;
|
||||
Breadcrumb.Separator = BreadcrumbSeparator;
|
||||
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
import Menu from '../../menu';
|
||||
import Menu, { Item as MenuItem } from '../../menu';
|
||||
import PropTypes from '../../_util/vue-types';
|
||||
import { OptionProps } from './Option';
|
||||
import { inject } from 'vue';
|
||||
|
||||
const MenuItem = Menu.Item;
|
||||
|
||||
function noop() {}
|
||||
export default {
|
||||
name: 'DropdownMenu',
|
||||
|
|
Loading…
Reference in New Issue