[new feature] Tab: add name prop (#3762)

This commit is contained in:
neverland 2019-07-05 14:14:20 +08:00 committed by GitHub
parent 2389ac06ba
commit b802047e24
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 243 additions and 84 deletions

View File

@ -32,6 +32,26 @@ export default {
} }
``` ```
### Match By Name
```html
<van-tabs v-model="activeName">
<van-tab title="tab 1" name="a">content of tab 1</van-tab>
<van-tab title="tab 2" name="b">content of tab 2</van-tab>
<van-tab title="tab 3" name="c">content of tab 3</van-tab>
</van-tabs>
```
```js
export default {
data() {
return {
activeName: 'a'
};
}
}
```
### Swipe Tabs ### Swipe Tabs
By default more than 4 tabs, you can scroll through the tabs. You can set `swipe-threshold` attribute to customize threshold number. By default more than 4 tabs, you can scroll through the tabs. You can set `swipe-threshold` attribute to customize threshold number.
@ -59,7 +79,7 @@ You can set `disabled` attribute on the corresponding `van-tab`.
```javascript ```javascript
export default { export default {
methods: { methods: {
onClickDisabled(index, title) { onClickDisabled(name, title) {
this.$toast(title + ' is disabled'); this.$toast(title + ' is disabled');
} }
} }
@ -91,7 +111,7 @@ Tabs styled as cards.
```javascript ```javascript
export default { export default {
methods: { methods: {
onClick(index, title) { onClick(name, title) {
this.$toast(title); this.$toast(title);
} }
} }
@ -155,7 +175,7 @@ In swipeable mode, you can switch tabs with swipe gestrue in the content
| Attribute | Description | Type | Default | | Attribute | Description | Type | Default |
|------|------|------|------| |------|------|------|------|
| v-model | Index of active tab | `String` `Number` | `0` | | v-model | Index of active tab | `String | Number` | `0` |
| type | Can be set to `line` `card` | `String` | `line` | | type | Can be set to `line` `card` | `String` | `line` |
| duration | Toggle tab's animation time | `Number` | `0.3` | - | | duration | Toggle tab's animation time | `Number` | `0.3` | - |
| background | Background color | `String` | `white` | | background | Background color | `String` | `white` |
@ -177,6 +197,7 @@ In swipeable mode, you can switch tabs with swipe gestrue in the content
| Attribute | Description | Type | Default | | Attribute | Description | Type | Default |
|------|------|------|------| |------|------|------|------|
| name | Identifier | `String | Number` | Index of tab |
| title | Title | `String` | - | | title | Title | `String` | - |
| disabled | Whether to disable tab | `Boolean` | `false` | | disabled | Whether to disable tab | `Boolean` | `false` |
@ -198,7 +219,7 @@ In swipeable mode, you can switch tabs with swipe gestrue in the content
| Event | Description | Arguments | | Event | Description | Arguments |
|------|------|------| |------|------|------|
| click | Triggered when click tab | indexindex of current tabtitle: tab title | | click | Triggered when click tab | namename of current tabtitle: tab title |
| change | Triggered when active tab changed | indexindex of current tabtitle: tab title | | change | Triggered when active tab changed | namename of current tabtitle: tab title |
| disabled | Triggered when click disabled tab | indexindex of current tab, title: tab title | | disabled | Triggered when click disabled tab | namename of current tab, title: tab title |
| scroll | Triggered when tab scroll in sticky mode | Object: { scrollTop, isFixed } | | scroll | Triggered when tab scroll in sticky mode | Object: { scrollTop, isFixed } |

View File

@ -12,7 +12,7 @@ Vue.use(Tab).use(Tabs);
### 基础用法 ### 基础用法
默认情况下启用第一个标签,可以通过`v-model`绑定当前激活标签索引 通过`v-model`绑定当前激活标签对应的索引值,默认情况下启用第一个标签
```html ```html
<van-tabs v-model="active"> <van-tabs v-model="active">
@ -33,9 +33,31 @@ export default {
} }
``` ```
### 横向滚动 ### 通过名称匹配
多于 4 个标签时Tab 可以横向滚动 在标签指定`name`属性的情况下,`v-model`的值为当前标签的`name`
```html
<van-tabs v-model="activeName">
<van-tab title="标签 1" name="a">内容 1</van-tab>
<van-tab title="标签 2" name="b">内容 2</van-tab>
<van-tab title="标签 3" name="c">内容 3</van-tab>
</van-tabs>
```
```js
export default {
data() {
return {
activeName: 'a'
};
}
}
```
### 标签栏滚动
标签数量超过 4 个时,标签栏可以在水平方向上滚动,切换时会自动将当前标签居中
```html ```html
<van-tabs> <van-tabs>
@ -60,8 +82,8 @@ export default {
```javascript ```javascript
export default { export default {
methods: { methods: {
onClickDisabled(index, title) { onClickDisabled(name, title) {
this.$toast(title + '已被禁用'); this.$toast(name + '已被禁用');
} }
} }
}; };
@ -93,7 +115,7 @@ export default {
```javascript ```javascript
export default { export default {
methods: { methods: {
onClick(index, title) { onClick(name, title) {
this.$toast(title); this.$toast(title);
} }
} }
@ -157,7 +179,7 @@ export default {
| 参数 | 说明 | 类型 | 默认值 | 版本 | | 参数 | 说明 | 类型 | 默认值 | 版本 |
|------|------|------|------|------| |------|------|------|------|------|
| v-model | 当前标签的索引 | `String` `Number` | `0` | - | | v-model | 绑定当前选中标签的标识符 | `String | Number` | `0` | - |
| type | 样式类型,可选值为`card` | `String` | `line` | - | | type | 样式类型,可选值为`card` | `String` | `line` | - |
| duration | 动画时间,单位秒 | `Number` | `0.3` | - | | duration | 动画时间,单位秒 | `Number` | `0.3` | - |
| background | 标签栏背景色 | `String` | `white` | 1.6.5 | | background | 标签栏背景色 | `String` | `white` | 1.6.5 |
@ -179,6 +201,7 @@ export default {
| 参数 | 说明 | 类型 | 默认值 | 版本 | | 参数 | 说明 | 类型 | 默认值 | 版本 |
|------|------|------|------|------| |------|------|------|------|------|
| name | 标签名称,作为匹配的标识符 | `String | Number` | 标签的索引值 | 2.0.6 |
| title | 标题 | `String` | - | - | | title | 标题 | `String` | - | - |
| disabled | 是否禁用标签 | `Boolean` | `false` | - | | disabled | 是否禁用标签 | `Boolean` | `false` | - |
@ -200,7 +223,7 @@ export default {
| 事件名 | 说明 | 回调参数 | | 事件名 | 说明 | 回调参数 |
|------|------|------| |------|------|------|
| click | 点击标签时触发 | index标签索引title标题 | | click | 点击标签时触发 | name标签标识符title标题 |
| change | 当前激活的标签改变时触发 | index标签索引title标题 | | change | 当前激活的标签改变时触发 | name标签标识符title标题 |
| disabled | 点击被禁用的标签时触发 | index标签索引title标题 | | disabled | 点击被禁用的标签时触发 | name标签标识符title标题 |
| scroll | 滚动时触发,仅在 sticky 模式下生效 | { scrollTop: 距离顶部位置, isFixed: 是否吸顶 } | | scroll | 滚动时触发,仅在 sticky 模式下生效 | { scrollTop: 距离顶部位置, isFixed: 是否吸顶 } |

View File

@ -12,8 +12,31 @@
</van-tabs> </van-tabs>
</demo-block> </demo-block>
<demo-block :title="$t('matchByName')">
<van-tabs v-model="activeName">
<van-tab
name="a"
:title="$t('tab') + 1"
>
{{ $t('content') }} 1
</van-tab>
<van-tab
name="b"
:title="$t('tab') + 2"
>
{{ $t('content') }} 2
</van-tab>
<van-tab
name="c"
:title="$t('tab') + 3"
>
{{ $t('content') }} 3
</van-tab>
</van-tabs>
</demo-block>
<demo-block :title="$t('title2')"> <demo-block :title="$t('title2')">
<van-tabs @scroll="onScroll"> <van-tabs>
<van-tab <van-tab
v-for="index in 8" v-for="index in 8"
:title="$t('tab') + index" :title="$t('tab') + index"
@ -124,7 +147,7 @@ export default {
i18n: { i18n: {
'zh-CN': { 'zh-CN': {
tab: '标签 ', tab: '标签 ',
title2: '横向滚动', title2: '标签栏滚动',
title3: '禁用标签', title3: '禁用标签',
title4: '样式风格', title4: '样式风格',
title5: '点击事件', title5: '点击事件',
@ -132,7 +155,8 @@ export default {
title7: '自定义标签', title7: '自定义标签',
title8: '切换动画', title8: '切换动画',
title9: '滑动切换', title9: '滑动切换',
disabled: ' 已被禁用' disabled: ' 已被禁用',
matchByName: '通过名称匹配'
}, },
'en-US': { 'en-US': {
tab: 'Tab ', tab: 'Tab ',
@ -145,13 +169,15 @@ export default {
title7: 'Custom Tab', title7: 'Custom Tab',
title8: 'Switch Animation', title8: 'Switch Animation',
title9: 'Swipeable', title9: 'Swipeable',
disabled: ' is disabled' disabled: ' is disabled',
matchByName: 'Match By Name'
} }
}, },
data() { data() {
return { return {
active: 2, active: 2,
activeName: 'b',
tabs: [1, 2, 3, 4] tabs: [1, 2, 3, 4]
}; };
}, },
@ -163,10 +189,6 @@ export default {
onClick(index, title) { onClick(index, title) {
this.$toast(title); this.$toast(title);
},
onScroll(e) {
console.log(e);
} }
} }
}; };

View File

@ -8,6 +8,7 @@ export default createComponent({
mixins: [ChildrenMixin('vanTabs')], mixins: [ChildrenMixin('vanTabs')],
props: { props: {
name: [String, Number],
title: String, title: String,
disabled: Boolean disabled: Boolean
}, },
@ -19,14 +20,18 @@ export default createComponent({
}, },
computed: { computed: {
selected() { computedName() {
return this.index === this.parent.curActive; return this.name || this.index;
},
isActive() {
return this.computedName === this.parent.currentName;
} }
}, },
watch: { watch: {
'parent.curActive'() { 'parent.currentIndex'() {
this.inited = this.inited || this.selected; this.inited = this.inited || this.isActive;
}, },
title() { title() {
@ -41,7 +46,7 @@ export default createComponent({
}, },
render(h) { render(h) {
const { slots, selected } = this; const { slots, isActive } = this;
const shouldRender = this.inited || !this.parent.lazyRender; const shouldRender = this.inited || !this.parent.lazyRender;
const Content = [shouldRender ? slots() : h()]; const Content = [shouldRender ? slots() : h()];
@ -53,8 +58,8 @@ export default createComponent({
return ( return (
<div <div
role="tabpanel" role="tabpanel"
aria-hidden={!selected} aria-hidden={!isActive}
class={bem('pane-wrapper', { inactive: !selected })} class={bem('pane-wrapper', { inactive: !isActive })}
> >
<div class={bem('pane')}>{Content}</div> <div class={bem('pane')}>{Content}</div>
</div> </div>
@ -62,7 +67,7 @@ export default createComponent({
} }
return ( return (
<div vShow={selected} role="tabpanel" class={bem('pane')}> <div vShow={isActive} role="tabpanel" class={bem('pane')}>
{Content} {Content}
</div> </div>
); );

View File

@ -29,6 +29,29 @@ exports[`renders demo correctly 1`] = `
</div> </div>
</div> </div>
</div> </div>
<div>
<div class="van-tabs van-tabs--line">
<div class="van-tabs__wrap van-hairline--top-bottom">
<div role="tablist" class="van-tabs__nav van-tabs__nav--line">
<div role="tab" class="van-tab"><span class="van-ellipsis">标签 1</span></div>
<div role="tab" aria-selected="true" class="van-tab van-tab--active"><span class="van-ellipsis">标签 2</span></div>
<div role="tab" class="van-tab"><span class="van-ellipsis">标签 3</span></div>
<div class="van-tabs__line" style="width: 0px; transform: translateX(0px) translateX(-50%);"></div>
</div>
</div>
<div class="van-tabs__content">
<div role="tabpanel" class="van-tab__pane" style="display: none;">
<!---->
</div>
<div role="tabpanel" class="van-tab__pane" style="">
内容 2
</div>
<div role="tabpanel" class="van-tab__pane" style="display: none;">
<!---->
</div>
</div>
</div>
</div>
<div> <div>
<div class="van-tabs van-tabs--line"> <div class="van-tabs van-tabs--line">
<div class="van-tabs__wrap van-tabs__wrap--scrollable van-hairline--top-bottom"> <div class="van-tabs__wrap van-tabs__wrap--scrollable van-hairline--top-bottom">

View File

@ -136,6 +136,28 @@ exports[`lazy render 2`] = `
</div> </div>
`; `;
exports[`name prop 1`] = `
<div class="van-tabs van-tabs--line">
<div class="van-tabs__wrap van-hairline--top-bottom">
<div role="tablist" class="van-tabs__nav van-tabs__nav--line">
<div role="tab" aria-selected="true" class="van-tab van-tab--active"><span class="van-ellipsis">title1</span></div>
<div role="tab" class="van-tab"><span class="van-ellipsis">title2</span></div>
<div role="tab" class="van-tab van-tab--disabled"><span class="van-ellipsis">title3</span></div>
<div class="van-tabs__line"></div>
</div>
</div>
<div class="van-tabs__content">
<div role="tabpanel" class="van-tab__pane">Text</div>
<div role="tabpanel" class="van-tab__pane" style="display: none;">
<!---->
</div>
<div role="tabpanel" class="van-tab__pane" style="display: none;">
<!---->
</div>
</div>
</div>
`;
exports[`render nav-left & nav-right slot 1`] = ` exports[`render nav-left & nav-right slot 1`] = `
<div class="van-tabs van-tabs--line"> <div class="van-tabs van-tabs--line">
<div class="van-tabs__wrap van-hairline--top-bottom"> <div class="van-tabs__wrap van-hairline--top-bottom">

View File

@ -145,3 +145,38 @@ test('border props', async () => {
expect(wrapper).toMatchSnapshot(); expect(wrapper).toMatchSnapshot();
}); });
test('name prop', async () => {
const onClick = jest.fn();
const onChange = jest.fn();
const onDisabled = jest.fn();
const wrapper = mount({
template: `
<van-tabs @click="onClick" @disabled="onDisabled" @change="onChange">
<van-tab title="title1" name="a">Text</van-tab>
<van-tab title="title2" name="b">Text</van-tab>
<van-tab title="title3" name="c" disabled>Text</van-tab>
</van-tabs>
`,
methods: {
onClick,
onChange,
onDisabled
}
});
await later();
expect(wrapper).toMatchSnapshot();
const tabs = wrapper.findAll('.van-tab');
tabs.at(1).trigger('click');
expect(onClick).toHaveBeenCalledWith('b', 'title2');
expect(onChange).toHaveBeenCalledWith('b', 'title2');
expect(onChange).toHaveBeenCalledTimes(1);
tabs.at(2).trigger('click');
expect(onDisabled).toHaveBeenCalledWith('c', 'title3');
expect(onChange).toHaveBeenCalledTimes(1);
});

View File

@ -31,7 +31,7 @@ export default {
} }
``` ```
### Item Name ### Match by name
```html ```html
<van-tabbar v-model="active"> <van-tabbar v-model="active">

View File

@ -9,7 +9,7 @@
</van-tabbar> </van-tabbar>
</demo-block> </demo-block>
<demo-block :title="$t('itemName')"> <demo-block :title="$t('matchByName')">
<van-tabbar v-model="activeName"> <van-tabbar v-model="activeName">
<van-tabbar-item <van-tabbar-item
name="home" name="home"
@ -97,13 +97,13 @@ export default {
badge: '显示徽标', badge: '显示徽标',
customIcon: '自定义图标', customIcon: '自定义图标',
customColor: '自定义颜色', customColor: '自定义颜色',
itemName: '通过名称匹配' matchByName: '通过名称匹配'
}, },
'en-US': { 'en-US': {
badge: 'Show Badge', badge: 'Show Badge',
customIcon: 'Custom Icon', customIcon: 'Custom Icon',
customColor: 'Custom Color', customColor: 'Custom Color',
itemName: 'Item Name' matchByName: 'Match by name'
} }
}, },

View File

@ -9,17 +9,17 @@ export default createComponent({
props: { props: {
count: Number, count: Number,
active: Number,
duration: Number, duration: Number,
animated: Boolean, animated: Boolean,
swipeable: Boolean swipeable: Boolean,
currentIndex: Number
}, },
computed: { computed: {
style() { style() {
if (this.animated) { if (this.animated) {
return { return {
transform: `translate3d(${-1 * this.active * 100}%, 0, 0)`, transform: `translate3d(${-1 * this.currentIndex * 100}%, 0, 0)`,
transitionDuration: `${this.duration}s` transitionDuration: `${this.duration}s`
}; };
} }
@ -40,15 +40,15 @@ export default createComponent({
methods: { methods: {
// watch swipe touch end // watch swipe touch end
onTouchEnd() { onTouchEnd() {
const { direction, deltaX, active } = this; const { direction, deltaX, currentIndex } = this;
/* istanbul ignore else */ /* istanbul ignore else */
if (direction === 'horizontal' && this.offsetX >= MIN_SWIPE_DISTANCE) { if (direction === 'horizontal' && this.offsetX >= MIN_SWIPE_DISTANCE) {
/* istanbul ignore else */ /* istanbul ignore else */
if (deltaX > 0 && active !== 0) { if (deltaX > 0 && currentIndex !== 0) {
this.$emit('change', active - 1); this.$emit('change', currentIndex - 1);
} else if (deltaX < 0 && active !== this.count - 1) { } else if (deltaX < 0 && currentIndex !== this.count - 1) {
this.$emit('change', active + 1); this.$emit('change', currentIndex + 1);
} }
} }
}, },

View File

@ -7,7 +7,7 @@ export default {
type: String, type: String,
color: String, color: String,
title: String, title: String,
active: Boolean, isActive: Boolean,
ellipsis: Boolean, ellipsis: Boolean,
disabled: Boolean, disabled: Boolean,
scrollable: Boolean, scrollable: Boolean,
@ -19,7 +19,7 @@ export default {
computed: { computed: {
style() { style() {
const style = {}; const style = {};
const { color, active } = this; const { color, isActive } = this;
const isCard = this.type === 'card'; const isCard = this.type === 'card';
// card theme color // card theme color
@ -27,7 +27,7 @@ export default {
style.borderColor = color; style.borderColor = color;
if (!this.disabled) { if (!this.disabled) {
if (active) { if (isActive) {
style.backgroundColor = color; style.backgroundColor = color;
} else { } else {
style.color = color; style.color = color;
@ -35,7 +35,7 @@ export default {
} }
} }
const titleColor = active ? this.activeColor : this.inactiveColor; const titleColor = isActive ? this.activeColor : this.inactiveColor;
if (titleColor) { if (titleColor) {
style.color = titleColor; style.color = titleColor;
} }
@ -64,9 +64,9 @@ export default {
return ( return (
<div <div
role="tab" role="tab"
aria-selected={this.active} aria-selected={this.isActive}
class={bem({ class={bem({
active: this.active, active: this.isActive,
disabled: this.disabled, disabled: this.disabled,
complete: !this.ellipsis complete: !this.ellipsis
})} })}

View File

@ -73,7 +73,7 @@ export default createComponent({
return { return {
position: '', position: '',
curActive: null, currentIndex: null,
lineStyle: { lineStyle: {
backgroundColor: this.color backgroundColor: this.color
} }
@ -108,13 +108,21 @@ export default createComponent({
borderColor: this.color, borderColor: this.color,
background: this.background background: this.background
}; };
},
currentName() {
const activeTab = this.children[this.currentIndex];
if (activeTab) {
return activeTab.computedName;
}
} }
}, },
watch: { watch: {
active(val) { active(name) {
if (val !== this.curActive) { if (name !== this.currentName) {
this.correctActive(val); this.setCurrentIndexByName(name);
} }
}, },
@ -123,12 +131,12 @@ export default createComponent({
}, },
children() { children() {
this.correctActive(this.curActive || this.active); this.setCurrentIndexByName(this.currentName || this.active);
this.scrollIntoView(); this.scrollIntoView();
this.setLine(); this.setLine();
}, },
curActive() { currentIndex() {
this.scrollIntoView(); this.scrollIntoView();
this.setLine(); this.setLine();
@ -201,11 +209,11 @@ export default createComponent({
this.$nextTick(() => { this.$nextTick(() => {
const { titles } = this.$refs; const { titles } = this.$refs;
if (!titles || !titles[this.curActive] || this.type !== 'line') { if (!titles || !titles[this.currentIndex] || this.type !== 'line') {
return; return;
} }
const title = titles[this.curActive].$el; const title = titles[this.currentIndex].$el;
const { lineWidth, lineHeight } = this; const { lineWidth, lineHeight } = this;
const width = isDef(lineWidth) ? lineWidth : title.offsetWidth / 2; const width = isDef(lineWidth) ? lineWidth : title.offsetWidth / 2;
const left = title.offsetLeft + title.offsetWidth / 2; const left = title.offsetLeft + title.offsetWidth / 2;
@ -230,47 +238,47 @@ export default createComponent({
}); });
}, },
// correct the value of active // correct the index of active tab
correctActive(active) { setCurrentIndexByName(name) {
active = +active; const matched = this.children.filter(tab => tab.computedName === name);
const exist = this.children.some(tab => tab.index === active); const defaultIndex = (this.children[0] || {}).index || 0;
const defaultActive = (this.children[0] || {}).index || 0; this.setCurrentIndex(matched.length ? matched[0].index : defaultIndex);
this.setCurActive(exist ? active : defaultActive);
}, },
setCurActive(active) { setCurrentIndex(currentIndex) {
active = this.findAvailableTab(active, active < this.curActive); currentIndex = this.findAvailableTab(currentIndex);
if (isDef(active) && active !== this.curActive) {
this.$emit('input', active);
if (this.curActive !== null) { if (isDef(currentIndex) && currentIndex !== this.currentIndex) {
this.$emit('change', active, this.children[active].title); const shouldEmitChange = this.currentIndex !== null;
this.currentIndex = currentIndex;
this.$emit('input', this.currentName);
if (shouldEmitChange) {
this.$emit('change', this.currentName, this.children[currentIndex].title);
} }
this.curActive = active;
} }
}, },
findAvailableTab(active, reverse) { findAvailableTab(index) {
const diff = reverse ? -1 : 1; const diff = index < this.currentIndex ? -1 : 1;
let index = active;
while (index >= 0 && index < this.children.length) { while (index >= 0 && index < this.children.length) {
if (!this.children[index].disabled) { if (!this.children[index].disabled) {
return index; return index;
} }
index += diff; index += diff;
} }
}, },
// emit event when clicked // emit event when clicked
onClick(index) { onClick(index) {
const { title, disabled } = this.children[index]; const { title, disabled, name } = this.children[index];
if (disabled) { if (disabled) {
this.$emit('disabled', index, title); this.$emit('disabled', name, title);
} else { } else {
this.setCurActive(index); this.setCurrentIndex(index);
this.$emit('click', index, title); this.$emit('click', name, title);
} }
}, },
@ -278,12 +286,12 @@ export default createComponent({
scrollIntoView(immediate) { scrollIntoView(immediate) {
const { titles } = this.$refs; const { titles } = this.$refs;
if (!this.scrollable || !titles || !titles[this.curActive]) { if (!this.scrollable || !titles || !titles[this.currentIndex]) {
return; return;
} }
const { nav } = this.$refs; const { nav } = this.$refs;
const title = titles[this.curActive].$el; const title = titles[this.currentIndex].$el;
const to = title.offsetLeft - (nav.offsetWidth - title.offsetWidth) / 2; const to = title.offsetLeft - (nav.offsetWidth - title.offsetWidth) / 2;
scrollLeftTo(nav, to, immediate ? 0 : this.duration); scrollLeftTo(nav, to, immediate ? 0 : this.duration);
@ -307,7 +315,7 @@ export default createComponent({
type={type} type={type}
title={item.title} title={item.title}
color={this.color} color={this.color}
active={index === this.curActive} isActive={index === this.currentIndex}
ellipsis={ellipsis} ellipsis={ellipsis}
disabled={item.disabled} disabled={item.disabled}
scrollable={scrollable} scrollable={scrollable}
@ -339,11 +347,11 @@ export default createComponent({
</div> </div>
<Content <Content
count={this.children.length} count={this.children.length}
active={this.curActive}
animated={animated} animated={animated}
duration={this.duration} duration={this.duration}
swipeable={this.swipeable} swipeable={this.swipeable}
onChange={this.setCurActive} currentIndex={this.currentIndex}
onChange={this.setCurrentIndex}
> >
{this.slots()} {this.slots()}
</Content> </Content>