diff --git a/src-next/step/index.js b/src-next/step/index.js new file mode 100644 index 000000000..5998e2d51 --- /dev/null +++ b/src-next/step/index.js @@ -0,0 +1,88 @@ +import { createNamespace } from '../utils'; +import { BORDER } from '../utils/constant'; +import { ChildrenMixin } from '../mixins/relation'; +import Icon from '../icon'; + +const [createComponent, bem] = createNamespace('step'); + +export default createComponent({ + mixins: [ChildrenMixin('vanSteps')], + + computed: { + status() { + if (this.index < this.parent.active) { + return 'finish'; + } + if (this.index === +this.parent.active) { + return 'process'; + } + }, + + active() { + return this.status === 'process'; + }, + + lineStyle() { + if (this.status === 'finish' && this.parent.activeColor) { + return { + background: this.parent.activeColor, + }; + } + }, + }, + + methods: { + genCircle() { + const { activeIcon, activeColor, inactiveIcon } = this.parent; + + if (this.active) { + return this.$slots['active-icon'] ? ( + this.$slots['active-icon']() + ) : ( + + ); + } + + if (inactiveIcon || this.$slots['inactive-icon']) { + return this.$slots['inactive-icon'] ? ( + this.$slots['inactive-icon']() + ) : ( + + ); + } + + return ; + }, + + onClickStep() { + this.parent.$emit('click-step', this.index); + }, + }, + + render() { + const { status, active } = this; + const { activeColor, direction } = this.parent; + + const titleStyle = active && { color: activeColor }; + + return ( +
+
+ {this.$slots.default?.()} +
+
+ {this.genCircle()} +
+
+
+ ); + }, +}); diff --git a/src-next/step/index.less b/src-next/step/index.less new file mode 100644 index 000000000..8ecc8ca13 --- /dev/null +++ b/src-next/step/index.less @@ -0,0 +1,151 @@ +@import '../style/var'; + +.van-step { + position: relative; + flex: 1; + color: @step-text-color; + font-size: @step-font-size; + + &__circle { + display: block; + width: @step-circle-size; + height: @step-circle-size; + background-color: @step-circle-color; + border-radius: 50%; + } + + &__line { + position: absolute; + background-color: @step-line-color; + transition: background-color @animation-duration-base; + } + + &--horizontal { + float: left; + + &:first-child { + .van-step__title { + margin-left: 0; + transform: none; + } + } + + &:last-child { + position: absolute; + right: 1px; + width: auto; + + .van-step__title { + margin-left: 0; + transform: none; + } + + .van-step__circle-container { + right: -9px; + left: auto; + } + } + + .van-step__circle-container { + position: absolute; + top: 30px; + left: -@padding-xs; + z-index: 1; + padding: 0 @padding-xs; + background-color: @white; + transform: translateY(-50%); + } + + .van-step__title { + display: inline-block; + margin-left: 3px; + font-size: @step-horizontal-title-font-size; + transform: translateX(-50%); + + @media (max-width: 321px) { + font-size: @step-horizontal-title-font-size - 1px; + } + } + + .van-step__line { + top: 30px; + left: 0; + width: 100%; + height: 1px; + } + + .van-step__icon { + display: block; + font-size: @step-icon-size; + } + + .van-step--process { + color: @step-process-text-color; + } + } + + &--vertical { + display: block; + float: none; + padding: 10px 10px 10px 0; + line-height: 18px; + + &:not(:last-child)::after { + border-bottom-width: 1px; + } + + &:first-child { + &::before { + position: absolute; + top: 0; + left: -15px; + z-index: 1; + width: 1px; + height: 20px; + background-color: @white; + content: ''; + } + } + + .van-step__circle-container { + position: absolute; + top: 19px; + left: -15px; + z-index: 2; + font-size: @step-icon-size; + line-height: 1; + transform: translate(-50%, -50%); + } + + .van-step__line { + top: 16px; + left: -15px; + width: 1px; + height: 100%; + } + } + + &:last-child { + .van-step__line { + width: 0; + } + } + + &--finish { + color: @step-finish-text-color; + + .van-step__circle, + .van-step__line { + background-color: @step-finish-line-color; + } + } + + &__icon, + &__title { + transition: color @animation-duration-base; + + &--active { + color: @step-active-color; + } + } +} diff --git a/src-next/steps/README.md b/src-next/steps/README.md new file mode 100644 index 000000000..0f2863655 --- /dev/null +++ b/src-next/steps/README.md @@ -0,0 +1,89 @@ +# Steps + +### Install + +```js +import Vue from 'vue'; +import { Step, Steps } from 'vant'; + +Vue.use(Step); +Vue.use(Steps); +``` + +## Usage + +### Basic Usage + +```html + + Step1 + Step2 + Step3 + Step4 + +``` + +```js +export default { + data() { + return { + active: 1, + }; + }, +}; +``` + +### Custom Style + +```html + + Step1 + Step2 + Step3 + Step4 + +``` + +### Vertical Steps + +```html + + +

