[improvement] rename packages dir to src (#3659)
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<demo-section>
|
||||
<demo-block :title="$t('basicUsage')">
|
||||
<van-search
|
||||
v-model="value"
|
||||
:placeholder="$t('placeholder')"
|
||||
/>
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="$t('title2')">
|
||||
<form action="/">
|
||||
<van-search
|
||||
v-model="value"
|
||||
:placeholder="$t('placeholder')"
|
||||
show-action
|
||||
@search="onSearch"
|
||||
@cancel="onCancel"
|
||||
/>
|
||||
</form>
|
||||
</demo-block>
|
||||
|
||||
<demo-block :title="$t('title3')">
|
||||
<van-search
|
||||
v-model="value"
|
||||
:placeholder="$t('placeholder')"
|
||||
show-action
|
||||
shape="round"
|
||||
:label="$t('label')"
|
||||
@search="onSearch"
|
||||
>
|
||||
<template #action>
|
||||
<div @click="onSearch">{{ $t('search') }}</div>
|
||||
</template>
|
||||
</van-search>
|
||||
</demo-block>
|
||||
</demo-section>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
i18n: {
|
||||
'zh-CN': {
|
||||
title2: '监听对应事件',
|
||||
title3: '高级用法',
|
||||
placeholder: '请输入搜索关键词',
|
||||
label: '地址'
|
||||
},
|
||||
'en-US': {
|
||||
title2: 'Listen to Events',
|
||||
title3: 'Advanced Usage',
|
||||
placeholder: 'Placeholder',
|
||||
label: 'Address'
|
||||
}
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
value: ''
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
onSearch() {
|
||||
this.$toast(this.value);
|
||||
},
|
||||
onCancel() {
|
||||
this.$toast(this.$t('cancel'));
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,99 @@
|
||||
# Search
|
||||
|
||||
### Install
|
||||
|
||||
``` javascript
|
||||
import { Search } from 'vant';
|
||||
|
||||
Vue.use(Search);
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```html
|
||||
<van-search placeholder="Placeholder" v-model="value" />
|
||||
```
|
||||
|
||||
```javascript
|
||||
export default {
|
||||
data() {
|
||||
value: ''
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Listen to Events
|
||||
|
||||
`search` event will be triggered when click the search button on the keyboard.
|
||||
|
||||
`cancel` event will be triggered when click the cancel button.
|
||||
|
||||
Tips: There will be a search button on the keyboard when Search is inside a form in iOS.
|
||||
|
||||
```html
|
||||
<form action="/">
|
||||
<van-search
|
||||
v-model="value"
|
||||
placeholder="Placeholder"
|
||||
show-action
|
||||
@search="onSearch"
|
||||
@cancel="onCancel"
|
||||
/>
|
||||
</form>
|
||||
```
|
||||
|
||||
### Custom Button
|
||||
|
||||
Use `action` slot to custom right button, `cancel` event will no longer be triggered when use this slot
|
||||
|
||||
```html
|
||||
<van-search
|
||||
v-model="value"
|
||||
show-action
|
||||
shape="round"
|
||||
@search="onSearch"
|
||||
>
|
||||
<div slot="action" @click="onSearch">Search</div>
|
||||
</van-search>
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Props
|
||||
|
||||
Search support all native properties of input tag,such as `maxlength`、`placeholder`、`autofocus`
|
||||
|
||||
| Attribute | Description | Type | Default |
|
||||
|------|------|------|------|
|
||||
| label | Search label | `String` | - |
|
||||
| shape | Can be set to `round` | `String` | `square` |
|
||||
| background | Background color | `String` | `#f2f2f2` |
|
||||
| clearable | Whether to be clearable | `Boolean` | `true` |
|
||||
| show-action | Whether to show right button | `Boolean` | `false` |
|
||||
| disabled | Whether to disable field | `Boolean` | `false` |
|
||||
| readonly | Whether to be readonly | `Boolean` | `false` |
|
||||
| error | Whether to show error info | `Boolean` | `false` |
|
||||
| input-align | Input text align, can be set to `center` `right` | `String` | `left` |
|
||||
| left-icon | Left icon name | `String` | `search` |
|
||||
| right-icon | Right icon name | `String` | - |
|
||||
|
||||
### Events
|
||||
|
||||
Search support all native events of input tag,such as `focus`、`blur`、`keypress`
|
||||
|
||||
| Event | Description | Arguments |
|
||||
|------|------|------|
|
||||
| cancel | Triggered when click cancel button | - |
|
||||
| search | Triggered when confirm search | - |
|
||||
| clear | Triggered when click clear icon | - |
|
||||
|
||||
### Slots
|
||||
|
||||
| Name | Description |
|
||||
|------|------|
|
||||
| label | Custom Search label |
|
||||
| action | Custom right button, displayed when `showAction` is true |
|
||||
| left-icon | Custom left icon |
|
||||
| right-icon | Custom right icon |
|
||||
@@ -0,0 +1,61 @@
|
||||
@import '../style/var';
|
||||
|
||||
.van-search {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
padding: @search-padding;
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
padding-left: 10px;
|
||||
background-color: @search-background-color;
|
||||
border-radius: 2px;
|
||||
|
||||
&--round {
|
||||
border-radius: @search-input-height / 2;
|
||||
}
|
||||
}
|
||||
|
||||
&__label {
|
||||
padding: @search-label-padding;
|
||||
color: @search-label-color;
|
||||
font-size: @search-label-font-size;
|
||||
line-height: @search-input-height;
|
||||
}
|
||||
|
||||
.van-cell {
|
||||
flex: 1;
|
||||
padding: 5px 10px 5px 0;
|
||||
background-color: transparent;
|
||||
|
||||
&__left-icon {
|
||||
color: @search-left-icon-color;
|
||||
}
|
||||
}
|
||||
|
||||
&--show-action {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
input {
|
||||
&::-webkit-search-decoration,
|
||||
&::-webkit-search-cancel-button,
|
||||
&::-webkit-search-results-button,
|
||||
&::-webkit-search-results-decoration {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__action {
|
||||
padding: @search-action-padding;
|
||||
color: @search-action-text-color;
|
||||
font-size: @search-action-font-size;
|
||||
line-height: @search-input-height;
|
||||
|
||||
&:active {
|
||||
background-color: @active-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
import { createNamespace } from '../utils';
|
||||
import { inherit, emit } from '../utils/functional';
|
||||
import { preventDefault } from '../utils/dom/event';
|
||||
import Field from '../field';
|
||||
|
||||
// Types
|
||||
import { CreateElement, RenderContext } from 'vue/types';
|
||||
import { DefaultSlots, ScopedSlot } from '../utils/types';
|
||||
|
||||
const [createComponent, bem, t] = createNamespace('search');
|
||||
|
||||
export type SearchProps = {
|
||||
shape: string;
|
||||
value?: string;
|
||||
label?: string;
|
||||
leftIcon: string;
|
||||
rightIcon?: string;
|
||||
clearable: boolean;
|
||||
background: string;
|
||||
showAction?: boolean;
|
||||
};
|
||||
|
||||
export type SearchSlots = DefaultSlots & {
|
||||
label?: ScopedSlot;
|
||||
action?: ScopedSlot;
|
||||
'left-icon'?: ScopedSlot;
|
||||
'right-icon'?: ScopedSlot;
|
||||
};
|
||||
|
||||
export type SearchEvents = {
|
||||
onCancel?(): void;
|
||||
onInput?(value: string): void;
|
||||
onSearch?(value: string): void;
|
||||
onKeypress?(event: KeyboardEvent): void;
|
||||
};
|
||||
|
||||
function Search(
|
||||
h: CreateElement,
|
||||
props: SearchProps,
|
||||
slots: SearchSlots,
|
||||
ctx: RenderContext<SearchProps>
|
||||
) {
|
||||
function Label() {
|
||||
if (slots.label || props.label) {
|
||||
return <div class={bem('label')}>{slots.label ? slots.label() : props.label}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
function Action() {
|
||||
if (!props.showAction) {
|
||||
return;
|
||||
}
|
||||
|
||||
function onCancel() {
|
||||
emit(ctx, 'input', '');
|
||||
emit(ctx, 'cancel');
|
||||
}
|
||||
|
||||
return (
|
||||
<div class={bem('action')}>
|
||||
{slots.action ? slots.action() : <div onClick={onCancel}>{t('cancel')}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const fieldData = {
|
||||
attrs: ctx.data.attrs,
|
||||
on: {
|
||||
...ctx.listeners,
|
||||
input(value: string) {
|
||||
emit(ctx, 'input', value);
|
||||
},
|
||||
keypress(event: KeyboardEvent) {
|
||||
// press enter
|
||||
if (event.keyCode === 13) {
|
||||
preventDefault(event);
|
||||
emit(ctx, 'search', props.value);
|
||||
}
|
||||
emit(ctx, 'keypress', event);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const inheritData = inherit(ctx);
|
||||
delete inheritData.attrs;
|
||||
|
||||
return (
|
||||
<div
|
||||
class={bem({ 'show-action': props.showAction })}
|
||||
style={{ background: props.background }}
|
||||
{...inheritData}
|
||||
>
|
||||
<div class={bem('content', props.shape)}>
|
||||
{Label()}
|
||||
<Field
|
||||
type="search"
|
||||
border={false}
|
||||
value={props.value}
|
||||
leftIcon={props.leftIcon}
|
||||
rightIcon={props.rightIcon}
|
||||
clearable={props.clearable}
|
||||
scopedSlots={{
|
||||
'left-icon': slots['left-icon'],
|
||||
'right-icon': slots['right-icon']
|
||||
}}
|
||||
{...fieldData}
|
||||
/>
|
||||
</div>
|
||||
{Action()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Search.props = {
|
||||
value: String,
|
||||
label: String,
|
||||
rightIcon: String,
|
||||
showAction: Boolean,
|
||||
shape: {
|
||||
type: String,
|
||||
default: 'square'
|
||||
},
|
||||
clearable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
background: {
|
||||
type: String,
|
||||
default: '#fff'
|
||||
},
|
||||
leftIcon: {
|
||||
type: String,
|
||||
default: 'search'
|
||||
}
|
||||
};
|
||||
|
||||
export default createComponent<SearchProps, SearchEvents, SearchSlots>(Search);
|
||||
@@ -0,0 +1,54 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders demo correctly 1`] = `
|
||||
<div>
|
||||
<div>
|
||||
<div class="van-search" style="background: rgb(255, 255, 255);">
|
||||
<div class="van-search__content van-search__content--square">
|
||||
<div class="van-cell van-cell--borderless van-field">
|
||||
<div class="van-field__left-icon"><i class="van-icon van-icon-search">
|
||||
<!----></i></div>
|
||||
<div class="van-cell__value van-cell__value--alone">
|
||||
<div class="van-field__body"><input type="search" placeholder="请输入搜索关键词" class="van-field__control"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<form action="/">
|
||||
<div class="van-search van-search--show-action" style="background: rgb(255, 255, 255);">
|
||||
<div class="van-search__content van-search__content--square">
|
||||
<div class="van-cell van-cell--borderless van-field">
|
||||
<div class="van-field__left-icon"><i class="van-icon van-icon-search">
|
||||
<!----></i></div>
|
||||
<div class="van-cell__value van-cell__value--alone">
|
||||
<div class="van-field__body"><input type="search" placeholder="请输入搜索关键词" class="van-field__control"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-search__action">
|
||||
<div>取消</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div>
|
||||
<div class="van-search van-search--show-action" style="background: rgb(255, 255, 255);">
|
||||
<div class="van-search__content van-search__content--round">
|
||||
<div class="van-search__label">地址</div>
|
||||
<div class="van-cell van-cell--borderless van-field">
|
||||
<div class="van-field__left-icon"><i class="van-icon van-icon-search">
|
||||
<!----></i></div>
|
||||
<div class="van-cell__value van-cell__value--alone">
|
||||
<div class="van-field__body"><input type="search" placeholder="请输入搜索关键词" class="van-field__control"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="van-search__action">
|
||||
<div>搜索</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,63 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`left-icon prop 1`] = `
|
||||
<div class="van-search" style="background: rgb(255, 255, 255);">
|
||||
<div class="van-search__content van-search__content--square">
|
||||
<div class="van-cell van-cell--borderless van-field">
|
||||
<div class="van-field__left-icon"><i class="van-icon van-icon-setting-o">
|
||||
<!----></i></div>
|
||||
<div class="van-cell__value van-cell__value--alone">
|
||||
<div class="van-field__body"><input type="search" class="van-field__control"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`render label slot 1`] = `
|
||||
<div class="van-search" style="background: rgb(255, 255, 255);">
|
||||
<div class="van-search__content van-search__content--square">
|
||||
<div class="van-search__label">Custom Label</div>
|
||||
<div class="van-cell van-cell--borderless van-field">
|
||||
<div class="van-field__left-icon"><i class="van-icon van-icon-search">
|
||||
<!----></i></div>
|
||||
<div class="van-cell__value van-cell__value--alone">
|
||||
<div class="van-field__body"><input type="search" class="van-field__control"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`right-icon prop 1`] = `
|
||||
<div class="van-search" style="background: rgb(255, 255, 255);">
|
||||
<div class="van-search__content van-search__content--square">
|
||||
<div class="van-cell van-cell--borderless van-field">
|
||||
<div class="van-field__left-icon"><i class="van-icon van-icon-search">
|
||||
<!----></i></div>
|
||||
<div class="van-cell__value van-cell__value--alone">
|
||||
<div class="van-field__body"><input type="search" class="van-field__control">
|
||||
<div class="van-field__right-icon"><i class="van-icon van-icon-setting-o">
|
||||
<!----></i></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`right-icon slot 1`] = `
|
||||
<div class="van-search" style="background: rgb(255, 255, 255);">
|
||||
<div class="van-search__content van-search__content--square">
|
||||
<div class="van-cell van-cell--borderless van-field">
|
||||
<div class="van-field__left-icon"><i class="van-icon van-icon-search">
|
||||
<!----></i></div>
|
||||
<div class="van-cell__value van-cell__value--alone">
|
||||
<div class="van-field__body"><input type="search" class="van-field__control">
|
||||
<div class="van-field__right-icon">Custom Right Icon</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,4 @@
|
||||
import Demo from '../demo';
|
||||
import demoTest from '../../../test/demo-test';
|
||||
|
||||
demoTest(Demo);
|
||||
@@ -0,0 +1,109 @@
|
||||
/* eslint-disable object-shorthand */
|
||||
import Search from '..';
|
||||
import { mount } from '../../../test/utils';
|
||||
|
||||
test('listen input event', () => {
|
||||
const onInput = jest.fn();
|
||||
const wrapper = mount(Search, {
|
||||
context: {
|
||||
on: {
|
||||
input: onInput
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const input = wrapper.find('input');
|
||||
input.element.value = '1';
|
||||
input.trigger('input');
|
||||
|
||||
expect(onInput).toHaveBeenCalledWith('1');
|
||||
});
|
||||
|
||||
test('cancel search', () => {
|
||||
const onInput = jest.fn();
|
||||
const onCancel = jest.fn();
|
||||
|
||||
const wrapper = mount(Search, {
|
||||
propsData: {
|
||||
value: 'test',
|
||||
showAction: true
|
||||
},
|
||||
context: {
|
||||
on: {
|
||||
input: onInput,
|
||||
cancel: onCancel
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const cancel = wrapper.find('.van-search__action div');
|
||||
cancel.trigger('click');
|
||||
|
||||
expect(onInput).toHaveBeenCalledWith('');
|
||||
expect(onCancel).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('emit a search event', () => {
|
||||
const onSearch = jest.fn();
|
||||
const onKeypress = jest.fn();
|
||||
|
||||
const wrapper = mount(Search, {
|
||||
context: {
|
||||
on: {
|
||||
search: onSearch,
|
||||
keypress: onKeypress
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const input = wrapper.find('input');
|
||||
input.trigger('keypress.enter');
|
||||
input.trigger('keypress.a');
|
||||
|
||||
expect(onSearch).toHaveBeenCalled();
|
||||
expect(onKeypress).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test('render label slot', () => {
|
||||
const wrapper = mount(Search, {
|
||||
scopedSlots: {
|
||||
label() {
|
||||
return 'Custom Label';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('left-icon prop', () => {
|
||||
const wrapper = mount(Search, {
|
||||
propsData: {
|
||||
leftIcon: 'setting-o'
|
||||
}
|
||||
});
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('right-icon prop', () => {
|
||||
const wrapper = mount(Search, {
|
||||
propsData: {
|
||||
rightIcon: 'setting-o'
|
||||
}
|
||||
});
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('right-icon slot', () => {
|
||||
const wrapper = mount(Search, {
|
||||
scopedSlots: {
|
||||
'right-icon'() {
|
||||
return 'Custom Right Icon';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
@@ -0,0 +1,92 @@
|
||||
# Search 搜索
|
||||
|
||||
### 引入
|
||||
|
||||
``` javascript
|
||||
import { Search } from 'vant';
|
||||
|
||||
Vue.use(Search);
|
||||
```
|
||||
|
||||
## 代码演示
|
||||
|
||||
### 基础用法
|
||||
|
||||
`van-search` 中,v-model 用于控制搜索框中的文字。background 可以自定义搜索框外部背景色。
|
||||
|
||||
```html
|
||||
<van-search placeholder="请输入搜索关键词" v-model="value" />
|
||||
```
|
||||
|
||||
### 监听对应事件
|
||||
|
||||
`van-search` 提供了 search 和 cancel 事件。search 事件在用户点击键盘上的 搜索/回车 按钮触发。cancel 事件在用户点击搜索框右侧取消按钮时触发
|
||||
|
||||
Tips: 在 `van-search` 外层增加 form 标签,并且 action 不为空,即可在 IOS 弹出的输入法中显示搜索按钮
|
||||
|
||||
```html
|
||||
<form action="/">
|
||||
<van-search
|
||||
v-model="value"
|
||||
placeholder="请输入搜索关键词"
|
||||
show-action
|
||||
@search="onSearch"
|
||||
@cancel="onCancel"
|
||||
/>
|
||||
</form>
|
||||
```
|
||||
|
||||
### 自定义行动按钮
|
||||
|
||||
`van-search` 支持自定义右侧取消按钮,使用名字为 action 的插槽即可。使用此插槽以后,原有的 cancel 事件不再生效。
|
||||
|
||||
```html
|
||||
<van-search
|
||||
v-model="value"
|
||||
placeholder="请输入搜索关键词"
|
||||
show-action
|
||||
shape="round"
|
||||
@search="onSearch"
|
||||
>
|
||||
<div slot="action" @click="onSearch">搜索</div>
|
||||
</van-search>
|
||||
```
|
||||
|
||||
## API
|
||||
|
||||
### Props
|
||||
|
||||
Search 默认支持 Input 标签所有的原生属性,比如 `maxlength`、`placeholder`、`autofocus` 等
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 | 版本 |
|
||||
|------|------|------|------|------|
|
||||
| label | 搜索框左侧文本 | `String` | - | 1.6.6 |
|
||||
| shape | 形状,可选值为 `round` | `String` | `square` | 1.6.6 |
|
||||
| background | 搜索框背景色 | `String` | `#f2f2f2` | - |
|
||||
| clearable | 是否启用清除控件 | `Boolean` | `true` | 2.0.0 |
|
||||
| show-action | 是否在搜索框右侧显示取消按钮 | `Boolean` | `false` | - |
|
||||
| disabled | 是否禁用输入框 | `Boolean` | `false` | - |
|
||||
| readonly | 是否将输入框设为只读 | `Boolean` | `false` | - |
|
||||
| error | 是否将输入内容标红 | `Boolean` | `false` | - |
|
||||
| input-align | 输入框内容对齐方式,可选值为 `center` `right` | `String` | `left` | - |
|
||||
| left-icon | 输入框左侧图标名称或图片链接,可选值见 Icon 组件 | `String` | `search` | 2.0.0 |
|
||||
| right-icon | 输入框右侧图标名称或图片链接,可选值见 Icon 组件 | `String` | - | 2.0.0 |
|
||||
|
||||
### Events
|
||||
|
||||
Search 默认支持 Input 标签所有的原生事件,如 `focus`、`blur`、`keypress` 等
|
||||
|
||||
| 事件名 | 说明 | 回调参数 |
|
||||
|------|------|------|
|
||||
| cancel | 取消搜索 | - |
|
||||
| search | 确定搜索 | - |
|
||||
| clear | 点击清除按钮后触发 | - |
|
||||
|
||||
### Slots
|
||||
|
||||
| 名称 | 说明 |
|
||||
|------|------|
|
||||
| label | 自定义搜索框左侧文本 |
|
||||
| action | 自定义搜索框右侧按钮,需要在`showAction`为 true 时才会显示 |
|
||||
| left-icon | 自定义输入框左侧图标 |
|
||||
| right-icon | 自定义输入框右侧图标 |
|
||||
Reference in New Issue
Block a user