diff --git a/build/config.js b/build/config.js index 954463dbe..9648f68e5 100644 --- a/build/config.js +++ b/build/config.js @@ -1,5 +1,5 @@ module.exports = { dev: { - componentName: 'button', // dev components + componentName: 'breadcrumb', // dev components }, }; diff --git a/components/breadcrumb/Breadcrumb.jsx b/components/breadcrumb/Breadcrumb.jsx index 82af7120b..6b17d93f3 100644 --- a/components/breadcrumb/Breadcrumb.jsx +++ b/components/breadcrumb/Breadcrumb.jsx @@ -4,10 +4,12 @@ import { filterEmpty, getComponentFromProp, getSlotOptions } from '../_util/prop import warning from '../_util/warning'; import { ConfigConsumerProps } from '../config-provider'; import BreadcrumbItem from './BreadcrumbItem'; +import Menu from '../menu'; const Route = PropTypes.shape({ path: PropTypes.string, breadcrumbName: PropTypes.string, + children: PropTypes.array, }).loose; const BreadcrumbProps = { @@ -42,6 +44,62 @@ export default { const name = getBreadcrumbName(route, params); return isLastItem ? {name} : {name}; }, + getPath(path, params) { + path = (path || '').replace(/^\//, ''); + Object.keys(params).forEach(key => { + path = path.replace(`:${key}`, params[key]); + }); + return path; + }, + + addChildPath(paths, childPath, params) { + const originalPaths = [...paths]; + const path = this.getPath(childPath, params); + if (path) { + originalPaths.push(path); + } + return originalPaths; + }, + + genForRoutes({ routes = [], params = {}, separator, itemRender = defaultItemRender }) { + const paths = []; + return routes.map(route => { + const path = this.getPath(route.path, params); + + if (path) { + paths.push(path); + } + // generated overlay by route.children + let overlay = null; + if (route.children && route.children.length) { + overlay = ( + + {route.children.map(child => ( + + {itemRender({ + route: child, + params, + routes, + paths: this.addChildPath(paths, child.path, params), + h: this.$createElement, + })} + + ))} + + ); + } + + return ( + + {itemRender({ route, params, routes, paths, h: this.$createElement })} + + ); + }); + }, }, render() { let crumbs; @@ -51,29 +109,22 @@ export default { const children = filterEmpty($slots.default); const separator = getComponentFromProp(this, 'separator'); + const itemRender = this.itemRender || $scopedSlots.itemRender || this.defaultItemRender; if (routes && routes.length > 0) { - const paths = []; - const itemRender = this.itemRender || $scopedSlots.itemRender || this.defaultItemRender; - crumbs = routes.map(route => { - route.path = route.path || ''; - let path = route.path.replace(/^\//, ''); - Object.keys(params).forEach(key => { - path = path.replace(`:${key}`, params[key]); - }); - if (path) { - paths.push(path); - } - return ( - - {itemRender({ route, params, routes, paths, h })} - - ); + // generated by route + crumbs = this.genForRoutes({ + routes, + params, + separator, + itemRender, }); } else if (children.length) { crumbs = children.map((element, index) => { warning( - getSlotOptions(element).__ANT_BREADCRUMB_ITEM, - "Breadcrumb only accepts Breadcrumb.Item as it's children", + getSlotOptions(element).__ANT_BREADCRUMB_ITEM || + getSlotOptions(element).__ANT_BREADCRUMB_SEPARATOR, + 'Breadcrumb', + "Only accepts Breadcrumb.Item and Breadcrumb.Separator as it's children", ); return cloneElement(element, { props: { separator }, diff --git a/components/breadcrumb/BreadcrumbItem.jsx b/components/breadcrumb/BreadcrumbItem.jsx index f90cb023e..bfebfb63c 100644 --- a/components/breadcrumb/BreadcrumbItem.jsx +++ b/components/breadcrumb/BreadcrumbItem.jsx @@ -1,6 +1,8 @@ import PropTypes from '../_util/vue-types'; import { hasProp, getComponentFromProp } from '../_util/props-util'; import { ConfigConsumerProps } from '../config-provider'; +import DropDown from '../dropdown/dropdown'; +import Icon from '../icon'; export default { name: 'ABreadcrumbItem', @@ -8,16 +10,37 @@ export default { props: { prefixCls: PropTypes.string, href: PropTypes.string, - separator: PropTypes.any, + separator: PropTypes.any.def('/'), + overlay: PropTypes.any, }, inject: { configProvider: { default: () => ConfigConsumerProps }, }, + methods: { + /** + * if overlay is have + * Wrap a DropDown + */ + renderBreadcrumbNode(breadcrumbItem, prefixCls) { + const overlay = getComponentFromProp(this, 'overlay'); + if (overlay) { + return ( + + + {breadcrumbItem} + + + + ); + } + return breadcrumbItem; + }, + }, render() { const { prefixCls: customizePrefixCls, $slots } = this; const getPrefixCls = this.configProvider.getPrefixCls; const prefixCls = getPrefixCls('breadcrumb', customizePrefixCls); - + const separator = getComponentFromProp(this, 'separator'); const children = $slots.default; let link; if (hasProp(this, 'href')) { @@ -25,13 +48,15 @@ export default { } else { link = {children}; } + // wrap to dropDown + link = this.renderBreadcrumbNode(link, prefixCls); if (children) { return ( {link} - - {getComponentFromProp(this, 'separator') || '/'} - + {separator && separator !== '' && ( + {separator} + )} ); } diff --git a/components/breadcrumb/BreadcrumbSeparator.jsx b/components/breadcrumb/BreadcrumbSeparator.jsx new file mode 100644 index 000000000..09f0b2c6f --- /dev/null +++ b/components/breadcrumb/BreadcrumbSeparator.jsx @@ -0,0 +1,21 @@ +import { ConfigConsumerProps } from '../config-provider'; +import PropTypes from '../_util/vue-types'; + +export default { + name: 'ABreadcrumbSeparator', + __ANT_BREADCRUMB_SEPARATOR: true, + props: { + prefixCls: PropTypes.string, + }, + inject: { + configProvider: { default: () => ConfigConsumerProps }, + }, + render() { + const { prefixCls: customizePrefixCls, $slots } = this; + const getPrefixCls = this.configProvider.getPrefixCls; + const prefixCls = getPrefixCls('breadcrumb', customizePrefixCls); + + const children = $slots.default; + return {children || '/'}; + }, +}; diff --git a/components/breadcrumb/__tests__/Breadcrumb.test.js b/components/breadcrumb/__tests__/Breadcrumb.test.js index e247b57aa..fc6c07515 100644 --- a/components/breadcrumb/__tests__/Breadcrumb.test.js +++ b/components/breadcrumb/__tests__/Breadcrumb.test.js @@ -13,7 +13,7 @@ describe('Breadcrumb', () => { }); // // https://github.com/airbnb/enzyme/issues/875 - it('warns on non-Breadcrumb.Item children', () => { + it('warns on non-Breadcrumb.Item and non-Breadcrumb.Separator children', () => { mount({ render() { return ( @@ -25,7 +25,7 @@ describe('Breadcrumb', () => { }); expect(errorSpy.mock.calls).toHaveLength(1); expect(errorSpy.mock.calls[0][0]).toMatch( - "Breadcrumb only accepts Breadcrumb.Item as it's children", + "Warning: [antdv: Breadcrumb] Only accepts Breadcrumb.Item and Breadcrumb.Separator as it's children", ); }); @@ -61,4 +61,50 @@ describe('Breadcrumb', () => { }); expect(wrapper.html()).toMatchSnapshot(); }); + it('should render a menu', () => { + const routes = [ + { + path: 'index', + breadcrumbName: 'home', + }, + { + path: 'first', + breadcrumbName: 'first', + children: [ + { + path: '/general', + breadcrumbName: 'General', + }, + { + path: '/layout', + breadcrumbName: 'Layout', + }, + { + path: '/navigation', + breadcrumbName: 'Navigation', + }, + ], + }, + { + path: 'second', + breadcrumbName: 'second', + }, + ]; + const wrapper = mount(Breadcrumb, { propsData: { routes } }); + expect(wrapper.html()).toMatchSnapshot(); + }); + + it('should support custom attribute', () => { + const wrapper = mount({ + render() { + return ( + + xxx + yyy + + ); + }, + }); + expect(wrapper.html()).toMatchSnapshot(); + }); }); diff --git a/components/breadcrumb/__tests__/__snapshots__/Breadcrumb.test.js.snap b/components/breadcrumb/__tests__/__snapshots__/Breadcrumb.test.js.snap index faa3fc8b8..bf76d0d88 100644 --- a/components/breadcrumb/__tests__/__snapshots__/Breadcrumb.test.js.snap +++ b/components/breadcrumb/__tests__/__snapshots__/Breadcrumb.test.js.snap @@ -6,3 +6,11 @@ exports[`Breadcrumb should not display Breadcrumb Item when its children is fals
xxx/yyy/
`; + +exports[`Breadcrumb should render a menu 1`] = ` +
home/first/second/
+`; + +exports[`Breadcrumb should support custom attribute 1`] = `
xxx/yyy/
`; diff --git a/components/breadcrumb/__tests__/__snapshots__/demo.test.js.snap b/components/breadcrumb/__tests__/__snapshots__/demo.test.js.snap index 60427dd32..79edce515 100644 --- a/components/breadcrumb/__tests__/__snapshots__/demo.test.js.snap +++ b/components/breadcrumb/__tests__/__snapshots__/demo.test.js.snap @@ -2,6 +2,12 @@ exports[`renders ./components/breadcrumb/demo/basic.md correctly 1`] = `
Home/Application Center/Application List/An Application/
`; +exports[`renders ./components/breadcrumb/demo/overlay.md correctly 1`] = ` +
Ant Design Vue/Component/General /Button/
+`; + exports[`renders ./components/breadcrumb/demo/router.md correctly 1`] = ``; exports[`renders ./components/breadcrumb/demo/separator.md correctly 1`] = ` @@ -11,6 +17,8 @@ exports[`renders ./components/breadcrumb/demo/separator.md correctly 1`] = ` `; +exports[`renders ./components/breadcrumb/demo/separator-indepent.md correctly 1`] = `
Location:Application Center/Application List/An Application
`; + exports[`renders ./components/breadcrumb/demo/withIcon.md correctly 1`] = `
/ Application List/ Application diff --git a/components/breadcrumb/demo/index.vue b/components/breadcrumb/demo/index.vue index 1ec3cf19d..ab4d25c02 100644 --- a/components/breadcrumb/demo/index.vue +++ b/components/breadcrumb/demo/index.vue @@ -3,6 +3,8 @@ import Basic from './basic.md'; import WithIcon from './withIcon.md'; import Separator from './separator.md'; import Router from './router'; +import Overlay from './Overlay'; +import SeparatorIndepent from './separator-indepent'; import US from './../index.en-US.md'; import CN from './../index.zh-CN.md'; @@ -41,6 +43,8 @@ export default { + + diff --git a/components/breadcrumb/demo/overlay.md b/components/breadcrumb/demo/overlay.md new file mode 100644 index 000000000..323efd389 --- /dev/null +++ b/components/breadcrumb/demo/overlay.md @@ -0,0 +1,39 @@ + + #### 带下拉菜单的面包屑 + 面包屑支持下拉菜单。 + + + + #### Bread crumbs with drop down menu + Breadcrumbs support drop down menu. + + +```tpl + +``` diff --git a/components/breadcrumb/demo/router.md b/components/breadcrumb/demo/router.md index 82a1ebe1b..fd33dad1d 100644 --- a/components/breadcrumb/demo/router.md +++ b/components/breadcrumb/demo/router.md @@ -30,19 +30,33 @@ Used together with `vue-router` data() { const { lang } = this.$route.params; return { - basePath: `/${lang}/components/breadcrumb`, + basePath: '/components/breadcrumb', routes: [ { path: 'index', - breadcrumbName: '首页', + breadcrumbName: 'home', }, { path: 'first', - breadcrumbName: '一级面包屑', + breadcrumbName: 'first', + children: [ + { + path: '/general', + breadcrumbName: 'General', + }, + { + path: '/layout', + breadcrumbName: 'Layout', + }, + { + path: '/navigation', + breadcrumbName: 'Navigation', + }, + ], }, { path: 'second', - breadcrumbName: '当前页面', + breadcrumbName: 'second', }, ], }; diff --git a/components/breadcrumb/demo/separator-indepent.md b/components/breadcrumb/demo/separator-indepent.md new file mode 100644 index 000000000..afe6f758f --- /dev/null +++ b/components/breadcrumb/demo/separator-indepent.md @@ -0,0 +1,23 @@ + + #### 分隔符 + 使用 `Breadcrumb.Separator` 可以自定义分隔符。 + + + + #### Configuring the Separator + The separator can be customized by setting the separator property: `Breadcrumb.Separator` + + +```tpl + +``` diff --git a/components/breadcrumb/index.en-US.md b/components/breadcrumb/index.en-US.md index 62953638f..89a6261f9 100644 --- a/components/breadcrumb/index.en-US.md +++ b/components/breadcrumb/index.en-US.md @@ -1,11 +1,24 @@ ## API -| Property | Description | Type | Optional | Default | -| --- | --- | --- | --- | --- | -| itemRender | Custom item renderer, slot="itemRender" and slot-scope="{route, params, routes, paths}" | ({route, params, routes, paths, h}) => vNode | | - | -| params | Routing parameters | object | | - | -| routes | The routing stack information of router | object\[] | | - | -| separator | Custom separator | string\|slot | | `/` | +| Property | Description | Type | Optional | Default | Version | +| --- | --- | --- | --- | --- | --- | +| itemRender | Custom item renderer, slot="itemRender" and slot-scope="{route, params, routes, paths}" | ({route, params, routes, paths, h}) => vNode | | - | | +| params | Routing parameters | object | | - | | +| routes | The routing stack information of router | [routes\[\]](#routes) | | - | | +| separator | Custom separator | string\|slot | | `/` | | + +### routes + +```ts +interface Route { + path: string; + breadcrumbName: string; + children: Array<{ + path: string; + breadcrumbName: string; + }>; +} +``` ### Use with browserHistory @@ -31,15 +44,29 @@ The link of Breadcrumb item targets `#` by default, you can use `itemRender` to routes: [ { path: 'index', - breadcrumbName: '首页', + breadcrumbName: 'home', }, { path: 'first', - breadcrumbName: '一级面包屑', + breadcrumbName: 'first', + children: [ + { + path: '/general', + breadcrumbName: 'General', + }, + { + path: '/layout', + breadcrumbName: 'Layout', + }, + { + path: '/navigation', + breadcrumbName: 'Navigation', + }, + ], }, { path: 'second', - breadcrumbName: '当前页面', + breadcrumbName: 'second', }, ], }; diff --git a/components/breadcrumb/index.js b/components/breadcrumb/index.js index 4387b6959..f740b2def 100644 --- a/components/breadcrumb/index.js +++ b/components/breadcrumb/index.js @@ -1,14 +1,17 @@ import Breadcrumb from './Breadcrumb'; import BreadcrumbItem from './BreadcrumbItem'; +import BreadcrumbSeparator from './BreadcrumbSeparator'; import Base from '../base'; Breadcrumb.Item = BreadcrumbItem; +Breadcrumb.Separator = BreadcrumbSeparator; /* istanbul ignore next */ Breadcrumb.install = function(Vue) { Vue.use(Base); Vue.component(Breadcrumb.name, Breadcrumb); Vue.component(BreadcrumbItem.name, BreadcrumbItem); + Vue.component(BreadcrumbSeparator.name, BreadcrumbSeparator); }; export default Breadcrumb; diff --git a/components/breadcrumb/index.zh-CN.md b/components/breadcrumb/index.zh-CN.md index 09c7c7cd2..ce15c621d 100644 --- a/components/breadcrumb/index.zh-CN.md +++ b/components/breadcrumb/index.zh-CN.md @@ -4,9 +4,43 @@ | --- | --- | --- | --- | --- | | itemRender | 自定义链接函数,和 vue-router 配置使用, 也可使用 slot="itemRender" 和 slot-scope="props" | ({route, params, routes, paths, h}) => vNode | | - | | params | 路由的参数 | object | | - | -| routes | router 的路由栈信息 | object\[] | | - | +| routes | router 的路由栈信息 | [routes\[\]](#routes) | | - | | separator | 分隔符自定义 | string\|slot | | '/' | +### Breadcrumb.Item + +| 参数 | 参数 | 类型 | 默认值 | 版本 | +| ------- | -------------- | -------------------------------------- | ------ | ----- | +| href | 链接的目的地 | string | - | 1.5.0 | +| overlay | 下拉菜单的内容 | [Menu](/components/menu) \| () => Menu | - | 1.5.0 | + +#### 事件 + +| 事件名称 | 说明 | 回调参数 | 版本 | +| -------- | -------- | -------------------- | ---- | +| click | 单击事件 | (e:MouseEvent)=>void | - | 1.5.0 | + +### Breadcrumb.Separator `3.21.0` + +| 参数 | 参数 | 类型 | 默认值 | 版本 | +| ---- | ---- | ---- | ------ | ---- | + + +> 注意:在使用 `Breadcrumb.Separator` 时,其父组件的分隔符必须设置为 `separator=""`,否则会出现父组件默认的分隔符。 + +### routes + +```ts +interface Route { + path: string; + breadcrumbName: string; + children: Array<{ + path: string; + breadcrumbName: string; + }>; +} +``` + ### 和 browserHistory 配合 和 vue-router 一起使用时,默认生成的 url 路径是带有 `#` 的,如果和 browserHistory 一起使用的话,你可以使用 `itemRender` 属性定义面包屑链接。 @@ -31,15 +65,29 @@ routes: [ { path: 'index', - breadcrumbName: '首页', + breadcrumbName: 'home', }, { path: 'first', - breadcrumbName: '一级面包屑', + breadcrumbName: 'first', + children: [ + { + path: '/general', + breadcrumbName: 'General', + }, + { + path: '/layout', + breadcrumbName: 'Layout', + }, + { + path: '/navigation', + breadcrumbName: 'Navigation', + }, + ], }, { path: 'second', - breadcrumbName: '当前页面', + breadcrumbName: 'second', }, ], }; diff --git a/types/breadcrumb/breadcrumb-item.d.ts b/types/breadcrumb/breadcrumb-item.d.ts index ff01d8f20..8e380b7c1 100644 --- a/types/breadcrumb/breadcrumb-item.d.ts +++ b/types/breadcrumb/breadcrumb-item.d.ts @@ -11,4 +11,5 @@ export declare class BreadcrumbItem extends AntdComponent { * @type string */ href?: String; + overlay?: any; } diff --git a/types/breadcrumb/breadcrumb-separator.ts b/types/breadcrumb/breadcrumb-separator.ts new file mode 100644 index 000000000..d209f407e --- /dev/null +++ b/types/breadcrumb/breadcrumb-separator.ts @@ -0,0 +1,7 @@ +// Project: https://github.com/vueComponent/ant-design-vue +// Definitions by: akki-jat +// Definitions: https://github.com/vueComponent/ant-design-vue/types + +import { AntdComponent } from '../component'; + +export declare class BreadcrumbSeparator extends AntdComponent {} diff --git a/types/breadcrumb/breadcrumb.d.ts b/types/breadcrumb/breadcrumb.d.ts index cb5718cac..ca2aefc92 100644 --- a/types/breadcrumb/breadcrumb.d.ts +++ b/types/breadcrumb/breadcrumb.d.ts @@ -5,6 +5,7 @@ import { AntdComponent } from '../component'; import { VNode } from 'vue'; import { BreadcrumbItem } from './breadcrumb-item'; +import { BreadcrumbSeparator } from './breadcrumb-separator'; export interface Route { path?: String; @@ -13,6 +14,7 @@ export interface Route { export declare class Breadcrumb extends AntdComponent { static Item: typeof BreadcrumbItem; + static Separator: typeof BreadcrumbSeparator; /** * The routing stack information of router * @type Route[]