【City】Status1

+

2016-07-12 12:40

+
+ +

【City】Status2

+

2016-07-11 10:00

+
+ +

【City】Status3

+

2016-07-10 09:30

+
+
+``` + +## API + +### Steps Props + +| Attribute | Description | Type | Default | +| ------------- | ------------------------ | ------------------ | ------------ | +| active | Active step | _number \| string_ | `0` | +| direction | Can be set to `vertical` | _string_ | `horizontal` | +| active-color | Active step color | _string_ | `#07c160` | +| active-icon | Active icon name | _string_ | `checked` | +| inactive-icon | Active icon name | _string_ | - | + +### Step Slots + +| Name | Description | +| ------------- | -------------------- | +| active-icon | Custom active icon | +| inactive-icon | Custom inactive icon | + +### Steps Events + +| Event | Description | Arguments | +| --- | --- | --- | +| click-step `v2.5.9` | Triggered when a step's title or icon is clicked | _index: number_ | diff --git a/src-next/steps/README.zh-CN.md b/src-next/steps/README.zh-CN.md new file mode 100644 index 000000000..d1a93a0f1 --- /dev/null +++ b/src-next/steps/README.zh-CN.md @@ -0,0 +1,95 @@ +# Steps 步骤条 + +### 引入 + +```js +import Vue from 'vue'; +import { Step, Steps } from 'vant'; + +Vue.use(Step); +Vue.use(Steps); +``` + +## 代码演示 + +### 基础用法 + +`active`属性表示当前步骤的索引,从 0 起计 + +```html + + 买家下单 + 商家接单 + 买家提货 + 交易完成 + +``` + +```js +export default { + data() { + return { + active: 1, + }; + }, +}; +``` + +### 自定义样式 + +可以通过`active-icon`和`active-color`属性设置激活状态下的图标和颜色 + +```html + + 买家下单 + 商家接单 + 买家提货 + 交易完成 + +``` + +### 竖向步骤条 + +可以通过设置`direction`属性来改变步骤条的显示方向 + +```html + + +

【城市】物流状态1

+

2016-07-12 12:40

+
+ +

【城市】物流状态2

+

2016-07-11 10:00

+
+ +

快件已发货

+

2016-07-10 09:30

+
+
+``` + +## API + +### Steps Props + +| 参数 | 说明 | 类型 | 默认值 | +| --- | --- | --- | --- | +| active | 当前步骤 | _number \| string_ | `0` | +| direction | 显示方向,可选值为 `vertical` | _string_ | `horizontal` | +| active-color | 激活状态颜色 | _string_ | `#07c160` | +| active-icon | 激活状态底部图标,可选值见 [Icon 组件](#/zh-CN/icon) | _string_ | `checked` | +| inactive-icon | 未激活状态底部图标,可选值见 [Icon 组件](#/zh-CN/icon) | _string_ | - | + +### Step Slots + +| 名称 | 说明 | +| ------------- | -------------------- | +| active-icon | 自定义激活状态图标 | +| inactive-icon | 自定义未激活状态图标 | + +### Steps Events + +| 事件名 | 说明 | 回调参数 | +| ------------------- | -------------------------- | --------------- | +| click-step `v2.5.9` | 点击步骤的标题或图标时触发 | _index: number_ | diff --git a/src-next/steps/demo/index.vue b/src-next/steps/demo/index.vue new file mode 100644 index 000000000..1d0a6236b --- /dev/null +++ b/src-next/steps/demo/index.vue @@ -0,0 +1,116 @@ + + + + + diff --git a/src-next/steps/index.js b/src-next/steps/index.js new file mode 100644 index 000000000..45b0ad84b --- /dev/null +++ b/src-next/steps/index.js @@ -0,0 +1,35 @@ +import { createNamespace } from '../utils'; +import { ParentMixin } from '../mixins/relation'; + +const [createComponent, bem] = createNamespace('steps'); + +export default createComponent({ + mixins: [ParentMixin('vanSteps')], + + props: { + activeColor: String, + inactiveIcon: String, + active: { + type: [Number, String], + default: 0, + }, + direction: { + type: String, + default: 'horizontal', + }, + activeIcon: { + type: String, + default: 'checked', + }, + }, + + emits: ['click-step'], + + render() { + return ( +
+
{this.$slots.default?.()}
+
+ ); + }, +}); diff --git a/src-next/steps/index.less b/src-next/steps/index.less new file mode 100644 index 000000000..51d94d716 --- /dev/null +++ b/src-next/steps/index.less @@ -0,0 +1,21 @@ +@import '../style/var'; + +.van-steps { + overflow: hidden; + background-color: @steps-background-color; + + &--horizontal { + padding: 10px 10px 0; + + .van-steps__items { + position: relative; + display: flex; + margin: 0 0 10px; + padding-bottom: 22px; + } + } + + &--vertical { + padding: 0 0 0 @padding-xl; + } +} diff --git a/src-next/steps/test/__snapshots__/demo.spec.js.snap b/src-next/steps/test/__snapshots__/demo.spec.js.snap new file mode 100644 index 000000000..98a80d91e --- /dev/null +++ b/src-next/steps/test/__snapshots__/demo.spec.js.snap @@ -0,0 +1,96 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders demo correctly 1`] = ` +
+
+
+
+
+
买家下单
+
+
+
+
+
商家接单
+
+
+
+
+
+
买家提货
+
+
+
+
+
交易完成
+
+
+
+
+
+
+
+
+
+
+
买家下单
+
+
+
+
+
+
商家接单
+
+
+
+
+
+
买家提货
+
+
+
+
+
+
交易完成
+
+
+
+
+
+
+
+
+
+
+
+
+

