[new feature] NumberKeyboard add custom theme (#472)

This commit is contained in:
neverland 2017-12-22 16:29:21 +08:00 committed by GitHub
parent 57abc04346
commit 9e6b663145
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 259 additions and 97 deletions

View File

@ -1,15 +1,31 @@
<template> <template>
<demo-section> <demo-section>
<demo-block :title="$t('basicUsage')"> <demo-block :title="$t('default')">
<van-button @touchstart.native.stop="showKeyboard = true"> <van-button @touchstart.native.stop="keyboard = 'default'">
{{ $t('button1') }} {{ $t('button1') }}
</van-button> </van-button>
<van-number-keyboard <van-number-keyboard
:show="showKeyboard" :show="keyboard === 'default'"
:closeButtonText="$t('close')" :closeButtonText="$t('close')"
extraKey="." extraKey="."
@blur="showKeyboard = false" @blur="keyboard = ''"
@input="onInput"
@delete="onDelete"
/>
</demo-block>
<demo-block :title="$t('custom')">
<van-button @touchstart.native.stop="keyboard = 'custom'">
{{ $t('button2') }}
</van-button>
<van-number-keyboard
:show="keyboard === 'custom'"
:closeButtonText="$t('close')"
theme="custom"
extraKey="."
@blur="keyboard = ''"
@input="onInput" @input="onInput"
@delete="onDelete" @delete="onDelete"
/> />
@ -21,20 +37,24 @@
export default { export default {
i18n: { i18n: {
'zh-CN': { 'zh-CN': {
button1: '弹出键盘', default: '默认样式',
button2: '收起键盘', custom: '自定义样式',
button1: '弹出默认键盘',
button2: '弹出自定义键盘',
close: '完成' close: '完成'
}, },
'en-US': { 'en-US': {
button1: 'Show Keyboard', default: 'Default style',
button2: 'Hide Keyboard', custom: 'Custom style',
button1: 'Show Default Keyboard',
button2: 'Show Custom Keyboard',
close: 'Close' close: 'Close'
} }
}, },
data() { data() {
return { return {
showKeyboard: true keyboard: 'default'
}; };
}, },
@ -46,10 +66,9 @@ export default {
Toast('Delete'); Toast('Delete');
} }
} }
} };
</script> </script>
<style lang="postcss"> <style lang="postcss">
.demo-number-keyboard { .demo-number-keyboard {
.van-button { .van-button {

View File

@ -9,7 +9,7 @@ Vue.use(NumberKeyboard);
### Usage ### Usage
#### Basic Usage #### Default Style
```html ```html
<van-button @touchstart.native.stop="showKeyboard = true"> <van-button @touchstart.native.stop="showKeyboard = true">
@ -45,11 +45,26 @@ export default {
} }
``` ```
#### Custom Style
```html
<van-number-keyboard
theme="custom"
extraKey="."
:show="showKeyboard"
closeButtonText="Close"
@blur="showKeyboard = false"
@input="onInput"
@delete="onDelete"
/>
```
### API ### API
| Attribute | Description | Type | Default | Accepted Values | | Attribute | Description | Type | Default | Accepted Values |
|-----------|-----------|-----------|-------------|-------------| |-----------|-----------|-----------|-------------|-------------|
| show | Whether to show keyboard | `Boolean` | - | - | | show | Whether to show keyboard | `Boolean` | - | - |
| theme | Keyboard theme | `String` | `Default` | `Custom` |
| title | Keyboard title | `String` | - | - | | title | Keyboard title | `String` | - | - |
| zIndex | Keyboard z-index | `Number` | `100` | - | | zIndex | Keyboard z-index | `Number` | `100` | - |
| extraKey | Content of bottom left key | `String` | `''` | - | | extraKey | Content of bottom left key | `String` | `''` | - |

View File

@ -9,11 +9,11 @@ Vue.use(NumberKeyboard);
### 代码演示 ### 代码演示
#### 基础用法 #### 默认样式
```html ```html
<van-button @touchstart.native.stop="showKeyboard = true"> <van-button @touchstart.native.stop="showKeyboard = true">
弹出键盘 弹出默认键盘
</van-button> </van-button>
<van-number-keyboard <van-number-keyboard
@ -45,11 +45,26 @@ export default {
} }
``` ```
#### 自定义样式
```html
<van-number-keyboard
theme="custom"
extraKey="."
:show="showKeyboard"
closeButtonText="完成"
@blur="showKeyboard = false"
@input="onInput"
@delete="onDelete"
/>
```
### API ### API
| 参数 | 说明 | 类型 | 默认值 | 可选值 | | 参数 | 说明 | 类型 | 默认值 | 可选值 |
|-----------|-----------|-----------|-------------|-------------| |-----------|-----------|-----------|-------------|-------------|
| show | 是否显示键盘 | `Boolean` | - | - | | show | 是否显示键盘 | `Boolean` | - | - |
| theme | 键盘样式风格 | `String` | `Default` | `Custom` |
| title | 键盘标题 | `String` | - | - | | title | 键盘标题 | `String` | - | - |
| zIndex | 键盘 z-index | `Number` | `100` | - | | zIndex | 键盘 z-index | `Number` | `100` | - |
| extraKey | 左下角按键内容 | `String` | `''` | - | | extraKey | 左下角按键内容 | `String` | `''` | - |

View File

@ -0,0 +1,49 @@
<template>
<i
v-text="text"
@touchstart.stop.prevent="onFocus"
@touchmove="onBlur"
@touchend="onBlur"
@touchcancel="onBlur"
class="van-hairline van-key"
:class="className"
/>
</template>
<script>
export default {
props: {
text: [String, Number],
type: {
type: Array,
default: () => []
}
},
data() {
return {
active: false
};
},
computed: {
className() {
const types = this.type.slice(0);
this.active && types.push('active');
return types.map(type => `van-key--${type}`);
}
},
methods: {
onFocus() {
this.active = true;
this.$emit('press', this.text);
},
onBlur() {
this.active = false;
}
}
};
</script>

View File

@ -4,43 +4,48 @@
v-show="show" v-show="show"
:style="style" :style="style"
class="van-number-keyboard" class="van-number-keyboard"
@touchstart.stop.prevent="focus" :class="`van-number-keyboard--${theme}`"
@touchmove="blurKey"
@touchend="blurKey"
@touchcancel="blurKey"
@animationend="onAnimationEnd" @animationend="onAnimationEnd"
@webkitAnimationEnd="onAnimationEnd" @webkitAnimationEnd="onAnimationEnd"
> >
<div class="van-number-keyboard__title van-hairline--top" v-if="title || closeButtonText"> <div class="van-number-keyboard__title van-hairline--top" v-if="title || showTitleClose">
<span>{{ title }}</span> <span v-text="title" />
<span <span
class="van-number-keyboard__close" class="van-number-keyboard__close"
v-if="showTitleClose"
v-text="closeButtonText" v-text="closeButtonText"
@click="blurKeyboard" @click="onBlur"
/> />
</div> </div>
<i <div class="van-number-keyboard__body">
v-for="(key, index) in keys" <key
v-text="key" v-for="(key, index) in keys"
:data-key="index" :key="index"
class="van-hairline" :text="key.text"
:class="{ :type="key.type"
'van-number-keyboard--active': index === active, @press="onPressKey"
'van-number-keyboard__delete': index === 11 && showDeleteKey />
}" </div>
/> <div class="van-number-keyboard__sidebar" v-if="theme === 'custom'">
<key :text="'delete'" :type="['delete', 'big']" @press="onPressKey" />
<key :text="closeButtonText" :type="['green', 'big']" @press="onPressKey" />
</div>
</div> </div>
</transition> </transition>
</template> </template>
<script> <script>
import { create } from '../utils'; import { create } from '../utils';
import Key from './Key';
export default create({ export default create({
name: 'van-number-keyboard', name: 'van-number-keyboard',
components: { Key },
props: { props: {
show: Boolean, show: Boolean,
title: String,
closeButtonText: String, closeButtonText: String,
theme: { theme: {
type: String, type: String,
@ -50,7 +55,6 @@ export default create({
type: String, type: String,
default: '' default: ''
}, },
title: String,
zIndex: { zIndex: {
type: Number, type: Number,
default: 100 default: 100
@ -85,12 +89,6 @@ export default create({
this.handler(false); this.handler(false);
}, },
data() {
return {
active: -1
};
},
watch: { watch: {
show() { show() {
if (!this.transition) { if (!this.transition) {
@ -102,10 +100,26 @@ export default create({
computed: { computed: {
keys() { keys() {
const keys = []; const keys = [];
for (let i = 0; i < 12; i++) { for (let i = 1; i <= 9; i++) {
const key = i === 10 ? 0 : i < 9 ? i + 1 : i === 9 ? this.extraKey : ''; keys.push({ text: i });
keys.push(key);
} }
switch (this.theme) {
case 'default':
keys.push(
{ text: this.extraKey, type: ['gray'] },
{ text: 0 },
{ text: 'delete', type: ['gray', 'delete'] }
);
break;
case 'custom':
keys.push(
{ text: 0, type: ['middle'] },
{ text: this.extraKey }
);
break;
}
return keys; return keys;
}, },
@ -113,6 +127,10 @@ export default create({
return { return {
zIndex: this.zIndex zIndex: this.zIndex
}; };
},
showTitleClose() {
return this.closeButtonText && this.theme === 'default';
} }
}, },
@ -120,32 +138,30 @@ export default create({
handler(action) { handler(action) {
if (action !== this.handlerStatus && this.hideOnClickOutside) { if (action !== this.handlerStatus && this.hideOnClickOutside) {
this.handlerStatus = action; this.handlerStatus = action;
document.body[(action ? 'add' : 'remove') + 'EventListener']('touchstart', this.blurKeyboard); document.body[(action ? 'add' : 'remove') + 'EventListener']('touchstart', this.onBlur);
} }
}, },
focus(event) { onBlur() {
this.active = parseInt(event.target.dataset.key);
if (this.active === 11) {
this.$emit('delete');
} else if (!isNaN(this.active)) {
const key = this.keys[this.active];
if (key !== '') {
this.$emit('input', key);
}
}
},
blurKey() {
this.active = -1;
},
blurKeyboard() {
this.$emit('blur'); this.$emit('blur');
}, },
onAnimationEnd() { onAnimationEnd() {
this.$emit(this.show ? 'show' : 'hide'); this.$emit(this.show ? 'show' : 'hide');
},
onPressKey(text) {
if (text === '') {
return;
}
if (text === 'delete') {
this.$emit('delete');
} else if (text === this.closeButtonText) {
this.onBlur();
} else {
this.$emit('input', text);
}
} }
} }
}); });

View File

@ -1,5 +1,7 @@
@import "./common/var.css"; @import "./common/var.css";
$van-number-keyboard-key-height: 54px;
.van-number-keyboard { .van-number-keyboard {
left: 0; left: 0;
bottom: 0; bottom: 0;
@ -10,13 +12,16 @@
animation-timing-function: ease-out; animation-timing-function: ease-out;
&__title { &__title {
font-weight: 400;
text-align: center;
color: $gray-dark;
font-size: 12px;
height: 30px; height: 30px;
font-size: 14px;
line-height: 30px; line-height: 30px;
text-align: center;
position: relative; position: relative;
color: $gray-darker;
}
&__body {
box-sizing: border-box;
} }
&__close { &__close {
@ -31,36 +36,70 @@
} }
} }
i { &__sidebar {
width: calc(100%/3); right: 0;
height: 54px; bottom: 0;
font-size: 24px; width: 25%;
line-height: 54px; position: absolute;
font-style: normal; height: calc($van-number-keyboard-key-height * 4);
text-align: center; }
display: inline-block;
vertical-align: middle; &--custom {
.van-number-keyboard__body {
padding-right: 25%;
}
}
}
.van-key {
width: calc(100%/3);
font-size: 24px;
font-style: normal;
text-align: center;
display: inline-block;
vertical-align: middle;
height: $van-number-keyboard-key-height;
line-height: $van-number-keyboard-key-height;
&::after {
border-width: 1px 1px 0 0;
}
&--middle {
width: calc(200%/3);
}
&--big {
width: 100%;
height: calc($van-number-keyboard-key-height * 2);
line-height: calc($van-number-keyboard-key-height * 2);
}
&--green {
font-size: 20px;
color: $white;
background-color: $green;
&.van-key--active {
background-color: #308305;
}
&::after { &::after {
border-top-width: 1px; border-color: $green;
}
&:not(:nth-of-type(3n))::after {
border-right-width: 1px;
}
&:nth-of-type(10),
&:nth-of-type(12) {
background-color: #F3F3F6;
} }
} }
&__delete { &--delete {
font-size: 0;
background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAeCAMAAABg6AyVAAAAbFBMVEUAAAAfHiIdHB4eHR8dHR4eHB4dHB4dHR8gICIdHB4dHB4dHB4dHB8eHh8hISEeHR8fHB8fHR8fHR8fHx8eHiArKyszMzMeHB8eHB8fHR8eHiAeHh4dHB4vLjDY2Nn////b29zKysq9vb28vLzkfBRpAAAAHHRSTlMAK/PW+I/llBv77N1kSCPwWlFAOTMGBb28hHlu08g5sgAAAMlJREFUOMuV1MsWgiAQgGHQyOx+s+sgYO//jnnMGIdDDfwbN99CYEDQFiVEKkolPUG7gl9VTWC31NKuDbVz+Fc1tRJtPDmxS2BS3p5ZC+XXnnbAVoz2WEBCH7uZAalzGoa06whGiznT6sG2xgX4QO2Aej1+KN7XBKL2FvGaMtTWBhbQhtoaYzVQrHKwuGf8hhAPSF5g3xPSt45sCHcouNWx436FGA+RHyQcD35EcUj54U8ff4WYvVi1zLjelUh/OG6XjOeLWv5hfAOI+HLwwOAqhAAAAABJRU5ErkJggg==") no-repeat center center; background: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACwAAAAeCAMAAABg6AyVAAAAbFBMVEUAAAAfHiIdHB4eHR8dHR4eHB4dHB4dHR8gICIdHB4dHB4dHB4dHB8eHh8hISEeHR8fHB8fHR8fHR8fHx8eHiArKyszMzMeHB8eHB8fHR8eHiAeHh4dHB4vLjDY2Nn////b29zKysq9vb28vLzkfBRpAAAAHHRSTlMAK/PW+I/llBv77N1kSCPwWlFAOTMGBb28hHlu08g5sgAAAMlJREFUOMuV1MsWgiAQgGHQyOx+s+sgYO//jnnMGIdDDfwbN99CYEDQFiVEKkolPUG7gl9VTWC31NKuDbVz+Fc1tRJtPDmxS2BS3p5ZC+XXnnbAVoz2WEBCH7uZAalzGoa06whGiznT6sG2xgX4QO2Aej1+KN7XBKL2FvGaMtTWBhbQhtoaYzVQrHKwuGf8hhAPSF5g3xPSt45sCHcouNWx436FGA+RHyQcD35EcUj54U8ff4WYvVi1zLjelUh/OG6XjOeLWv5hfAOI+HLwwOAqhAAAAABJRU5ErkJggg==") no-repeat center center;
background-size: auto 15px; background-size: auto 15px;
} }
i&--active { &--gray {
background-color: $active-color!important; background-color: #F3F3F6;
}
&--active {
background-color: $active-color;
} }
} }

View File

@ -4,10 +4,17 @@ import { mount } from 'avoriaz';
import { triggerTouch } from '../utils'; import { triggerTouch } from '../utils';
function mockKeyDown(wrapper, keyIndex) { function mockKeyDown(wrapper, keyIndex) {
const customEvent = document.createEvent('CustomEvent'); const key = wrapper.element.querySelectorAll('.van-key')[keyIndex];
customEvent.initCustomEvent('touchstart', true, true, {}); const touchStart = document.createEvent('CustomEvent');
wrapper.element.dataset.key = keyIndex; touchStart.initCustomEvent('touchstart', true, true, {});
wrapper.element.dispatchEvent(customEvent); key.dispatchEvent(touchStart);
}
function mockKeyUp(wrapper, keyIndex) {
const key = wrapper.element.querySelectorAll('.van-key')[keyIndex];
const touchEnd = document.createEvent('CustomEvent');
touchEnd.initCustomEvent('touchend', true, true, {});
key.dispatchEvent(touchEnd);
} }
describe('NumberKeyboard', () => { describe('NumberKeyboard', () => {
@ -22,23 +29,25 @@ describe('NumberKeyboard', () => {
}); });
it('click a keyboard key', (done) => { it('click a keyboard key', (done) => {
wrapper = mount(NumberKeyboard, {}); wrapper = mount(NumberKeyboard, {
propsData: {
theme: 'custom',
closeButtonText: 'close'
}
});
// just for coverage // just for coverage
wrapper.vm.handler(true); wrapper.vm.handler(true);
wrapper.vm.$on('input', value => { wrapper.vm.$on('input', value => {
mockKeyUp(wrapper, 0);
expect(value).to.equal(1); expect(value).to.equal(1);
expect(wrapper.vm.active).to.equal(0);
triggerTouch(wrapper, 'touchend');
expect(wrapper.vm.active).to.equal(-1);
done(); done();
}); });
mockKeyDown(wrapper, 9); mockKeyDown(wrapper, 12); // close
mockKeyDown(wrapper, NaN); mockKeyDown(wrapper, 10); // empty
mockKeyDown(wrapper, 0); mockKeyDown(wrapper, 0); // 1
}); });
it('click delete key', (done) => { it('click delete key', (done) => {