feat: migrate Tab component

This commit is contained in:
chenjiahan 2020-07-26 15:29:52 +08:00
parent 4ad0c396ef
commit d2291f3710
10 changed files with 97 additions and 88 deletions

View File

@ -4,8 +4,9 @@
以下改动是为了适配 Vue 3 的 v-model API 用法变更:
- Circle: `v-model` 重命名为 `v-model:currentRate`
- Tabs: `v-model` 重命名为 `v-model:active`
- Popup: `v-model` 重命名为 `v-model:show`
- Circle: `v-model` 重命名为 `v-model:currentRate`
- ShareSheet: `v-model` 重命名为 `v-model:show`
- ActionSheet: `v-model` 重命名为 `v-model:show`
- List: `v-model` 重命名为 `v-model:loading``error.sync` 重命名为 `v-model:error`

View File

@ -40,4 +40,7 @@ module.exports = [
'tabbar',
'tabbar-item',
'list',
'tab',
'tabs',
'sticky',
];

View File

@ -14,10 +14,10 @@ Vue.use(Tabs);
### Basic Usage
The first tab is actived by default, you can set `v-model` to active specified tab.
The first tab is actived by default, you can set `v-model:active` to active specified tab.
```html
<van-tabs v-model="active">
<van-tabs v-model:active="active">
<van-tab v-for="index in 4" :title="'tab' + index">
content of tab {{ index }}
</van-tab>
@ -37,7 +37,7 @@ export default {
### Match By Name
```html
<van-tabs v-model="activeName">
<van-tabs v-model:active="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>
@ -129,7 +129,7 @@ export default {
In sticky mode, the tab will be fixed to top when scroll to top.
```html
<van-tabs v-model="active" sticky>
<van-tabs v-model:active="active" sticky>
<van-tab v-for="index in 4" :title="'tab ' + index">
content {{ index }}
</van-tab>
@ -141,7 +141,7 @@ In sticky mode, the tab will be fixed to top when scroll to top.
Use title slot to custom tab title.
```html
<van-tabs v-model="active">
<van-tabs v-model:active="active">
<van-tab v-for="index in 2" :key="index">
<template #title> <van-icon name="more-o" />tab </template>
content {{ index }}
@ -154,7 +154,7 @@ Use title slot to custom tab title.
Use `animated` props to change tabs with animation.
```html
<van-tabs v-model="active" animated>
<van-tabs v-model:active="active" animated>
<van-tab v-for="index in 4" :title="'tab ' + index">
content {{ index }}
</van-tab>
@ -166,7 +166,7 @@ Use `animated` props to change tabs with animation.
In swipeable mode, you can switch tabs with swipe gestrue in the content.
```html
<van-tabs v-model="active" swipeable>
<van-tabs v-model:active="active" swipeable>
<van-tab v-for="index in 4" :title="'tab ' + index">
content {{ index }}
</van-tab>
@ -178,7 +178,7 @@ In swipeable mode, you can switch tabs with swipe gestrue in the content.
In scrollspy mode, the list of content will be tiled.
```html
<van-tabs v-model="active" scrollspy sticky>
<van-tabs v-model:active="active" scrollspy sticky>
<van-tab v-for="index in 8" :title="'tab ' + index">
content {{ index }}
</van-tab>
@ -219,7 +219,7 @@ export default {
| Attribute | Description | Type | Default |
| --- | --- | --- | --- |
| v-model | Index of active tab | _number \| string_ | `0` |
| v-model:active | Index of active tab | _number \| string_ | `0` |
| type | Can be set to `line` `card` | _string_ | `line` |
| color | Tab color | _string_ | `#ee0a24` |
| background | Background color | _string_ | `white` |

View File

@ -14,10 +14,10 @@ Vue.use(Tabs);
### 基础用法
通过 `v-model` 绑定当前激活标签对应的索引值,默认情况下启用第一个标签。
通过 `v-model:active` 绑定当前激活标签对应的索引值,默认情况下启用第一个标签。
```html
<van-tabs v-model="active">
<van-tabs v-model:active="active">
<van-tab title="标签 1">内容 1</van-tab>
<van-tab title="标签 2">内容 2</van-tab>
<van-tab title="标签 3">内容 3</van-tab>
@ -37,10 +37,10 @@ export default {
### 通过名称匹配
在标签指定 `name` 属性的情况下,`v-model` 的值为当前标签的 `name`(此时无法通过索引值来匹配标签)。
在标签指定 `name` 属性的情况下,`v-model:active` 的值为当前标签的 `name`(此时无法通过索引值来匹配标签)。
```html
<van-tabs v-model="activeName">
<van-tabs v-model:active="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>
@ -133,7 +133,7 @@ export default {
通过 `sticky` 属性可以开启粘性布局,粘性布局下,标签页滚动到顶部时会自动吸顶。
```html
<van-tabs v-model="active" sticky>
<van-tabs v-model:active="active" sticky>
<van-tab v-for="index in 4" :title="'选项 ' + index">
内容 {{ index }}
</van-tab>
@ -145,7 +145,7 @@ export default {
通过 `title` 插槽可以自定义标签内容。
```html
<van-tabs v-model="active">
<van-tabs v-model:active="active">
<van-tab v-for="index in 2" :key="index">
<template #title> <van-icon name="more-o" />选项 </template>
内容 {{ index }}
@ -158,7 +158,7 @@ export default {
通过 `animated` 属性可以开启切换标签内容时的转场动画。
```html
<van-tabs v-model="active" animated>
<van-tabs v-model:active="active" animated>
<van-tab v-for="index in 4" :title="'选项 ' + index">
内容 {{ index }}
</van-tab>
@ -170,7 +170,7 @@ export default {
通过 `swipeable` 属性可以开启滑动切换标签页。
```html
<van-tabs v-model="active" swipeable>
<van-tabs v-model:active="active" swipeable>
<van-tab v-for="index in 4" :title="'选项 ' + index">
内容 {{ index }}
</van-tab>
@ -182,7 +182,7 @@ export default {
通过 `scrollspy` 属性可以开启滚动导航模式,该模式下,内容将会平铺展示。
```html
<van-tabs v-model="active" scrollspy sticky>
<van-tabs v-model:active="active" scrollspy sticky>
<van-tab v-for="index in 8" :title="'选项 ' + index">
内容 {{ index }}
</van-tab>
@ -226,7 +226,7 @@ export default {
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| v-model | 绑定当前选中标签的标识符 | _number \| string_ | `0` |
| v-model:active | 绑定当前选中标签的标识符 | _number \| string_ | `0` |
| type | 样式风格类型,可选值为`card` | _string_ | `line` |
| color | 标签主题色 | _string_ | `#ee0a24` |
| background | 标签栏背景色 | _string_ | `white` |

View File

@ -1,7 +1,7 @@
<template>
<demo-section>
<demo-block :title="t('basicUsage')">
<van-tabs v-model="active">
<van-tabs v-model:active="active">
<van-tab :title="t('tab') + index" v-for="index in tabs" :key="index">
{{ t('content') }} {{ index }}
</van-tab>
@ -9,7 +9,7 @@
</demo-block>
<demo-block :title="t('matchByName')">
<van-tabs v-model="activeName">
<van-tabs v-model:active="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>

View File

@ -53,11 +53,11 @@ export default createComponent({
},
},
render(h) {
const { slots, parent, isActive } = this;
render() {
const { parent, isActive } = this;
const shouldRender = this.inited || parent.scrollspy || !parent.lazyRender;
const show = parent.scrollspy || isActive;
const Content = shouldRender ? slots() : h();
const Content = shouldRender ? this.$slots.default?.() : null;
if (parent.animated) {
return (

View File

@ -15,6 +15,8 @@ export default createComponent({
currentIndex: Number,
},
emits: ['change'],
computed: {
style() {
if (this.animated) {
@ -28,10 +30,10 @@ export default createComponent({
listeners() {
if (this.swipeable) {
return {
touchstart: this.touchStart,
touchmove: this.touchMove,
touchend: this.onTouchEnd,
touchcancel: this.onTouchEnd,
onTouchstart: this.touchStart,
onTouchmove: this.touchMove,
onTouchend: this.onTouchEnd,
onTouchcancel: this.onTouchEnd,
};
}
},
@ -54,15 +56,16 @@ export default createComponent({
},
genChildren() {
const Content = this.$slots.default?.();
if (this.animated) {
return (
<div class={bem('track')} style={this.style}>
{this.slots()}
{Content}
</div>
);
}
return this.slots();
return Content;
},
},
@ -70,7 +73,7 @@ export default createComponent({
return (
<div
class={bem('content', { animated: this.animated })}
{...{ on: this.listeners }}
{...this.listeners}
>
{this.genChildren()}
</div>

View File

@ -15,6 +15,7 @@ export default createComponent({
disabled: Boolean,
scrollable: Boolean,
activeColor: String,
renderTitle: Function,
inactiveColor: String,
swipeThreshold: [Number, String],
},
@ -59,7 +60,7 @@ export default createComponent({
genText() {
const Text = (
<span class={bem('text', { ellipsis: this.ellipsis })}>
{this.slots() || this.title}
{this.renderTitle ? this.renderTitle() : this.title}
</span>
);

View File

@ -40,10 +40,6 @@ export default createComponent({
}),
],
model: {
prop: 'active',
},
props: {
color: String,
sticky: Boolean,
@ -90,7 +86,11 @@ export default createComponent({
},
},
emits: ['rendered', 'input', 'change', 'disabled', 'click', 'scroll'],
data() {
this.titleRefs = [];
return {
position: '',
currentIndex: null,
@ -194,18 +194,18 @@ export default createComponent({
const shouldAnimate = this.inited;
this.$nextTick(() => {
const { titles } = this.$refs;
const { titleRefs } = this;
if (
!titles ||
!titles[this.currentIndex] ||
!titleRefs ||
!titleRefs[this.currentIndex] ||
this.type !== 'line' ||
isHidden(this.$el)
) {
return;
}
const title = titles[this.currentIndex].$el;
const title = titleRefs[this.currentIndex].$el;
const { lineWidth, lineHeight } = this;
const width = isDef(lineWidth) ? lineWidth : title.offsetWidth / 2;
const left = title.offsetLeft + title.offsetWidth / 2;
@ -302,14 +302,14 @@ export default createComponent({
// scroll active tab into view
scrollIntoView(immediate) {
const { titles } = this.$refs;
const { titleRefs } = this;
if (!this.scrollable || !titles || !titles[this.currentIndex]) {
if (!this.scrollable || !titleRefs || !titleRefs[this.currentIndex]) {
return;
}
const { nav } = this.$refs;
const title = titles[this.currentIndex].$el;
const title = titleRefs[this.currentIndex].$el;
const to = title.offsetLeft - (nav.offsetWidth - title.offsetWidth) / 2;
scrollLeftTo(nav, to, immediate ? 0 : +this.duration);
@ -369,31 +369,32 @@ export default createComponent({
render() {
const { type, ellipsis, animated, scrollable } = this;
const Nav = this.children.map((item, index) => (
<Title
ref="titles"
refInFor
type={type}
dot={item.dot}
info={isDef(item.badge) ? item.badge : item.info}
title={item.title}
color={this.color}
style={item.titleStyle}
isActive={index === this.currentIndex}
ellipsis={ellipsis}
disabled={item.disabled}
scrollable={scrollable}
activeColor={this.titleActiveColor}
inactiveColor={this.titleInactiveColor}
swipeThreshold={this.swipeThreshold}
scopedSlots={{
default: () => item.slots('title'),
}}
onClick={() => {
this.onClick(item, index);
}}
/>
));
const Nav = this.children.map((item, index) => {
return (
<Title
ref={(val) => {
this.titleRefs[index] = val;
}}
dot={item.dot}
type={type}
info={isDef(item.badge) ? item.badge : item.info}
title={item.title}
color={this.color}
style={item.titleStyle}
isActive={index === this.currentIndex}
ellipsis={ellipsis}
disabled={item.disabled}
scrollable={scrollable}
renderTitle={item.$slots.title}
activeColor={this.titleActiveColor}
inactiveColor={this.titleInactiveColor}
swipeThreshold={this.swipeThreshold}
onClick={() => {
this.onClick(item, index);
}}
/>
);
});
const Wrap = (
<div
@ -409,12 +410,12 @@ export default createComponent({
class={bem('nav', [type])}
style={this.navStyle}
>
{this.slots('nav-left')}
{this.$slots['nav-left']?.()}
{Nav}
{type === 'line' && (
<div class={bem('line')} style={this.lineStyle} />
)}
{this.slots('nav-right')}
{this.$slots['nav-right']?.()}
</div>
</div>
);
@ -440,7 +441,7 @@ export default createComponent({
currentIndex={this.currentIndex}
onChange={this.setCurrentIndex}
>
{this.slots()}
{this.$slots.default?.()}
</Content>
</div>
);

View File

@ -269,10 +269,10 @@ module.exports = {
path: 'steps',
title: 'Steps 步骤条',
},
// {
// path: 'sticky',
// title: 'Sticky 粘性布局',
// },
{
path: 'sticky',
title: 'Sticky 粘性布局',
},
// {
// path: 'swipe',
// title: 'Swipe 轮播',
@ -306,10 +306,10 @@ module.exports = {
path: 'sidebar',
title: 'Sidebar 侧边导航',
},
// {
// path: 'tab',
// title: 'Tab 标签页',
// },
{
path: 'tab',
title: 'Tab 标签页',
},
{
path: 'tabbar',
title: 'Tabbar 标签栏',
@ -603,10 +603,10 @@ module.exports = {
path: 'steps',
title: 'Steps',
},
// {
// path: 'sticky',
// title: 'Sticky',
// },
{
path: 'sticky',
title: 'Sticky',
},
// {
// path: 'swipe',
// title: 'Swipe',
@ -640,10 +640,10 @@ module.exports = {
path: 'sidebar',
title: 'Sidebar',
},
// {
// path: 'tab',
// title: 'Tab',
// },
{
path: 'tab',
title: 'Tab',
},
{
path: 'tabbar',
title: 'Tabbar',