update breadcrumb

pull/165/head
tjz 2018-03-10 13:34:26 +08:00
parent 42d0df1c9e
commit 878fe4da40
18 changed files with 271 additions and 155 deletions

View File

@ -7,7 +7,7 @@ import getTransitionProps from '../_util/getTransitionProps'
export const BadgeProps = { export const BadgeProps = {
/** Number to show in badge */ /** Number to show in badge */
count: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), count: PropTypes.oneOfType([PropTypes.number, PropTypes.string, null]),
showZero: PropTypes.bool, showZero: PropTypes.bool,
/** Max count to show */ /** Max count to show */
overflowCount: PropTypes.number, overflowCount: PropTypes.number,

View File

@ -14,7 +14,7 @@ function getNumberArray (num) {
const ScrollNumberProps = { const ScrollNumberProps = {
prefixCls: PropTypes.string.def('ant-scroll-number'), prefixCls: PropTypes.string.def('ant-scroll-number'),
count: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).def(null), count: PropTypes.oneOfType([PropTypes.number, PropTypes.string, null]).def(null),
component: PropTypes.string, component: PropTypes.string,
title: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), title: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
} }

View File

@ -1,24 +1,90 @@
<script> <script>
import PropTypes from '../_util/vue-types' import PropTypes from '../_util/vue-types'
import { cloneElement } from '../_util/vnode'
import { filterEmpty, getComponentFromProp, getSlotOptions } from '../_util/props-util'
import warning from '../_util/warning'
import BreadcrumbItem from './BreadcrumbItem'
export default { const Route = PropTypes.shape({
name: 'Breadcrumb', path: PropTypes.string,
props: { breadcrumbName: PropTypes.string,
prefixCls: PropTypes.string.def('ant-breadcrumb'), }).loose
separator: PropTypes.string.def('/'),
}, const BreadcrumbProps = {
provide () { prefixCls: PropTypes.string.def('ant-breadcrumb'),
return { routes: PropTypes.arrayOf(Route),
breadCrumbParent: this, params: PropTypes.any,
} separator: PropTypes.any,
}, itemRender: PropTypes.func,
render () { }
const { prefixCls } = this
return ( function getBreadcrumbName (route, params) {
<div class={prefixCls}> if (!route.breadcrumbName) {
{this.$slots.default} return null
</div>
)
},
} }
const paramsKeys = Object.keys(params).join('|')
const name = route.breadcrumbName.replace(
new RegExp(`:(${paramsKeys})`, 'g'),
(replacement, key) => params[key] || replacement,
)
return name
}
export default {
props: BreadcrumbProps,
methods: {
defaultItemRender ({ route, params, routes, paths }) {
const isLastItem = routes.indexOf(route) === routes.length - 1
const name = getBreadcrumbName(route, params)
return isLastItem
? <span>{name}</span>
: <a href={`#/${paths.join('/')}`}>{name}</a>
},
},
render () {
let crumbs
const {
prefixCls, routes, params = {},
$slots, $scopedSlots,
} = this
const children = filterEmpty($slots.default)
const separator = getComponentFromProp(this, 'separator')
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 (
<BreadcrumbItem separator={separator} key={route.breadcrumbName || path}>
{itemRender({ route, params, routes, paths })}
</BreadcrumbItem>
)
})
} else if (children.length) {
crumbs = children.map((element, index) => {
warning(
getSlotOptions(element).__ANT_BREADCRUMB_ITEM,
'Breadcrumb only accepts Breadcrumb.Item as it\'s children',
)
return cloneElement(element, {
props: { separator },
key: index,
})
})
}
return (
<div class={prefixCls}>
{crumbs}
</div>
)
},
}
</script> </script>

View File

