feat: migrate Sidebar component

This commit is contained in:
chenjiahan 2020-07-12 15:24:05 +08:00
parent 75d76d2cec
commit 08e03e988f
11 changed files with 649 additions and 1 deletions

View File

@ -18,7 +18,8 @@
- Circle: `v-model` 调整为 `v-model:currentRate`
- Popup: `v-model` 调整为 `v-model:show`
- Switch: v-model 对应的属性名和事件名由 `value/input` 调整为 `modelValue/update:modelValue`
- Switch: v-model 对应的属性 `value` 调整为 `modelValue`,事件由 `input` 调整为 `update:modelValue`
- Sidebar: v-model 对应的属性 `activeKey` 调整为 `modelValue`,事件由 `input` 调整为 `update:modelValue`
## 废弃个别组件

View File

@ -0,0 +1,51 @@
import { createNamespace } from '../utils';
import { ChildrenMixin } from '../mixins/relation';
import { route, routeProps } from '../utils/router';
import Info from '../info';
const [createComponent, bem] = createNamespace('sidebar-item');
export default createComponent({
mixins: [ChildrenMixin('vanSidebar')],
props: {
...routeProps,
dot: Boolean,
badge: [Number, String],
title: String,
disabled: Boolean,
},
computed: {
select() {
return this.index === +this.parent.modelValue;
},
},
methods: {
onClick() {
if (this.disabled) {
return;
}
this.$emit('click', this.index);
this.parent.$emit('update:modelValue', this.index);
this.parent.setIndex(this.index);
route(this.$router, this);
},
},
render() {
return (
<a
class={bem({ select: this.select, disabled: this.disabled })}
onClick={this.onClick}
>
<div class={bem('text')}>
{this.title}
<Info dot={this.dot} info={this.badge} class={bem('info')} />
</div>
</a>
);
},
});

View File

@ -0,0 +1,59 @@
@import '../style/var';
.van-sidebar-item {
position: relative;
display: block;
box-sizing: border-box;
padding: @sidebar-padding;
overflow: hidden;
color: @sidebar-text-color;
font-size: @sidebar-font-size;
line-height: @sidebar-line-height;
word-wrap: break-word;
background-color: @sidebar-background-color;
cursor: pointer;
user-select: none;
&:active {
background-color: @sidebar-active-color;
}
&__text {
position: relative;
display: inline-block;
}
&:not(:last-child)::after {
border-bottom-width: 1px;
}
&--select {
color: @sidebar-selected-text-color;
font-weight: @sidebar-selected-font-weight;
&,
&:active {
background-color: @sidebar-selected-background-color;
}
&::before {
position: absolute;
top: 50%;
left: 0;
width: @sidebar-selected-border-width;
height: @sidebar-selected-border-height;
background-color: @sidebar-selected-border-color;
transform: translateY(-50%);
content: '';
}
}
&--disabled {
color: @sidebar-disabled-text-color;
cursor: not-allowed;
&:active {
background-color: @sidebar-background-color;
}
}
}

112
src-next/sidebar/README.md Normal file
View File

