refactor: breadcrumb (#4137)

pull/4175/head
John 2021-06-08 16:47:09 +08:00 committed by GitHub
parent 772ac3c894
commit 770e9cc6f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 155 additions and 119 deletions

View File

@ -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: {},
});

View File

@ -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;
};
},
});

View File

@ -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>
);
},
});

View File

@ -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();
});
});

View File

@ -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>`;

View File

@ -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;

View File

@ -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',