@ -1,30 +1,29 @@
<script> <script>
import PropTypes from '../_util/vue-types' import PropTypes from '../_util/vue-types'
import hasProp from '../_util/props-util' import { hasProp, getComponentFromProp } from '../_util/props-util'
export default { export default {
name: 'BreadcrumbItem', name: 'BreadcrumbItem',
inject: ['breadCrumbParent'], __ANT_BREADCRUMB_ITEM: true,
props: { props: {
prefixCls: PropTypes.string.def('ant-breadcrumb'), prefixCls: PropTypes.string.def('ant-breadcrumb'),
href: PropTypes.string, href: PropTypes.string,
separator: PropTypes.any,
}, },
render () { render () {
const { prefixCls, ...restProps } = this.$props const { prefixCls, $slots } = this
const { breadCrumbParent = {}} = this const children = $slots.default
const { separator = '/' } = breadCrumbParent
const solt = this.$slots.default
let link let link
if (hasProp(this, 'href')) { if (hasProp(this, 'href')) {
link = <a class={`${prefixCls}-link`} {...restProps}>{solt}</a> link = <a class={`${prefixCls}-link`}>{children}</a>
} else { } else {
link = <span class={`${prefixCls}-link`} {...restProps}>{solt}</span> link = <span class={`${prefixCls}-link`}>{children}</span>
} }
if (solt) { if (children) {
return ( return (
<span> <span>
{link} {link}
<span class={`${prefixCls}-separator`}>{separator}</span> <span class={`${prefixCls}-separator`}>{getComponentFromProp(this, 'separator') || '/'}</span>
</span> </span>
) )
} }

View File

@ -10,24 +10,11 @@
```html ```html
<template> <template>
<Breadcrumb> <a-breadcrumb>
<BreadcrumbItem>Home</BreadcrumbItem> <a-breadcrumb-item>Home</a-breadcrumb-item>
<BreadcrumbItem><a href="">Application Center</a></BreadcrumbItem> <a-breadcrumb-item><a href="">Application Center</a></a-breadcrumb-item>
<BreadcrumbItem><a href="">Application List</a></BreadcrumbItem> <a-breadcrumb-item><a href="">Application List</a></a-breadcrumb-item>
<BreadcrumbItem>An Application</BreadcrumbItem> <a-breadcrumb-item>An Application</a-breadcrumb-item>
</Breadcrumb> </a-breadcrumb>
</template> </template>
<script>
import '../style'
import { Icon, Breadcrumb } from 'antd/index'
export default {
components: {
Icon,
Breadcrumb,
BreadcrumbItem: Breadcrumb.Item,
},
}
</script>
``` ```

View File

@ -2,6 +2,7 @@
import Basic from './basic.md' import Basic from './basic.md'
import WithIcon from './withIcon.md' import WithIcon from './withIcon.md'
import Separator from './separator.md' import Separator from './separator.md'
import Router from './router'
import US from './../index.en-US.md' import US from './../index.en-US.md'
import CN from './../index.zh-CN.md' import CN from './../index.zh-CN.md'
@ -27,6 +28,10 @@
} }
export default { export default {
category: 'Components',
subtitle: '面包屑',
type: 'Navigation',
title: 'Breadcrumb',
render () { render () {
return ( return (
<div> <div>
@ -34,6 +39,7 @@
<Basic /> <Basic />
<WithIcon /> <WithIcon />
<Separator /> <Separator />
<Router/>
<api> <api>
<CN slot='cn' /> <CN slot='cn' />
<US /> <US />

View File

@ -0,0 +1,48 @@
<cn>
#### vue-router
`vue-router` 进行结合使用。
</cn>
<us>
#### Vue Router Integration
Used together with `vue-router`
</us>
```html
<template>
<div>
<a-breadcrumb :routes="routes">
<template slot="itemRender" slot-scope="{route, params, routes, paths}">
<span v-if="routes.indexOf(route) === routes.length - 1">
{{route.breadcrumbName}}
</span>
<router-link v-else :to="`${basePath}/${paths.join('/')}`">
{{route.breadcrumbName}}
</router-link>
</template>
</a-breadcrumb>
<br/>
{{$route.path}}
</div>
</template>
<script>
export default {
data(){
const { lang } = this.$route.params
return {
basePath: `/${lang}/components/breadcrumb`,
routes: [{
path: 'index',
breadcrumbName: '首页'
}, {
path: 'first',
breadcrumbName: '一级面包屑'
}, {
path: 'second',
breadcrumbName: '当前页面'
}],
}
},
}
</script>
```

View File

@ -1,33 +1,30 @@
<cn> <cn>
#### 分隔符 #### 分隔符
使用` separator=">" `可以自定义分隔符 使用` separator=">" `可以自定义分隔符或者使用slot="separator"自定义更复杂的分隔符
</cn> </cn>
<us> <us>
#### Configuring the Separator #### Configuring the Separator
The separator can be customized by setting the separator preperty: separator=">" The separator can be customized by setting the separator preperty: separator=">" or use
slot="separator"
</us> </us>
```html ```html
<template> <template>
<Breadcrumb separator=">"> <div>
<BreadcrumbItem>Home</BreadcrumbItem> <a-breadcrumb separator=">">
<BreadcrumbItem href="">Application Center</BreadcrumbItem> <a-breadcrumb-item>Home</a-breadcrumb-item>
<BreadcrumbItem href="">Application List</BreadcrumbItem> <a-breadcrumb-item href="">Application Center</a-breadcrumb-item>
<BreadcrumbItem>An Application</BreadcrumbItem> <a-breadcrumb-item href="">Application List</a-breadcrumb-item>
</Breadcrumb> <a-breadcrumb-item>An Application</a-breadcrumb-item>
</a-breadcrumb>
<a-breadcrumb>
<span slot="separator" style="color: red">></span>
<a-breadcrumb-item>Home</a-breadcrumb-item>
<a-breadcrumb-item href="">Application Center</a-breadcrumb-item>
<a-breadcrumb-item href="">Application List</a-breadcrumb-item>
<a-breadcrumb-item>An Application</a-breadcrumb-item>
</a-breadcrumb>
</div>
</template> </template>
<script>
import '../style'
import { Icon, Breadcrumb } from 'antd/index'
export default {
components: {
Icon,
Breadcrumb,
BreadcrumbItem: Breadcrumb.Item,
},
}
</script>
``` ```

View File

@ -10,30 +10,17 @@
```html ```html
<template> <template>
<Breadcrumb> <a-breadcrumb>
<BreadcrumbItem href=""> <a-breadcrumb-item href="">
<Icon type="home" /> <a-icon type="home" />
</BreadcrumbItem> </a-breadcrumb-item>
<BreadcrumbItem href=""> <a-breadcrumb-item href="">
<Icon type="user" /> <a-icon type="user" />
<span>Application List</span> <span>Application List</span>
</BreadcrumbItem> </a-breadcrumb-item>
<BreadcrumbItem> <a-breadcrumb-item>
Application Application
</BreadcrumbItem> </a-breadcrumb-item>
</Breadcrumb> </a-breadcrumb>
</template> </template>
<script>
import '../style'
import { Icon, Breadcrumb } from 'antd/index'
export default {
components: {
Icon,
Breadcrumb,
BreadcrumbItem: Breadcrumb.Item,
},
}
</script>
``` ```

View File

@ -2,34 +2,44 @@
| Property | Description | Type | Optional | Default | | Property | Description | Type | Optional | Default |
| -------- | ----------- | ---- | -------- | ------- | | -------- | ----------- | ---- | -------- | ------- |
| itemRender | Custom item renderer | (route, params, routes, paths) => ReactNode | | - | | itemRender | Custom item renderer, slot="itemRender" and slot-scope="{route, params, routes, paths}" | ({route, params, routes, paths}) => vNode | | - |
| params | Routing parameters | object | | - | | params | Routing parameters | object | | - |
| routes | The routing stack information of router | object\[] | | - | | routes | The routing stack information of router | object\[] | | - |
| separator | Custom separator | string\|ReactNode | | `/` | | separator | Custom separator | string\|slot | | `/` |
> `linkRender` and `nameRender` were removed after `antd@2.0`, please use `itemRender` instead.
### Use with browserHistory ### Use with browserHistory
The link of Breadcrumb item targets `#` by default, you can use `itemRender` to make a `browserHistory` Link. The link of Breadcrumb item targets `#` by default, you can use `itemRender` to make a `browserHistory` Link.
```vue ````html
import { Link } from 'react-router'; <template>
<a-breadcrumb :routes="routes">
const routes = [{ <template slot="itemRender" slot-scope="{route, params, routes, paths}">
path: 'index', <span v-if="routes.indexOf(route) === routes.length - 1">
 breadcrumbName: 'home' {{route.breadcrumbName}}
}, { </span>
path: 'first', <router-link v-else :to="paths.join('/')">
breadcrumbName: 'first' {{route.breadcrumbName}}
}, { </router-link>
path: 'second', </template>
breadcrumbName: 'second' </a-breadcrumb>
}]; </template>
function itemRender(route, params, routes, paths) { <script>
const last = routes.indexOf(route) === routes.length - 1; export default {
return last ? <span>{route.breadcrumbName}</span> : <Link to={paths.join('/')}>{route.breadcrumbName}</Link>; data(){
} return {
routes: [{
return <Breadcrumb itemRender={itemRender} routes={routes} />; path: 'index',
``` breadcrumbName: '首页'
}, {
path: 'first',
breadcrumbName: '一级面包屑'
}, {
path: 'second',
breadcrumbName: '当前页面'
}],
}
},
}
</script>
````

View File

@ -2,7 +2,7 @@
| 参数 | 说明 | 类型 | 可选值 | 默认值 | | 参数 | 说明 | 类型 | 可选值 | 默认值 |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| itemRender | 自定义链接函数,和 react-router 配置使用 | (route, params, routes, paths) => ReactNode | | - | | itemRender | 自定义链接函数,和 vue-router 配置使用, 也可使用slot="itemRender" 和 slot-scope="props" | ({route, params, routes, paths}) => vNode | | - |
| params | 路由的参数 | object | | - | | params | 路由的参数 | object | | - |
| routes | router 的路由栈信息 | object\[] | | - | | routes | router 的路由栈信息 | object\[] | | - |
| separator | 分隔符自定义 | string\|slot | | '/' | | separator | 分隔符自定义 | string\|slot | | '/' |
@ -12,22 +12,34 @@
和 vue-router 一起使用时,默认生成的 url 路径是带有 `#` 的,如果和 browserHistory 一起使用的话,你可以使用 `itemRender` 属性定义面包屑链接。 和 vue-router 一起使用时,默认生成的 url 路径是带有 `#` 的,如果和 browserHistory 一起使用的话,你可以使用 `itemRender` 属性定义面包屑链接。
````html ````html
import { Link } from 'react-router'; <template>
<a-breadcrumb :routes="routes">
const routes = [{ <template slot="itemRender" slot-scope="{route, params, routes, paths}">
path: 'index', <span v-if="routes.indexOf(route) === routes.length - 1">
breadcrumbName: '首页' {{route.breadcrumbName}}
}, { </span>
path: 'first', <router-link v-else :to="paths.join('/')">
breadcrumbName: '一级面包屑' {{route.breadcrumbName}}
}, { </router-link>
path: 'second', </template>
breadcrumbName: '当前页面' </a-breadcrumb>
}]; </template>
function itemRender(route, params, routes, paths) { <script>
const last = routes.indexOf(route) === routes.length - 1; export default {
return last ? <span>{route.breadcrumbName}</span> : <Link to={paths.join('/')}>{route.breadcrumbName}</Link>; data(){
} return {
routes: [{
return <Breadcrumb itemRender={itemRender} routes={routes}/>; path: 'index',
breadcrumbName: '首页'
}, {
path: 'first',
breadcrumbName: '一级面包屑'
}, {
path: 'second',
breadcrumbName: '当前页面'
}],
}
},
}
</script>
```` ````

View File

@ -5,22 +5,23 @@
.@{breadcrumb-prefix-cls} { .@{breadcrumb-prefix-cls} {
.reset-component; .reset-component;
color: @text-color-secondary; color: @breadcrumb-base-color;
font-size: @breadcrumb-font-size;
.@{iconfont-css-prefix} { .@{iconfont-css-prefix} {
font-size: @font-size-sm; font-size: @breadcrumb-icon-font-size;
} }
a { a {
color: @text-color-secondary; color: @breadcrumb-link-color;
transition: color .3s; transition: color .3s;
&:hover { &:hover {
color: @primary-5; color: @breadcrumb-link-color-hover;
} }
} }
& > span:last-child { & > span:last-child {
color: @text-color; color: @breadcrumb-last-item-color;
} }
& > span:last-child &-separator { & > span:last-child &-separator {
@ -28,8 +29,8 @@
} }
&-separator { &-separator {
margin: 0 @padding-xs; margin: @breadcrumb-separator-margin;
color: @text-color-secondary; color: @breadcrumb-separator-color;
} }
&-link { &-link {

View File

@ -12,10 +12,10 @@ For instance, add an external link after the selected value.
```html ```html
<template> <template>
<a-cascader :options="options" :defaultValue="['zhejiang', 'hangzhou', 'xihu']" style="width: 100%"> <a-cascader :options="options" :defaultValue="['zhejiang', 'hangzhou', 'xihu']" style="width: 100%">
<template slot="displayRender" slot-scope="props"> <template slot="displayRender" slot-scope="{labels, selectedOptions}">
<span v-for="(label, index) in props.labels" :key="props.selectedOptions[index].value"> <span v-for="(label, index) in labels" :key="selectedOptions[index].value">
<span v-if="index === props.labels.length - 1"> <span v-if="index === labels.length - 1">
{{label}} (<a @click="e => handleAreaClick(e, label, props.selectedOptions[index])">{{props.selectedOptions[index].code}}</a>) {{label}} (<a @click="e => handleAreaClick(e, label, selectedOptions[index])">{{selectedOptions[index].code}}</a>)
</span> </span>
<span v-else @click="onChange"> <span v-else @click="onChange">
{{label}} / {{label}} /

View File

@ -12,7 +12,7 @@
| changeOnSelect | change value on each selection if set to true, see above demo for details | boolean | false | | changeOnSelect | change value on each selection if set to true, see above demo for details | boolean | false |
| defaultValue | initial selected value | string\[] | \[] | | defaultValue | initial selected value | string\[] | \[] |
| disabled | whether disabled select | boolean | false | | disabled | whether disabled select | boolean | false |
| displayRender | render function of displaying selected options, you can use slot="displayRender" and slot-scope="props" | `({labels, selectedOptions}) => vNode` | `labels => labels.join(' / ')` | | displayRender | render function of displaying selected options, you can use slot="displayRender" and slot-scope="{labels, selectedOptions}" | `({labels, selectedOptions}) => vNode` | `labels => labels.join(' / ')` |
| expandTrigger | expand current item when click or hover, one of 'click' 'hover' | string | 'click' | | expandTrigger | expand current item when click or hover, one of 'click' 'hover' | string | 'click' |
| getPopupContainer | Parent Node which the selector should be rendered to. Default to `body`. When position issues happen, try to modify it into scrollable content and position it relative. | Function(triggerNode) | () => document.body | | getPopupContainer | Parent Node which the selector should be rendered to. Default to `body`. When position issues happen, try to modify it into scrollable content and position it relative. | Function(triggerNode) | () => document.body |
| loadData | To load option lazily, and it cannot work with `showSearch` | `(selectedOptions) => void` | - | | loadData | To load option lazily, and it cannot work with `showSearch` | `(selectedOptions) => void` | - |
@ -33,7 +33,7 @@ Fields in `showSearch`:
| -------- | ----------- | ---- | ------- | | -------- | ----------- | ---- | ------- |
| filter | The function will receive two arguments, inputValue and option, if the function returns true, the option will be included in the filtered set; Otherwise, it will be excluded. | `function(inputValue, path): boolean` | | | filter | The function will receive two arguments, inputValue and option, if the function returns true, the option will be included in the filtered set; Otherwise, it will be excluded. | `function(inputValue, path): boolean` | |
| matchInputWidth | Whether the width of result list equals to input's | boolean | | | matchInputWidth | Whether the width of result list equals to input's | boolean | |
| render | Used to render filtered options, you can use slot="showSearchRender" and slot-scope="props" | `function({inputValue, path}): vNode` | | | render | Used to render filtered options, you can use slot="showSearchRender" and slot-scope="{inputValue, path}" | `function({inputValue, path}): vNode` | |
| sort | Used to sort filtered options. | `function(a, b, inputValue)` | | | sort | Used to sort filtered options. | `function(a, b, inputValue)` | |
### events ### events

View File

@ -12,7 +12,7 @@
| changeOnSelect | 当此项为 true 时,点选每级菜单选项值都会发生变化,具体见上面的演示 | boolean | false | | changeOnSelect | 当此项为 true 时,点选每级菜单选项值都会发生变化,具体见上面的演示 | boolean | false |
| defaultValue | 默认的选中项 | string\[] | \[] | | defaultValue | 默认的选中项 | string\[] | \[] |
| disabled | 禁用 | boolean | false | | disabled | 禁用 | boolean | false |
| displayRender | 选择后展示的渲染函数,可使用slot="displayRender" 和 slot-scope="props" | `({labels, selectedOptions}) => vNode` | `labels => labels.join(' / ')` | | displayRender | 选择后展示的渲染函数,可使用slot="displayRender" 和 slot-scope="{labels, selectedOptions}" | `({labels, selectedOptions}) => vNode` | `labels => labels.join(' / ')` |
| expandTrigger | 次级菜单的展开方式,可选 'click' 和 'hover' | string | 'click' | | expandTrigger | 次级菜单的展开方式,可选 'click' 和 'hover' | string | 'click' |
| getPopupContainer | 菜单渲染父节点。默认渲染到 body 上,如果你遇到菜单滚动定位问题,试试修改为滚动的区域,并相对其定位。 | Function(triggerNode) | () => document.body | | getPopupContainer | 菜单渲染父节点。默认渲染到 body 上,如果你遇到菜单滚动定位问题,试试修改为滚动的区域,并相对其定位。 | Function(triggerNode) | () => document.body |
| loadData | 用于动态加载选项,无法与 `showSearch` 一起使用 | `(selectedOptions) => void` | - | | loadData | 用于动态加载选项,无法与 `showSearch` 一起使用 | `(selectedOptions) => void` | - |
@ -33,7 +33,7 @@
| --- | --- | --- | --- | | --- | --- | --- | --- |
| filter | 接收 `inputValue` `path` 两个参数,当 `path` 符合筛选条件时,应返回 true反之则返回 false。 | `function(inputValue, path): boolean` | | | filter | 接收 `inputValue` `path` 两个参数,当 `path` 符合筛选条件时,应返回 true反之则返回 false。 | `function(inputValue, path): boolean` | |
| matchInputWidth | 搜索结果列表是否与输入框同宽 | boolean | | | matchInputWidth | 搜索结果列表是否与输入框同宽 | boolean | |
| render | 用于渲染 filter 后的选项,可使用slot="showSearchRender" 和 slot-scope="props" | `function({inputValue, path}): vNode` | | | render | 用于渲染 filter 后的选项,可使用slot="showSearchRender" 和 slot-scope="{inputValue, path}" | `function({inputValue, path}): vNode` | |
| sort | 用于排序 filter 后的选项 | `function(a, b, inputValue)` | | | sort | 用于排序 filter 后的选项 | `function(a, b, inputValue)` | |
### 事件 ### 事件

View File

@ -42,7 +42,9 @@ const Textarea = InputTextArea
export { Input, InputGroup, InputSearch, InputTextArea, Textarea } export { Input, InputGroup, InputSearch, InputTextArea, Textarea }
export { default as Breadcrumb } from './breadcrumb' import Breadcrumb from './breadcrumb'
const BreadcrumbItem = Breadcrumb.Item
export { Breadcrumb, BreadcrumbItem }
export { default as Popover } from './popover' export { default as Popover } from './popover'

View File

@ -30,3 +30,4 @@ import './back-top/style'
import './modal/style' import './modal/style'
import './alert/style' import './alert/style'
import './time-picker/style' import './time-picker/style'
import './breadcrumb/style'

View File

@ -7,7 +7,7 @@ const AsyncComp = () => {
} }
} }
export default [ export default [
{ path: '/:lang?/components/:name/:demo?', component: Demo }, { path: '/:lang?/components/:name/:demo?/:other?', component: Demo },
{ path: '/:lang?/test/:name/:demo?', component: AsyncComp }, { path: '/:lang?/test/:name/:demo?', component: AsyncComp },
{ path: '/*', redirect: '/cn/components/select' }, { path: '/*', redirect: '/cn/components/select' },
] ]