@ -0,0 +1,112 @@
# Sidebar
### Install
```js
import Vue from 'vue';
import { Sidebar, SidebarItem } from 'vant';
Vue.use(Sidebar);
Vue.use(SidebarItem);
```
## Usage
### Basic Usage
```html
<van-sidebar v-model="activeKey">
<van-sidebar-item title="Title" />
<van-sidebar-item title="Title" />
<van-sidebar-item title="Title" />
</van-sidebar>
```
```js
export default {
data() {
return {
activeKey: 0,
};
},
};
```
### Show Badge
```html
<van-sidebar v-model="activeKey">
<van-sidebar-item title="Title" dot />
<van-sidebar-item title="Title" badge="5" />
<van-sidebar-item title="Title" badge="99+" />
</van-sidebar>
```
### Disabled
```html
<van-sidebar v-model="activeKey">
<van-sidebar-item title="Title" />
<van-sidebar-item title="Title" disabled />
<van-sidebar-item title="Title" />
</van-sidebar>
```
### Change Event
```html
<van-sidebar v-model="activeKey" @change="onChange">
<van-sidebar-item title="Title1" />
<van-sidebar-item title="Title2" />
<van-sidebar-item title="Title3" />
</van-sidebar>
```
```js
import { Notify } from 'vant';
export default {
data() {
return {
activeKey: 0,
};
},
methods: {
onChange(index) {
Notify({ type: 'primary', message: index });
},
},
};
```
## API
### Sidebar Props
| Attribute | Description | Type | Default |
| --------- | -------------------- | ------------------ | ------- |
| v-model | Index of chosen item | _number \| string_ | `0` |
### Sidebar Events
| Event | Description | Arguments |
| ------ | --------------------------- | ---------------------------- |
| change | Triggered when item changed | index: index of current item |
### SidebarItem Props
| Attribute | Description | Type | Default |
| --- | --- | --- | --- |
| title | Content | _string_ | `''` |
| dot `v2.2.1` | Whether to show red dot | _boolean_ | `false` |
| badge `v2.5.6` | Content of the badge | _number \| string_ | `''` |
| disabled `v2.2.0` | Whether to be disabled | _boolean_ | `false` |
| url | Link | _string_ | - |
| to `v2.0.4` | Target route of the link, same as to of vue-router | _string \| object_ | - |
| replace `v2.0.4` | If true, the navigation will not leave a history record | _boolean_ | `false` |
### SidebarItem Events
| Event | Description | Arguments |
| ----- | ------------------------- | ---------------------------- |
| click | Triggered when click item | index: index of current item |

View File