【城市】物流状态1

+

2016-07-12 12:40

+
+
+
+
+
+
+
+

【城市】物流状态

+

2016-07-11 10:00

+
+
+
+
+
+
+

快件已发货

+

2016-07-10 09:30

+
+
+
+
+
+
+
+
+`; diff --git a/src-next/steps/test/__snapshots__/index.spec.js.snap b/src-next/steps/test/__snapshots__/index.spec.js.snap new file mode 100644 index 000000000..232fc1a04 --- /dev/null +++ b/src-next/steps/test/__snapshots__/index.spec.js.snap @@ -0,0 +1,29 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`icon slot 1`] = ` +
+
+
+
+ A +
+
Custim Inactive Icon
+
+
+
+
+ B +
+
Custim Active Icon
+
+
+
+
+ C +
+
Custim Inactive Icon
+
+
+
+
+`; diff --git a/src-next/steps/test/demo.spec.js b/src-next/steps/test/demo.spec.js new file mode 100644 index 000000000..5c70922b5 --- /dev/null +++ b/src-next/steps/test/demo.spec.js @@ -0,0 +1,4 @@ +import Demo from '../demo'; +import { snapshotDemo } from '../../../test/demo'; + +snapshotDemo(Demo); diff --git a/src-next/steps/test/index.spec.js b/src-next/steps/test/index.spec.js new file mode 100644 index 000000000..2521175f1 --- /dev/null +++ b/src-next/steps/test/index.spec.js @@ -0,0 +1,49 @@ +import { mount } from '../../../test'; + +test('icon slot', () => { + const wrapper = mount({ + template: ` + + + + A + + + + B + + + + C + + + `, + }); + expect(wrapper).toMatchSnapshot(); +}); + +test('click-step event', () => { + const onClickStep = jest.fn(); + const wrapper = mount({ + template: ` + + A + B + C + + `, + methods: { + onClickStep, + }, + }); + + wrapper.find('.van-step').trigger('click'); + expect(onClickStep).toHaveBeenCalledTimes(0); + + wrapper.find('.van-step__title').trigger('click'); + expect(onClickStep).toHaveBeenCalledWith(0); + + wrapper.findAll('.van-step__circle-container').at(2).trigger('click'); + expect(onClickStep).toHaveBeenCalledTimes(2); + expect(onClickStep).toHaveBeenLastCalledWith(2); +}); diff --git a/vant.config.js b/vant.config.js index 8bc31a38e..0c9d2eb25 100644 --- a/vant.config.js +++ b/vant.config.js @@ -265,10 +265,10 @@ module.exports = { path: 'skeleton', title: 'Skeleton 骨架屏', }, - // { - // path: 'steps', - // title: 'Steps 步骤条', - // }, + { + path: 'steps', + title: 'Steps 步骤条', + }, // { // path: 'sticky', // title: 'Sticky 粘性布局', @@ -599,10 +599,10 @@ module.exports = { path: 'skeleton', title: 'Skeleton', }, - // { - // path: 'steps', - // title: 'Steps', - // }, + { + path: 'steps', + title: 'Steps', + }, // { // path: 'sticky', // title: 'Sticky',