[new feature] add IndexBar component

This commit is contained in:
陈嘉涵
2019-05-08 15:39:27 +08:00
parent e9854daf29
commit c92791e8da
19 changed files with 515 additions and 3 deletions

View File

@@ -0,0 +1,64 @@
<template>
<demo-section>
<van-tabs v-model="activeTab">
<van-tab :title="$t('basicUsage')">
<van-index-bar>
<div
v-for="index in indexList"
:key="index"
>
<van-index-anchor :index="index" />
<van-cell :title="$t('text')" />
<van-cell :title="$t('text')" />
<van-cell :title="$t('text')" />
</div>
</van-index-bar>
</van-tab>
<van-tab :title="$t('customIndexList')">
<van-index-bar :index-list="customIndexList">
<div
v-for="index in customIndexList"
:key="index"
>
<van-index-anchor :index="index">
{{ $t('title') + index }}
</van-index-anchor>
<van-cell :title="$t('text')" />
<van-cell :title="$t('text')" />
<van-cell :title="$t('text')" />
</div>
</van-index-bar>
</van-tab>
</van-tabs>
</demo-section>
</template>
<script>
export default {
i18n: {
'zh-CN': {
text: '文本',
customIndexList: '自定义索引列表'
},
'en-US': {
text: 'Text',
customIndexList: 'Custom Index List'
}
},
data() {
const indexList = [];
const charCodeOfA = 'A'.charCodeAt(0);
for (let i = 0; i < 26; i++) {
indexList.push(String.fromCharCode(charCodeOfA + i));
}
return {
activeTab: 0,
indexList,
customIndexList: [1, 2, 3, 4, 5, 6, 8, 9, 10]
};
}
};
</script>

View File

@@ -0,0 +1,75 @@
## IndexBar
### Install
``` javascript
import { IndexBar } from 'vant';
Vue.use(IndexBar);
```
### Usage
#### Basic Usage
```html
<van-index-bar>
<van-index-anchor index="A" />
<van-cell title="Text" />
<van-cell title="Text" />
<van-cell title="Text" />
<van-index-anchor index="B" />
<van-cell title="Text" />
<van-cell title="Text" />
<van-cell title="Text" />
...
</van-index-bar>
```
#### Custom Index List
```html
<van-index-bar :index-list="indexList">
<van-index-anchor index="1">Title 1</van-index-anchor>
<van-cell title="Text" />
<van-cell title="Text" />
<van-cell title="Text" />
<van-index-anchor index="2">Title 2</van-index-anchor>
<van-cell title="Text" />
<van-cell title="Text" />
<van-cell title="Text" />
...
</van-index-bar>
```
```js
export default {
data() {
return {
indexList: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
}
}
}
```
### IndexBar Props
| Attribute | Description | Type | Default |
|------|------|------|------|
| index-list | Index List | `Array` | `A-Z` |
### IndexAnchor Props
| Attribute | Description | Type | Default |
|------|------|------|------|
| index | Index | `String | Number` | - |
### IndexAnchor Slots
| Name | Description |
|------|------|
| default | Anchor content, show index by default |

View File

@@ -0,0 +1,92 @@
import { use } from '../utils';
import { TouchMixin } from '../mixins/touch';
import { ParentMixin } from '../mixins/relation';
const [sfc, bem] = use('index-bar');
export default sfc({
mixins: [TouchMixin, ParentMixin('vanIndexBar')],
props: {
indexList: {
type: Array,
default() {
const indexList = [];
const charCodeOfA = 'A'.charCodeAt(0);
for (let i = 0; i < 26; i++) {
indexList.push(String.fromCharCode(charCodeOfA + i));
}
return indexList;
}
}
},
methods: {
onClick(event) {
this.scrollToElement(event.target);
},
onTouchStart(event) {
this.touchStart(event);
},
onTouchMove(event) {
this.touchMove(event);
if (this.direction === 'vertical') {
/* istanbul ignore else */
if (event.cancelable) {
event.preventDefault();
}
const { clientX, clientY } = event.touches[0];
const target = document.elementFromPoint(clientX, clientY);
this.scrollToElement(target);
}
},
scrollToElement(element, setActive) {
if (!element) {
return;
}
const { index } = element.dataset;
if (!index) {
return;
}
const match = this.children.filter(item => String(item.index) === index);
if (match[0]) {
match[0].scrollIntoView();
}
},
onTouchEnd() {
this.active = null;
}
},
render(h) {
return (
<div class={bem()}>
<div
class={bem('sidebar')}
onClick={this.onClick}
onTouchstart={this.onTouchStart}
onTouchmove={this.onTouchMove}
onTouchend={this.onTouchEnd}
onTouchcancel={this.onTouchEnd}
>
{this.indexList.map(index => (
<span class={bem('index')} data-index={index}>
{index}
</span>
))}
</div>
{this.slots('default')}
</div>
);
}
});

View File

@@ -0,0 +1,22 @@
@import '../style/var';
.van-index-bar {
&__sidebar {
position: fixed;
display: flex;
top: 50%;
right: 0;
z-index: 1;
user-select: none;
text-align: center;
flex-direction: column;
transform: translateY(-50%);
}
&__index {
font-size: 10px;
font-weight: 500;
line-height: 14px;
padding: 0 3px 0 15px;
}
}

View File