@ -0,0 +1,121 @@
# Sidebar 侧边导航
### 引入
```js
import Vue from 'vue';
import { Sidebar, SidebarItem } from 'vant';
Vue.use(Sidebar);
Vue.use(SidebarItem);
```
## 代码演示
### 基础用法
通过`v-model`绑定当前选中项的索引
```html
<van-sidebar v-model="activeKey">
<van-sidebar-item title="标签名称" />
<van-sidebar-item title="标签名称" />
<van-sidebar-item title="标签名称" />
</van-sidebar>
```
```js
export default {
data() {
return {
activeKey: 0,
};
},
};
```
### 徽标提示
设置`dot`属性后,会在右上角展示一个小红点。设置`badge`属性后,会在右上角展示相应的徽标
```html
<van-sidebar v-model="activeKey">
<van-sidebar-item title="标签名称" dot />
<van-sidebar-item title="标签名称" badge="5" />
<van-sidebar-item title="标签名称" badge="99+" />
</van-sidebar>
```
### 禁用选项
通过`disabled`属性禁用选项
```html
<van-sidebar v-model="activeKey">
<van-sidebar-item title="标签名称" />
<van-sidebar-item title="标签名称" disabled />
<van-sidebar-item title="标签名称" />
</van-sidebar>
```
### 监听切换事件
设置`change`方法来监听切换导航项时的事件
```html
<van-sidebar v-model="activeKey" @change="onChange">
<van-sidebar-item title="标签名1" />
<van-sidebar-item title="标签名2" />
<van-sidebar-item title="标签名3" />
</van-sidebar>
```
```js
import { Notify } from 'vant';
export default {
data() {
return {
activeKey: 0,
};
},
methods: {
onChange(index) {
Notify({ type: 'primary', message: index });
},
},
};
```
## API
### Sidebar Props
| 参数 | 说明 | 类型 | 默认值 |
| ---------------- | ---------------- | ------------------ | ------ |
| v-model `v2.0.4` | 当前导航项的索引 | _number \| string_ | `0` |
### Sidebar Events
| 事件名 | 说明 | 回调参数 |
| ------ | ---------------- | ----------------------- |
| change | 切换导航项时触发 | index: 当前导航项的索引 |
### SidebarItem Props
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| title | 内容 | _string_ | `''` |
| dot `v2.2.1` | 是否显示右上角小红点 | _boolean_ | `false` |
| badge `v2.5.6` | 图标右上角徽标的内容 | _number \| string_ | - |
| info | 图标右上角徽标的内容(已废弃,请使用 badge 属性) | _number \| string_ | - |
| disabled `v2.2.0` | 是否禁用该项 | _boolean_ | `false` |
| url | 点击后跳转的链接地址 | _string_ | - |
| to `v2.0.4` | 点击后跳转的目标路由对象,同 vue-router 的 [to 属性](https://router.vuejs.org/zh/api/#to) | _string \| object_ | - |
| replace `v2.0.4` | 是否在跳转时替换当前页面历史 | _boolean_ | `false` |
### SidebarItem Events
| 事件名 | 说明 | 回调参数 |
| ------ | ---------- | ----------------------- |
| click | 点击时触发 | index: 当前导航项的索引 |

View File

@ -0,0 +1,98 @@
<template>
<demo-section>
<van-grid :column-num="2" :border="false">
<van-grid-item>
<h3 class="demo-sidebar-title">{{ t('basicUsage') }}</h3>
<van-sidebar v-model="activeKey1">
<van-sidebar-item :title="t('title')" />
<van-sidebar-item :title="t('title')" />
<van-sidebar-item :title="t('title')" />
</van-sidebar>
</van-grid-item>
<van-grid-item>
<h3 class="demo-sidebar-title">{{ t('showBadge') }}</h3>
<van-sidebar v-model="activeKey2">
<van-sidebar-item :title="t('title')" dot />
<van-sidebar-item :title="t('title')" badge="5" />
<van-sidebar-item :title="t('title')" badge="99+" />
</van-sidebar>
</van-grid-item>
<van-grid-item>
<h3 class="demo-sidebar-title">{{ t('disabled') }}</h3>
<van-sidebar v-model="activeKey3">
<van-sidebar-item :title="t('title')" />
<van-sidebar-item :title="t('title')" disabled />
<van-sidebar-item :title="t('title')" />
</van-sidebar>
</van-grid-item>
<van-grid-item>
<h3 class="demo-sidebar-title">{{ t('changeEvent') }}</h3>
<van-sidebar v-model="activeKey4" @change="onChange">
<van-sidebar-item :title="t('title') + 1" />
<van-sidebar-item :title="t('title') + 2" />
<van-sidebar-item :title="t('title') + 3" />
</van-sidebar>
</van-grid-item>
</van-grid>
</demo-section>
</template>
<script>
export default {
i18n: {
'zh-CN': {
title: '标签名',
showBadge: '徽标提示',
disabled: '禁用选项',
changeEvent: '监听切换事件',
selectTip: '你切换到了',
},
'en-US': {
showBadge: 'Show Badge',
disabled: 'Disabled',
changeEvent: 'Change Event',
selectTip: 'You select ',
},
},
data() {
return {
activeKey1: 0,
activeKey2: 0,
activeKey3: 0,
activeKey4: 0,
};
},
methods: {
onChange(index) {
this.$notify({
type: 'primary',
message: `${this.t('selectTip')} ${this.t('title')}${index + 1}`,
});
},
},
};
</script>
<style lang="less">
@import '../../style/var';
.demo-sidebar {
background-color: @white;
.van-sidebar {
margin-left: @padding-md;
}
&-title {
margin-bottom: 16px;
color: @gray-6;
font-weight: normal;
font-size: 14px;
}
}
</style>

40
src-next/sidebar/index.js Normal file
View File

@ -0,0 +1,40 @@
import { createNamespace } from '../utils';
import { ParentMixin } from '../mixins/relation';
const [createComponent, bem] = createNamespace('sidebar');
export default createComponent({
mixins: [ParentMixin('vanSidebar')],
props: {
modelValue: {
type: [Number, String],
default: 0,
},
},
data() {
return {
index: +this.modelValue,
};
},
watch: {
modelValue() {
this.setIndex(+this.modelValue);
},
},
methods: {
setIndex(index) {
if (index !== this.index) {
this.index = index;
this.$emit('change', index);
}
},
},
render() {
return <div class={bem()}>{this.$slots.default?.()}</div>;
},
});

View File

@ -0,0 +1,7 @@
@import '../style/var';
.van-sidebar {
width: @sidebar-width;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}

View File

@ -0,0 +1,77 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders demo correctly 1`] = `
<div>
<div class="van-grid">
<div class="van-grid-item" style="flex-basis: 50%;">
<div class="van-grid-item__content van-grid-item__content--center">
<h3 class="demo-sidebar-title">基础用法</h3>
<div class="van-sidebar"><a class="van-sidebar-item van-sidebar-item--select">
<div class="van-sidebar-item__text">标签名
<!---->
</div>
</a> <a class="van-sidebar-item">
<div class="van-sidebar-item__text">标签名
<!---->
</div>
</a> <a class="van-sidebar-item">
<div class="van-sidebar-item__text">标签名
<!---->
</div>
</a></div>
</div>
</div>
<div class="van-grid-item" style="flex-basis: 50%;">
<div class="van-grid-item__content van-grid-item__content--center">
<h3 class="demo-sidebar-title">徽标提示</h3>
<div class="van-sidebar"><a class="van-sidebar-item van-sidebar-item--select">
<div class="van-sidebar-item__text">标签名<div class="van-info van-info--dot van-sidebar-item__info"></div>
</div>
</a> <a class="van-sidebar-item">
<div class="van-sidebar-item__text">标签名<div class="van-info van-sidebar-item__info">5</div>
</div>
</a> <a class="van-sidebar-item">
<div class="van-sidebar-item__text">标签名<div class="van-info van-sidebar-item__info">99+</div>
</div>
</a></div>
</div>
</div>
<div class="van-grid-item" style="flex-basis: 50%;">
<div class="van-grid-item__content van-grid-item__content--center">
<h3 class="demo-sidebar-title">禁用选项</h3>
<div class="van-sidebar"><a class="van-sidebar-item van-sidebar-item--select">
<div class="van-sidebar-item__text">标签名
<!---->
</div>
</a> <a class="van-sidebar-item van-sidebar-item--disabled">
<div class="van-sidebar-item__text">标签名
<!---->
</div>
</a> <a class="van-sidebar-item">
<div class="van-sidebar-item__text">标签名
<!---->
</div>
</a></div>
</div>
</div>
<div class="van-grid-item" style="flex-basis: 50%;">
<div class="van-grid-item__content van-grid-item__content--center">
<h3 class="demo-sidebar-title">监听切换事件</h3>
<div class="van-sidebar"><a class="van-sidebar-item van-sidebar-item--select">
<div class="van-sidebar-item__text">标签名1
<!---->
</div>
</a> <a class="van-sidebar-item">
<div class="van-sidebar-item__text">标签名2
<!---->
</div>
</a> <a class="van-sidebar-item">
<div class="van-sidebar-item__text">标签名3
<!---->
</div>
</a></div>
</div>
</div>
</div>
</div>
`;

View File

@ -0,0 +1,4 @@
import Demo from '../demo';
import { snapshotDemo } from '../../../test/demo';
snapshotDemo(Demo);

View File

@ -0,0 +1,78 @@
import { mount } from '../../../test';
import Sidebar from '..';
test('click event & change event', () => {
const onClick = jest.fn();
const onChange = jest.fn();
const wrapper = mount({
template: `
<van-sidebar @change="onChange">
<van-sidebar-item>Text</van-sidebar-item>
<van-sidebar-item @click="onClick">Text</van-sidebar-item>
</van-sidebar>
`,
methods: {
onClick,
onChange,
},
});
wrapper.findAll('.van-sidebar-item').at(1).trigger('click');
expect(onClick).toHaveBeenCalledWith(1);
expect(onChange).toHaveBeenCalledWith(1);
wrapper.vm.$destroy();
});
test('v-model', () => {
const onChange = jest.fn();
const wrapper = mount({
template: `
<van-sidebar v-model="active" @change="onChange">
<van-sidebar-item>Text</van-sidebar-item>
<van-sidebar-item>Text</van-sidebar-item>
</van-sidebar>
`,
data() {
return {
active: 0,
};
},
methods: {
onChange,
},
});
wrapper.findAll('.van-sidebar-item').at(1).trigger('click');
expect(wrapper.vm.active).toEqual(1);
expect(onChange).toHaveBeenCalledWith(1);
});
test('disabled prop', () => {
const wrapper = mount({
template: `
<van-sidebar v-model="active">
<van-sidebar-item>Text</van-sidebar-item>
<van-sidebar-item disabled>Text</van-sidebar-item>
</van-sidebar>
`,
data() {
return {
active: 0,
};
},
});
wrapper.findAll('.van-sidebar-item').at(1).trigger('click');
expect(wrapper.vm.active).toEqual(0);
});
test('without parent', () => {
const consoleError = console.error;
try {
console.error = jest.fn();
mount(Sidebar);
} catch (err) {
console.error = consoleError;
expect(err).toBeTruthy();
}
});