diff --git a/breaking-changes.md b/breaking-changes.md index c08ffd5cf..50ca14445 100644 --- a/breaking-changes.md +++ b/breaking-changes.md @@ -2,4 +2,8 @@ ## Popup -- v-model 调整为 v-model:show +- `v-model` 调整为 `v-model:show` + +## Switch + +- v-model 对应的属性名和事件名由 `value/input` 调整为 `modelValue/update:modelValue` diff --git a/src-next/switch/README.md b/src-next/switch/README.md new file mode 100644 index 000000000..8b75c4411 --- /dev/null +++ b/src-next/switch/README.md @@ -0,0 +1,110 @@ +# Switch + +### Install + +```js +import Vue from 'vue'; +import { Switch } from 'vant'; + +Vue.use(Switch); +``` + +## Usage + +### Basic Usage + +```html + +``` + +```js +export default { + data() { + return { + checked: true, + }; + }, +}; +``` + +### Disabled + +```html + +``` + +### Loading + +```html + +``` + +### Custom Size + +```html + +``` + +### Custom Color + +```html + +``` + +### Async Control + +```html + +``` + +```js +export default { + data() { + return { + checked: true, + }; + }, + methods: { + onUpdateValue(checked) { + Dialog.confirm({ + title: 'Confirm', + message: 'Are you sure to toggle switch?', + }).then(() => { + this.checked = checked; + }); + }, + }, +}; +``` + +### Inside a Cell + +```html + + + +``` + +## API + +### Props + +| Attribute | Description | Type | Default | +| --- | --- | --- | --- | +| v-model | Check status of Switch | _ActiveValue \| InactiveValue_ | `false` | +| loading | Whether to show loading icon | _boolean_ | `false` | +| disabled | Whether to disable switch | _boolean_ | `false` | +| size `v2.2.11` | Size of switch | _number \| string_ | `30px` | +| active-color | Background color when active | _string_ | `#1989fa` | +| inactive-color | Background color when inactive | _string_ | `white` | +| active-value | Value when active | _any_ | `true` | +| inactive-value | Value when inactive | _any_ | `false` | + +### Events + +| Event | Description | Parameters | +| --------------- | ----------------------------------- | -------------- | +| change | Triggered when check status changed | _value: any_ | +| click `v2.2.11` | Triggered when clicked | _event: Event_ | diff --git a/src-next/switch/README.zh-CN.md b/src-next/switch/README.zh-CN.md new file mode 100644 index 000000000..134658066 --- /dev/null +++ b/src-next/switch/README.zh-CN.md @@ -0,0 +1,122 @@ +# Switch 开关 + +### 引入 + +```js +import Vue from 'vue'; +import { Switch } from 'vant'; + +Vue.use(Switch); +``` + +## 代码演示 + +### 基础用法 + +通过`v-model`绑定开关的选中状态,`true`表示开,`false`表示关 + +```html + +``` + +```js +export default { + data() { + return { + checked: true, + }; + }, +}; +``` + +### 禁用状态 + +通过`disabled`属性来禁用开关,禁用状态下开关不可点击 + +```html + +``` + +### 加载状态 + +通过`loading`属性设置开关为加载状态,加载状态下开关不可点击 + +```html + +``` + +### 自定义大小 + +通过`size`属性自定义开关的大小 + +```html + +``` + +### 自定义颜色 + +`active-color`属性表示打开时的背景色,`inactive-color`表示关闭时的背景色 + +```html + +``` + +### 异步控制 + +需要异步控制开关时,可以使用 `modelValue` 属性和 `update:model-value` 事件代替 `v-model`,并在事件回调函数中手动处理开关状态。 + +```html + +``` + +```js +export default { + data() { + return { + checked: true, + }; + }, + methods: { + onUpdateValue(checked) { + Dialog.confirm({ + title: '提醒', + message: '是否切换开关?', + }).then(() => { + this.checked = checked; + }); + }, + }, +}; +``` + +### 搭配单元格使用 + +```html + + + +``` + +## API + +### Props + +| 参数 | 说明 | 类型 | 默认值 | +| -------------- | ------------------------ | ------------------ | --------- | +| v-model | 开关选中状态 | _any_ | `false` | +| loading | 是否为加载状态 | _boolean_ | `false` | +| disabled | 是否为禁用状态 | _boolean_ | `false` | +| size `v2.2.11` | 开关尺寸,默认单位为`px` | _number \| string_ | `30px` | +| active-color | 打开时的背景色 | _string_ | `#1989fa` | +| inactive-color | 关闭时的背景色 | _string_ | `white` | +| active-value | 打开时对应的值 | _any_ | `true` | +| inactive-value | 关闭时对应的值 | _any_ | `false` | + +### Events + +| 事件名 | 说明 | 回调参数 | +| --------------- | ------------------ | -------------- | +| change | 开关状态切换时触发 | _value: any_ | +| click `v2.2.11` | 点击时触发 | _event: Event_ | diff --git a/src-next/switch/demo/index.vue b/src-next/switch/demo/index.vue new file mode 100644 index 000000000..51d4ab39f --- /dev/null +++ b/src-next/switch/demo/index.vue @@ -0,0 +1,98 @@ + + + + + diff --git a/src-next/switch/index.js b/src-next/switch/index.js new file mode 100644 index 000000000..334e42fe7 --- /dev/null +++ b/src-next/switch/index.js @@ -0,0 +1,71 @@ +// Utils +import { createNamespace, addUnit } from '../utils'; +import { switchProps } from './shared'; + +// Mixins +import { FieldMixin } from '../mixins/field'; + +// Components +import Loading from '../loading'; + +const [createComponent, bem] = createNamespace('switch'); + +export default createComponent({ + mixins: [FieldMixin], + + props: switchProps, + + emits: ['click', 'change', 'update:modelValue'], + + computed: { + checked() { + return this.modelValue === this.activeValue; + }, + + style() { + return { + fontSize: addUnit(this.size), + backgroundColor: this.checked ? this.activeColor : this.inactiveColor, + }; + }, + }, + + methods: { + onClick(event) { + this.$emit('click', event); + + if (!this.disabled && !this.loading) { + const newValue = this.checked ? this.inactiveValue : this.activeValue; + this.$emit('update:modelValue', newValue); + this.$emit('change', newValue); + } + }, + + genLoading() { + if (this.loading) { + const color = this.checked ? this.activeColor : this.inactiveColor; + return ; + } + }, + }, + + render() { + const { checked, loading, disabled } = this; + + return ( +
+
{this.genLoading()}
+
+ ); + }, +}); diff --git a/src-next/switch/index.less b/src-next/switch/index.less new file mode 100644 index 000000000..8baa7a43a --- /dev/null +++ b/src-next/switch/index.less @@ -0,0 +1,58 @@ +@import '../style/var'; + +.van-switch { + position: relative; + display: inline-block; + box-sizing: content-box; + width: @switch-width; + height: @switch-height; + font-size: @switch-size; + background-color: @switch-background-color; + border: @switch-border; + border-radius: @switch-node-size; + cursor: pointer; + transition: background-color @switch-transition-duration; + + &__node { + position: absolute; + top: 0; + left: 0; + z-index: @switch-node-z-index; + width: @switch-node-size; + height: @switch-node-size; + background-color: @switch-node-background-color; + border-radius: 100%; + box-shadow: @switch-node-box-shadow; + transition: transform @switch-transition-duration + cubic-bezier(0.3, 1.05, 0.4, 1.05); + } + + &__loading { + top: 25%; + left: 25%; + width: 50%; + height: 50%; + line-height: 1; + } + + &--on { + background-color: @switch-on-background-color; + + .van-switch__node { + transform: translateX(@switch-width - @switch-node-size); + } + + .van-switch__loading { + color: @switch-on-background-color; + } + } + + &--disabled { + cursor: not-allowed; + opacity: @switch-disabled-opacity; + } + + &--loading { + cursor: default; + } +} diff --git a/src-next/switch/shared.ts b/src-next/switch/shared.ts new file mode 100644 index 000000000..fd77d663b --- /dev/null +++ b/src-next/switch/shared.ts @@ -0,0 +1,31 @@ +/** + * Common Switch Props + */ + +export type SharedSwitchProps = { + size?: string | number; + loading?: boolean; + disabled?: boolean; + modelValue?: any; + activeValue: any; + inactiveValue: any; + activeColor?: string; + inactiveColor?: string; +}; + +export const switchProps = { + size: [Number, String], + loading: Boolean, + disabled: Boolean, + modelValue: null as any, + activeColor: String, + inactiveColor: String, + activeValue: { + type: null as any, + default: true, + }, + inactiveValue: { + type: null as any, + default: false, + }, +}; diff --git a/src-next/switch/test/__snapshots__/demo.spec.js.snap b/src-next/switch/test/__snapshots__/demo.spec.js.snap new file mode 100644 index 000000000..c05490ce5 --- /dev/null +++ b/src-next/switch/test/__snapshots__/demo.spec.js.snap @@ -0,0 +1,46 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders demo correctly 1`] = ` +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
标题
+
+
+
+
+
+
+`; diff --git a/src-next/switch/test/__snapshots__/index.spec.js.snap b/src-next/switch/test/__snapshots__/index.spec.js.snap new file mode 100644 index 000000000..8a9a87679 --- /dev/null +++ b/src-next/switch/test/__snapshots__/index.spec.js.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`inactive-color prop 1`] = ` +
+
+
+`; + +exports[`size prop 1`] = ` +
+
+
+`; diff --git a/src-next/switch/test/demo.spec.js b/src-next/switch/test/demo.spec.js new file mode 100644 index 000000000..5c70922b5 --- /dev/null +++ b/src-next/switch/test/demo.spec.js @@ -0,0 +1,4 @@ +import Demo from '../demo'; +import { snapshotDemo } from '../../../test/demo'; + +snapshotDemo(Demo); diff --git a/src-next/switch/test/index.spec.js b/src-next/switch/test/index.spec.js new file mode 100644 index 000000000..07a177272 --- /dev/null +++ b/src-next/switch/test/index.spec.js @@ -0,0 +1,91 @@ +import Switch from '..'; +import { mount } from '../../../test'; + +test('emit event', () => { + const input = jest.fn(); + const change = jest.fn(); + const wrapper = mount(Switch, { + listeners: { + input, + change, + }, + }); + wrapper.trigger('click'); + + expect(input).toHaveBeenCalledWith(true); + expect(change).toHaveBeenCalledWith(true); +}); + +test('disabled', () => { + const input = jest.fn(); + const change = jest.fn(); + const wrapper = mount(Switch, { + listeners: { + input, + change, + }, + propsData: { + disabled: true, + }, + }); + wrapper.trigger('click'); + + expect(input).toHaveBeenCalledTimes(0); + expect(change).toHaveBeenCalledTimes(0); +}); + +test('active-value & inactive-value prop', () => { + const input = jest.fn(); + const change = jest.fn(); + const wrapper = mount(Switch, { + propsData: { + value: '1', + activeValue: '1', + inactiveValue: '2', + }, + listeners: { + input, + change, + }, + }); + + wrapper.trigger('click'); + + expect(input).toHaveBeenCalledWith('2'); + expect(change).toHaveBeenCalledWith('2'); +}); + +test('inactive-color prop', () => { + const wrapper = mount(Switch, { + propsData: { + value: '2', + inactiveValue: '2', + inactiveColor: 'black', + }, + }); + + expect(wrapper).toMatchSnapshot(); +}); + +test('size prop', () => { + const wrapper = mount(Switch, { + propsData: { + size: 20, + }, + }); + + expect(wrapper).toMatchSnapshot(); +}); + +test('click event', () => { + const click = jest.fn(); + const wrapper = mount(Switch, { + listeners: { + click, + }, + }); + + wrapper.trigger('click'); + + expect(click).toHaveBeenCalledTimes(1); +}); diff --git a/vant.config.js b/vant.config.js index 208884089..b7db042f7 100644 --- a/vant.config.js +++ b/vant.config.js @@ -167,10 +167,10 @@ module.exports = { // path: 'stepper', // title: 'Stepper 步进器', // }, - // { - // path: 'switch', - // title: 'Switch 开关', - // }, + { + path: 'switch', + title: 'Switch 开关', + }, // { // path: 'uploader', // title: 'Uploader 文件上传', @@ -501,10 +501,10 @@ module.exports = { // path: 'stepper', // title: 'Stepper', // }, - // { - // path: 'switch', - // title: 'Switch', - // }, + { + path: 'switch', + title: 'Switch', + }, // { // path: 'uploader', // title: 'Uploader',