@@ -0,0 +1,21 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders demo correctly 1`] = `
<div>
<div class="van-tabs van-tabs--line">
<div class="van-tabs__wrap van-hairline--top-bottom">
<div class="van-tabs__nav van-tabs__nav--line">
<div class="van-tabs__line"></div>
</div>
</div>
<div class="van-tabs__content">
<div class="van-tab__pane" style="display:none;">
<!---->
</div>
<div class="van-tab__pane" style="display:none;">
<!---->
</div>
</div>
</div>
</div>
`;

View File

@@ -0,0 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`custom anchor text 1`] = `
<div class="van-index-bar">
<div class="van-index-bar__sidebar"><span data-index="A" class="van-index-bar__index">A</span><span data-index="B" class="van-index-bar__index">B</span><span data-index="C" class="van-index-bar__index">C</span><span data-index="D" class="van-index-bar__index">D</span><span data-index="E" class="van-index-bar__index">E</span><span data-index="F" class="van-index-bar__index">F</span><span data-index="G" class="van-index-bar__index">G</span><span data-index="H" class="van-index-bar__index">H</span><span data-index="I" class="van-index-bar__index">I</span><span data-index="J" class="van-index-bar__index">J</span><span data-index="K" class="van-index-bar__index">K</span><span data-index="L" class="van-index-bar__index">L</span><span data-index="M" class="van-index-bar__index">M</span><span data-index="N" class="van-index-bar__index">N</span><span data-index="O" class="van-index-bar__index">O</span><span data-index="P" class="van-index-bar__index">P</span><span data-index="Q" class="van-index-bar__index">Q</span><span data-index="R" class="van-index-bar__index">R</span><span data-index="S" class="van-index-bar__index">S</span><span data-index="T" class="van-index-bar__index">T</span><span data-index="U" class="van-index-bar__index">U</span><span data-index="V" class="van-index-bar__index">V</span><span data-index="W" class="van-index-bar__index">W</span><span data-index="X" class="van-index-bar__index">X</span><span data-index="Y" class="van-index-bar__index">Y</span><span data-index="Z" class="van-index-bar__index">Z</span></div>
<div class="van-index-anchor">Title A</div>
<div class="van-index-anchor">Title B</div>
</div>
`;

View File

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

View File

@@ -0,0 +1,85 @@
import { mount, trigger, triggerDrag } from '../../../test/utils';
import Vue from 'vue';
import IndexBar from '..';
import IndexAnchor from '../../index-anchor';
Vue.use(IndexBar);
Vue.use(IndexAnchor);
function mockScrollIntoView() {
const fn = jest.fn();
Element.prototype.scrollIntoView = fn;
return fn;
}
test('custom anchor text', () => {
const wrapper = mount({
template: `
<van-index-bar>
<van-index-anchor index="A">Title A</van-index-anchor>
<van-index-anchor index="B">Title B</van-index-anchor>
</van-index-bar>
`
});
expect(wrapper).toMatchSnapshot();
});
test('click and scroll to anchor', () => {
const wrapper = mount({
template: `
<van-index-bar>
<van-index-anchor index="A" />
<van-index-anchor index="B" />
</van-index-bar>
`
});
const fn = mockScrollIntoView();
const indexes = wrapper.findAll('.van-index-bar__index');
indexes.at(0).trigger('click');
expect(fn).toHaveBeenCalledTimes(1);
});
test('touch and scroll to anchor', () => {
const wrapper = mount({
template: `
<van-index-bar>
<van-index-anchor index="A" />
<van-index-anchor index="B" />
<van-index-anchor index="XXX" />
</van-index-bar>
`
});
const fn = mockScrollIntoView();
const sidebar = wrapper.find('.van-index-bar__sidebar');
const indexes = wrapper.findAll('.van-index-bar__index');
document.elementFromPoint = function (x, y) {
const index = y / 100;
if (index === 1 || index === 2) {
return indexes.at(index).element;
}
if (index === 3) {
return {
dataset: {}
};
}
};
// horizontal drag
triggerDrag(sidebar, 100, 0);
expect(fn).toHaveBeenCalledTimes(0);
// vertiacl drag
trigger(sidebar, 'touchstart', 0, 0);
trigger(sidebar, 'touchmove', 0, 100);
trigger(sidebar, 'touchmove', 0, 200);
trigger(sidebar, 'touchmove', 0, 300);
trigger(sidebar, 'touchmove', 0, 400);
trigger(sidebar, 'touchend', 0, 400);
expect(fn).toHaveBeenCalledTimes(1);
});

View File

@@ -0,0 +1,79 @@
## IndexBar 索引栏
### 使用指南
``` javascript
import { IndexBar, IndexAnchor } from 'vant';
Vue.use(IndexBar).use(IndexAnchor);
```
### 代码演示
#### 基础用法
点击索引栏时,会自动跳转到对应的`IndexAnchor`锚点位置
```html
<van-index-bar>
<van-index-anchor index="A" />
<van-cell title="文本" />
<van-cell title="文本" />
<van-cell title="文本" />
<van-index-anchor index="B" />
<van-cell title="文本" />
<van-cell title="文本" />
<van-cell title="文本" />
...
</van-index-bar>
```
#### 自定义索引列表
可以通过`index-list`属性自定义展示的索引字符列表,
```html
<van-index-bar :index-list="indexList">
<van-index-anchor index="1">标题1</van-index-anchor>
<van-cell title="文本" />
<van-cell title="文本" />
<van-cell title="文本" />
<van-index-anchor index="2">标题2</van-index-anchor>
<van-cell title="文本" />
<van-cell title="文本" />
<van-cell title="文本" />
...
</van-index-bar>
```
```js
export default {
data() {
return {
indexList: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
}
}
}
```
### IndexBar Props
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|------|------|------|------|------|
| index-list | 索引字符列表 | `Array` | `A-Z` | - |
### IndexAnchor Props
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|------|------|------|------|------|
| index | 索引字符 | `String | Number` | - | - |
### IndexAnchor Slots
| 名称 | 说明 |
|------|------|
| default | 锚点位置显示内容,默认为索引字符 |