[improvement] Functional: ContactList, NavBar, Panel, SubmitBar, SwitchCell, Tag (#2675)

This commit is contained in:
neverland 2019-02-02 16:04:54 +08:00 committed by GitHub
parent 3c6c32e305
commit 5926d02d38
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 302 additions and 265 deletions

View File

@ -1,4 +1,4 @@
import { use } from '../utils';
import { use, noop } from '../utils';
import Icon from '../icon';
import Cell from '../cell';
import Button from '../button';
@ -7,55 +7,57 @@ import RadioGroup from '../radio-group';
const [sfc, bem, t] = use('contact-list');
export default sfc({
props: {
value: null,
list: Array,
addText: String
},
export default sfc(
{
props: {
value: null,
list: Array,
addText: String
},
render(h) {
return (
<div class={bem()}>
<RadioGroup
value={this.value}
class={bem('group')}
onInput={event => {
this.$emit('input', event);
render(h, context) {
const { props, listeners } = context;
const List = props.list.map((item, index) => (
<Cell
key={item.id}
isLink
onClick={() => {
listeners.input && listeners.input(item.id);
listeners.select && listeners.select(item, index);
}}
>
{this.list.map((item, index) => (
<Cell key={item.id} isLink>
<Radio
name={item.id}
onClick={() => {
this.$emit('select', item, index);
}}
>
<div class={bem('name')}>{`${item.name}${item.tel}`}</div>
</Radio>
<Icon
slot="right-icon"
name="edit"
class={bem('edit')}
onClick={() => {
this.$emit('edit', item, index);
}}
/>
</Cell>
))}
</RadioGroup>
<Button
square
size="large"
type="danger"
class={bem('add')}
text={this.addText || t('addText')}
onClick={() => {
this.$emit('add');
}}
/>
</div>
);
}
});
<Radio name={item.id}>
<div class={bem('name')}>{`${item.name}${item.tel}`}</div>
</Radio>
<Icon
slot="right-icon"
name="edit"
class={bem('edit')}
onClick={event => {
event.stopPropagation();
listeners.edit && listeners.edit(item, index);
}}
/>
</Cell>
));
return (
<div class={bem()} {...context.data}>
<RadioGroup value={props.value} class={bem('group')}>
{List}
</RadioGroup>
<Button
square
size="large"
type="danger"
class={bem('add')}
text={props.addText || t('addText')}
onClick={listeners.add || noop}
/>
</div>
);
}
},
true
);

View File

@ -19,16 +19,24 @@
margin-left: 27px;
}
.van-radio__input {
.van-radio__icon {
top: 50%;
left: 0;
font-size: 16px;
height: 16px;
position: absolute;
line-height: 16px;
transform: translate(0, -50%);
.van-icon {
width: 16px;
height: 16px;
font-size: 12px;
}
}
.van-icon-checked {
color: @red;
.van-radio__icon--checked .van-icon {
border-color: @red;
background-color: @red;
}
&__group {

View File

@ -1,53 +1,55 @@
import { use } from '../utils';
import { use, noop } from '../utils';
import Icon from '../icon';
const [sfc, bem] = use('nav-bar');
export default sfc({
props: {
title: String,
fixed: Boolean,
leftText: String,
rightText: String,
leftArrow: Boolean,
border: {
type: Boolean,
default: true
export default sfc(
{
props: {
title: String,
fixed: Boolean,
leftText: String,
rightText: String,
leftArrow: Boolean,
border: {
type: Boolean,
default: true
},
zIndex: {
type: Number,
default: 1
}
},
zIndex: {
type: Number,
default: 1
render(h, context) {
const { props, listeners } = context;
const slots = context.slots();
return (
<div
class={[bem({ fixed: props.fixed }), { 'van-hairline--bottom': props.border }]}
style={{ zIndex: props.zIndex }}
{...context.data}
>
<div
class={bem('left')}
onClick={listeners['click-left'] || noop}
>
{slots.left || [
props.leftArrow && <Icon class={bem('arrow')} name="arrow-left" />,
props.leftText && <span class={bem('text')}>{props.leftText}</span>
]}
</div>
<div class={[bem('title'), 'van-ellipsis']}>{slots.title || props.title}</div>
<div
class={bem('right')}
onClick={listeners['click-right'] || noop}
>
{slots.right || (props.rightText && <span class={bem('text')}>{props.rightText}</span>)}
</div>
</div>
);
}
},
render(h) {
return (
<div
class={[bem({ fixed: this.fixed }), { 'van-hairline--bottom': this.border }]}
style={{ zIndex: this.zIndex }}
>
<div
class={bem('left')}
onClick={() => {
this.$emit('click-left');
}}
>
{this.$slots.left || [
this.leftArrow && <Icon class={bem('arrow')} name="arrow-left" />,
this.leftText && <span class={bem('text')}>{this.leftText}</span>
]}
</div>
<div class={[bem('title'), 'van-ellipsis']}>{this.$slots.title || this.title}</div>
<div
class={bem('right')}
onClick={() => {
this.$emit('click-right');
}}
>
{this.$slots.right ||
(this.rightText && <span class={bem('text')}>{this.rightText}</span>)}
</div>
</div>
);
}
});
true
);

View File

@ -4,31 +4,35 @@ import CellGroup from '../cell-group';
const [sfc, bem] = use('panel');
export default sfc({
props: {
icon: String,
desc: String,
title: String,
status: String
export default sfc(
{
props: {
icon: String,
desc: String,
title: String,
status: String
},
render(h, context) {
const { props } = context;
const slots = context.slots();
return (
<CellGroup class={bem()} {...context.data}>
{slots.header || (
<Cell
class={bem('header')}
icon={props.icon}
label={props.desc}
title={props.title}
value={props.status}
/>
)}
<div class={bem('content')}>{slots.default}</div>
{slots.footer && <div class={[bem('footer'), 'van-hairline--top']}>{slots.footer}</div>}
</CellGroup>
);
}
},
render(h) {
const slots = this.$slots;
return (
<CellGroup class={bem()}>
{slots.header || (
<Cell
class={bem('header')}
icon={this.icon}
label={this.desc}
title={this.title}
value={this.status}
/>
)}
<div class={bem('content')}>{slots.default}</div>
{slots.footer && <div class={[bem('footer'), 'van-hairline--top']}>{slots.footer}</div>}
</CellGroup>
);
}
});
true
);

View File

@ -1,63 +1,66 @@
import { use } from '../utils';
import { use, noop } from '../utils';
import Button from '../button';
const [sfc, bem, t] = use('submit-bar');
export default sfc({
props: {
tip: String,
label: String,
loading: Boolean,
disabled: Boolean,
buttonText: String,
price: {
type: Number,
default: null
export default sfc(
{
props: {
tip: String,
label: String,
loading: Boolean,
disabled: Boolean,
buttonText: String,
price: {
type: Number,
default: null
},
currency: {
type: String,
default: '¥'
},
buttonType: {
type: String,
default: 'danger'
}
},
currency: {
type: String,
default: '¥'
},
buttonType: {
type: String,
default: 'danger'
render(h, context) {
const { props, listeners } = context;
const { tip, price } = props;
const slots = context.slots();
const hasPrice = typeof price === 'number';
return (
<div class={bem()} {...context.data}>
{slots.top}
{(slots.tip || tip) && (
<div class={bem('tip')}>
{tip}
{slots.tip}
</div>
)}
<div class={bem('bar')}>
{slots.default}
<div class={bem('text')}>
{hasPrice && [
<span>{props.label || t('label')}</span>,
<span class={bem('price')}>{`${props.currency} ${(price / 100).toFixed(2)}`}</span>
]}
</div>
<Button
square
size="large"
type={props.buttonType}
loading={props.loading}
disabled={props.disabled}
text={props.loading ? '' : props.buttonText}
onClick={listeners.submit || noop}
/>
</div>
</div>
);
}
},
render(h) {
const { tip, price, $slots } = this;
const hasPrice = typeof price === 'number';
return (
<div class={bem()}>
{$slots.top}
{($slots.tip || tip) && (
<div class={bem('tip')}>
{tip}
{$slots.tip}
</div>
)}
<div class={bem('bar')}>
{$slots.default}
<div class={bem('text')}>
{hasPrice && [
<span>{this.label || t('label')}</span>,
<span class={bem('price')}>{`${this.currency} ${(price / 100).toFixed(2)}`}</span>
]}
</div>
<Button
square
size="large"
type={this.buttonType}
loading={this.loading}
disabled={this.disabled}
text={this.loading ? '' : this.buttonText}
onClick={() => {
this.$emit('submit');
}}
/>
</div>
</div>
);
}
});
true
);

View File

@ -1,6 +1,6 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`submit 1`] = `
exports[`disable submit 1`] = `
<div class="van-submit-bar">
<div class="van-submit-bar__bar">
<div class="van-submit-bar__text"><span>合计:</span><span class="van-submit-bar__price">¥ 0.00</span></div><button disabled="disabled" class="van-button van-button--danger van-button--large van-button--square van-button--disabled"><span class="van-button__text"></span></button>

View File

@ -2,10 +2,30 @@ import SubmitBar from '..';
import { mount } from '../../../test/utils';
test('submit', () => {
const submit = jest.fn();
const wrapper = mount(SubmitBar, {
propsData: {
price: 0.01,
disabled: true
context: {
props: {
price: 0.01
},
on: { submit }
}
});
const button = wrapper.find('.van-button');
button.trigger('click');
expect(submit.mock.calls[0]).toBeTruthy();
});
test('disable submit', () => {
const submit = jest.fn();
const wrapper = mount(SubmitBar, {
context: {
props: {
price: 0.01,
disabled: true
},
on: { submit }
}
});
@ -14,10 +34,5 @@ test('submit', () => {
// disabled
const button = wrapper.find('.van-button');
button.trigger('click');
expect(wrapper.emitted('submit')).toBeFalsy();
// submit
wrapper.vm.disabled = false;
button.trigger('click');
expect(wrapper.emitted('submit')).toBeTruthy();
expect(submit.mock.calls[0]).toBeFalsy();
});

View File

@ -5,34 +5,34 @@ import SwitchMixin from '../mixins/switch';
const [sfc, bem] = use('switch-cell');
export default sfc({
mixins: [SwitchMixin],
export default sfc(
{
mixins: [SwitchMixin],
props: {
title: String,
border: Boolean,
size: {
type: String,
default: '24px'
props: {
title: String,
border: Boolean,
size: {
type: String,
default: '24px'
}
},
render(h, context) {
const { props } = context;
return (
<Cell
center
title={props.title}
border={props.border}
style={context.style}
class={[bem(), context.class, context.staticClass]}
>
<Switch {...{ props, on: context.listeners }} />
</Cell>
);
}
},
watch: {
value() {
this.$emit('change', this.value);
}
},
render(h) {
return (
<Cell center title={this.title} border={this.border} class={bem()}>
<Switch
{...{ props: this.$props }}
onInput={value => {
this.$emit('input', value);
}}
/>
</Cell>
);
}
});
true
);

View File

@ -1,13 +1,20 @@
import SwitchCell from '..';
import { mount } from '../../../test/utils';
test('emit event', () => {
const wrapper = mount(SwitchCell);
test('change event', () => {
const onChange = jest.fn();
const wrapper = mount(SwitchCell, {
context: {
on: {
change: onChange
}
}
});
wrapper.vm.$on('input', value => {
wrapper.setProps({ value });
});
wrapper.find('.van-switch').trigger('click');
expect(wrapper.emitted('change')).toBeTruthy();
expect(onChange.mock.calls[0]).toBeTruthy();
});

View File

@ -9,51 +9,45 @@ const COLOR_MAP = {
success: GREEN
};
export default sfc({
props: {
size: String,
type: String,
mark: Boolean,
color: String,
plain: Boolean,
round: Boolean,
textColor: String
},
export default sfc(
{
props: {
size: String,
type: String,
mark: Boolean,
color: String,
plain: Boolean,
round: Boolean,
textColor: String
},
computed: {
style() {
const color = this.color || COLOR_MAP[this.type] || GRAY_DARK;
const key = this.plain ? 'color' : 'backgroundColor';
render(h, context) {
const { props } = context;
const { mark, plain, round, size } = context.props;
const color = props.color || COLOR_MAP[props.type] || GRAY_DARK;
const key = plain ? 'color' : 'backgroundColor';
const style = { [key]: color };
if (this.textColor) {
style.color = this.textColor;
if (props.textColor) {
style.color = props.textColor;
}
return style;
return (
<span
style={style}
class={[
bem({ mark, plain, round, [size]: size }),
{
'van-hairline--surround': plain
}
]}
{...context.data}
>
{context.children}
</span>
);
}
},
render(h) {
const {
mark,
plain,
round,
size
} = this;
return (
<span
class={[
bem({ mark, plain, round, [size]: size }),
{
'van-hairline--surround': plain
}
]}
style={this.style}
>
{this.$slots.default}
</span>
);
}
});
true
);

View File

@ -4,6 +4,8 @@ export { use, useSlots } from './use';
export const isServer = Vue.prototype.$isServer;
export function noop() {}
export function isDef(value) {
return value !== undefined && value !== null;
}