diff --git a/breaking-changes.md b/breaking-changes.md index 2f1be1acb..daf6cfc31 100644 --- a/breaking-changes.md +++ b/breaking-changes.md @@ -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` ## 废弃个别组件 diff --git a/src-next/sidebar-item/index.js b/src-next/sidebar-item/index.js new file mode 100644 index 000000000..f2645b08c --- /dev/null +++ b/src-next/sidebar-item/index.js @@ -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 ( + +
+ {this.title} + +
+
+ ); + }, +}); diff --git a/src-next/sidebar-item/index.less b/src-next/sidebar-item/index.less new file mode 100644 index 000000000..49b6cd2d9 --- /dev/null +++ b/src-next/sidebar-item/index.less @@ -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; + } + } +} diff --git a/src-next/sidebar/README.md b/src-next/sidebar/README.md new file mode 100644 index 000000000..296140dc7 --- /dev/null +++ b/src-next/sidebar/README.md @@ -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 + + + + + +``` + +```js +export default { + data() { + return { + activeKey: 0, + }; + }, +}; +``` + +### Show Badge + +```html + + + + + +``` + +### Disabled + +```html + + + + + +``` + +### Change Event + +```html + + + + + +``` + +```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 | diff --git a/src-next/sidebar/README.zh-CN.md b/src-next/sidebar/README.zh-CN.md new file mode 100644 index 000000000..32a58d61d --- /dev/null +++ b/src-next/sidebar/README.zh-CN.md @@ -0,0 +1,121 @@ +# Sidebar 侧边导航 + +### 引入 + +```js +import Vue from 'vue'; +import { Sidebar, SidebarItem } from 'vant'; + +Vue.use(Sidebar); +Vue.use(SidebarItem); +``` + +## 代码演示 + +### 基础用法 + +通过`v-model`绑定当前选中项的索引 + +```html + + + + + +``` + +```js +export default { + data() { + return { + activeKey: 0, + }; + }, +}; +``` + +### 徽标提示 + +设置`dot`属性后,会在右上角展示一个小红点。设置`badge`属性后,会在右上角展示相应的徽标 + +```html + + + + + +``` + +### 禁用选项 + +通过`disabled`属性禁用选项 + +```html + + + + + +``` + +### 监听切换事件 + +设置`change`方法来监听切换导航项时的事件 + +```html + + + + + +``` + +```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: 当前导航项的索引 | diff --git a/src-next/sidebar/demo/index.vue b/src-next/sidebar/demo/index.vue new file mode 100644 index 000000000..5ab0d55f8 --- /dev/null +++ b/src-next/sidebar/demo/index.vue @@ -0,0 +1,98 @@ + + + + + diff --git a/src-next/sidebar/index.js b/src-next/sidebar/index.js new file mode 100644 index 000000000..6db07f361 --- /dev/null +++ b/src-next/sidebar/index.js @@ -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
{this.$slots.default?.()}
; + }, +}); diff --git a/src-next/sidebar/index.less b/src-next/sidebar/index.less new file mode 100644 index 000000000..ff09323c9 --- /dev/null +++ b/src-next/sidebar/index.less @@ -0,0 +1,7 @@ +@import '../style/var'; + +.van-sidebar { + width: @sidebar-width; + overflow-y: auto; + -webkit-overflow-scrolling: touch; +} diff --git a/src-next/sidebar/test/__snapshots__/demo.spec.js.snap b/src-next/sidebar/test/__snapshots__/demo.spec.js.snap new file mode 100644 index 000000000..1a7caf7b3 --- /dev/null +++ b/src-next/sidebar/test/__snapshots__/demo.spec.js.snap @@ -0,0 +1,77 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders demo correctly 1`] = ` +
+
+ + + + +
+
+`; diff --git a/src-next/sidebar/test/demo.spec.js b/src-next/sidebar/test/demo.spec.js new file mode 100644 index 000000000..5c70922b5 --- /dev/null +++ b/src-next/sidebar/test/demo.spec.js @@ -0,0 +1,4 @@ +import Demo from '../demo'; +import { snapshotDemo } from '../../../test/demo'; + +snapshotDemo(Demo); diff --git a/src-next/sidebar/test/index.spec.js b/src-next/sidebar/test/index.spec.js new file mode 100644 index 000000000..a3e533410 --- /dev/null +++ b/src-next/sidebar/test/index.spec.js @@ -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: ` + + Text + Text + + `, + 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: ` + + Text + Text + + `, + 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: ` + + Text + Text + + `, + 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(); + } +});