Merge branch '2.x' into dev

This commit is contained in:
chenjiahan 2020-11-20 20:46:10 +08:00
commit 27706b023b
17 changed files with 1382 additions and 5 deletions

View File

@ -75,7 +75,7 @@ module.exports = {
{
loader: 'less-loader',
options: {
// 若使用 less-loader@5,请移除 lessOptions 这一级,直接配置选项。
// 若 less-loader 版本小于 6.0,请移除 lessOptions 这一级,直接配置选项。
lessOptions: {
modifyVars: {
// 直接覆盖变量
@ -101,7 +101,7 @@ module.exports = {
css: {
loaderOptions: {
less: {
// 若使用 less-loader@5,请移除 lessOptions 这一级,直接配置选项。
// 若 less-loader 版本小于 6.0,请移除 lessOptions 这一级,直接配置选项。
lessOptions: {
modifyVars: {
// 直接覆盖变量

View File

@ -58,6 +58,7 @@
"license": "MIT",
"dependencies": {
"@babel/runtime": "7.x",
"@popperjs/core": "^2.5.4",
"@vant/icons": "1.4.0",
"@vant/use": "^0.1.0",
"vue-lazyload": "1.2.3"

195
src/popover/README.md Normal file
View File

@ -0,0 +1,195 @@
# Popover
### Install
```js
import Vue from 'vue';
import { Popover } from 'vant';
Vue.use(Popover);
```
## Usage
### Basic Usage
```html
<van-popover v-model="showPopover" :actions="actions" @select="onSelect">
<template #reference>
<van-button type="primary" @click="showPopover = true">
Light Theme
</van-button>
</template>
</van-popover>
```
```js
import { Toast } from 'vant';
export default {
data() {
return {
showPopover: false,
actions: [
{ text: 'Option 1' },
{ text: 'Option 2' },
{ text: 'Option 3' },
],
};
},
methods: {
onSelect(action) {
Toast(action.text);
},
},
};
```
### Dark theme
Using the `theme` prop to change the style of Popover.
```html
<van-popover v-model="showPopover" theme="dark" :actions="actions">
<template #reference>
<van-button type="primary" @click="showPopover = true">
Dark Theme
</van-button>
</template>
</van-popover>
```
```js
export default {
data() {
return {
showPopover: false,
actions: [
{ text: 'Option 1' },
{ text: 'Option 2' },
{ text: 'Option 3' },
],
};
},
};
```
### Placement
```html
<van-popover placement="top" />
```
`placement` supports the following values:
```bash
top # Top middle
top-start # Top left
top-end # Top right
left # Left middle
left-start # Left top
left-end # Left bottom
right # Right middle
right-start # Right top
right-end # Right bottom
bottom # Bottom middle
bottom-start # Bottom left
bottom-end # Bottom right
```
### Show Icon
```html
<van-popover v-model="showPopover" :actions="actions">
<template #reference>
<van-button type="primary" @click="showPopover = true">
Show Icon
</van-button>
</template>
</van-popover>
```
```js
export default {
data() {
return {
showPopover: false,
actions: [
{ text: 'Option 1', icon: 'add-o' },
{ text: 'Option 2', icon: 'music-o' },
{ text: 'Option 3', icon: 'more-o' },
],
};
},
};
```
### Disable Action
Using the `disabled` option to disable an action.
```html
<van-popover v-model="showPopover" :actions="actions">
<template #reference>
<van-button type="primary" @click="showPopover = true">
Disable Action
</van-button>
</template>
</van-popover>
```
```js
export default {
data() {
return {
showPopover: false,
actions: [
{ text: 'Option 1', disabled: true },
{ text: 'Option 2', disabled: true },
{ text: 'Option 3' },
],
};
},
};
```
## API
### Props
| Attribute | Description | Type | Default |
| --- | --- | --- | --- |
| v-model | Whether to show Popover | _boolean_ | `false` |
| actions | Actions | _Action[]_ | `[]` |
| placement | Placement | _string_ | `bottom` |
| theme | Themecan be set to `dark` | _string_ | `light` |
| overlay | Whether to show overlay | _boolean_ | `false` |
| close-on-click-action | Whether to close when clicking action | _boolean_ | `true` |
| close-on-click-outside | Whether to close when clicking outside | _boolean_ | `true` |
| get-container | Return the mount node for Popover | _string \| () => Element_ | `body` |
### Data Structure of Action
| Key | Description | Type |
| --------- | ----------------------- | --------- |
| text | Action Text | _string_ |
| icon | Icon | _string_ |
| disabled | Whether to be disabled | _boolean_ |
| className | className of the option | _any_ |
### Events
| Event | Description | Arguments |
| ------ | -------------------------------- | ------------------------------- |
| select | Triggered when clicking action | _action: Action, index: number_ |
| open | Triggered when opening Popover | - |
| close | Triggered when closing Popover | - |
| opened | Triggered when Popover is opened | - |
| closed | Triggered when Popover is closed | - |
### Slots
| Name | Description |
| --------- | ----------------- |
| default | Custom content |
| reference | Reference Element |

239
src/popover/README.zh-CN.md Normal file
View File

@ -0,0 +1,239 @@
# Popover 气泡弹出框
### 介绍
弹出式的气泡菜单。
### 引入
```js
import Vue from 'vue';
import { Popover } from 'vant';
Vue.use(Popover);
```
## 代码演示
### 基础用法
当 Popover 弹出时,会基于 `reference` 插槽的内容进行定位。
```html
<van-popover v-model="showPopover" :actions="actions" @select="onSelect">
<template #reference>
<van-button type="primary" @click="showPopover = true">
浅色风格
</van-button>
</template>
</van-popover>
```
```js
import { Toast } from 'vant';
export default {
data() {
return {
showPopover: false,
// 通过 actions 属性来定义菜单选项
actions: [{ text: '选项一' }, { text: '选项二' }, { text: '选项三' }],
};
},
methods: {
onSelect(action) {
Toast(action.text);
},
},
};
```
### 深色风格
Popover 支持浅色和深色两种风格,默认为浅色风格,将 `theme` 属性设置为 `dark` 可切换为深色风格。
```html
<van-popover v-model="showPopover" theme="dark" :actions="actions">
<template #reference>
<van-button type="primary" @click="showPopover = true">
深色风格
</van-button>
</template>
</van-popover>
```
```js
export default {
data() {
return {
showPopover: false,
actions: [{ text: '选项一' }, { text: '选项二' }, { text: '选项三' }],
};
},
};
```
### 弹出位置
通过 `placement` 属性来控制气泡的弹出位置。
```html
<van-popover placement="top" />
```
`placement` 支持以下值:
```bash
top # 顶部中间位置
top-start # 顶部左侧位置
top-end # 顶部右侧位置
left # 左侧中间位置
left-start # 左侧上方位置
left-end # 左侧下方位置
right # 右侧中间位置
right-start # 右侧上方位置
right-end # 右侧下方位置
bottom # 底部中间位置
bottom-start # 底部左侧位置
bottom-end # 底部右侧位置
```
### 展示图标
`actions` 数组中,可以通过 `icon` 字段来定义选项的图标,支持传入[图标名称](#/zh-CN/icon)或图片链接。
```html
<van-popover v-model="showPopover" :actions="actions">
<template #reference>
<van-button type="primary" @click="showPopover = true">
展示图标
</van-button>
</template>
</van-popover>
```
```js
export default {
data() {
return {
showPopover: false,
actions: [
{ text: '选项一', icon: 'add-o' },
{ text: '选项二', icon: 'music-o' },
{ text: '选项三', icon: 'more-o' },
],
};
},
};
```
### 禁用选项
`actions` 数组中,可以通过 `disabled` 字段来禁用某个选项。
```html
<van-popover v-model="showPopover" :actions="actions">
<template #reference>
<van-button type="primary" @click="showPopover = true">
禁用选项
</van-button>
</template>
</van-popover>
```
```js
export default {
data() {
return {
showPopover: false,
actions: [
{ text: '选项一', disabled: true },
{ text: '选项二', disabled: true },
{ text: '选项三' },
],
};
},
};
```
### 自定义内容
通过默认插槽,可以在 Popover 内部放置任意内容。
```html
<van-popover v-model="showPopover">
<van-grid
square
clickable
:border="false"
column-num="3"
style="width: 240px;"
>
<van-grid-item
v-for="i in 6"
:key="i"
text="选项"
icon="photo-o"
@click="showPopover = false"
/>
</van-grid>
<template #reference>
<van-button type="primary" @click="showPopover = true">
自定义内容
</van-button>
</template>
</van-popover>
```
```js
export default {
data() {
return {
showPopover: false,
};
},
};
```
## API
### Props
| 参数 | 说明 | 类型 | 默认值 |
| --- | --- | --- | --- |
| v-model | 是否展示气泡弹出层 | _boolean_ | `false` |
| actions | 选项列表 | _Action[]_ | `[]` |
| placement | 弹出位置 | _string_ | `bottom` |
| theme | 主题风格,可选值为 `dark` | _string_ | `light` |
| overlay | 是否显示遮罩层 | _boolean_ | `false` |
| close-on-click-action | 是否在点击选项后关闭 | _boolean_ | `true` |
| close-on-click-outside | 是否在点击外部元素后关闭菜单 | _boolean_ | `true` |
| get-container | 指定挂载的节点,[用法示例](#/zh-CN/popup#zhi-ding-gua-zai-wei-zhi) | _string \| () => Element_ | `body` |
### Action 数据结构
`actions` 属性是一个由对象构成的数组,数组中的每个对象配置一列,对象可以包含以下值:
| 键名 | 说明 | 类型 |
| --- | --- | --- |
| text | 选项文字 | _string_ |
| icon | 文字左侧的图标,支持传入[图标名称](#/zh-CN/icon)或图片链接 | _string_ |
| disabled | 是否为禁用状态 | _boolean_ |
| className | 为对应选项添加额外的类名 | _any_ |
### Events
| 事件名 | 说明 | 回调参数 |
| ------ | ------------------------ | ------------------------------- |
| select | 点击选项时触发 | _action: Action, index: number_ |
| open | 打开菜单时触发 | - |
| close | 关闭菜单时触发 | - |
| opened | 打开菜单且动画结束后触发 | - |
| closed | 关闭菜单且动画结束后触发 | - |
### Slots
| 名称 | 说明 |
| --------- | --------------------------- |
| default | 自定义菜单内容 |
| reference | 触发 Popover 显示的元素内容 |

249
src/popover/demo/index.vue Normal file
View File

@ -0,0 +1,249 @@
<template>
<demo-section>
<demo-block :title="t('basicUsage')">
<van-popover
v-model="show.lightTheme"
:actions="t('actions')"
placement="bottom-start"
style="margin-left: 16px;"
@select="onSelect"
>
<template #reference>
<van-button type="primary" @click="show.lightTheme = true">
{{ t('lightTheme') }}
</van-button>
</template>
</van-popover>
<van-popover
v-model="show.darkTheme"
theme="dark"
:actions="t('actions')"
style="margin-left: 16px;"
@select="onSelect"
>
<template #reference>
<van-button type="primary" @click="show.darkTheme = true">
{{ t('darkTheme') }}
</van-button>
</template>
</van-popover>
</demo-block>
<demo-block :title="t('placement')">
<van-field
is-link
readonly
name="picker"
:label="t('choosePlacement')"
@click="showPicker = true"
/>
<van-popup
v-model="showPicker"
round
position="bottom"
get-container="body"
>
<div class="demo-popover-box">
<van-popover
v-model="show.placement"
theme="dark"
:actions="t('shortActions')"
:placement="currentPlacement"
@select="onSelect"
>
<template #reference>
<div class="demo-popover-refer" />
</template>
</van-popover>
</div>
<van-picker :columns="placements" @change="onPickerChange" />
</van-popup>
</demo-block>
<demo-block :title="t('actionOptions')">
<van-popover
v-model="show.showIcon"
:actions="t('actionsWithIcon')"
placement="bottom-start"
style="margin-left: 16px;"
@select="onSelect"
>
<template #reference>
<van-button type="primary" @click="show.showIcon = true">
{{ t('showIcon') }}
</van-button>
</template>
</van-popover>
<van-popover
v-model="show.disableAction"
:actions="t('actionsDisabled')"
style="margin-left: 16px;"
@select="onSelect"
>
<template #reference>
<van-button type="primary" @click="show.disableAction = true">
{{ t('disableAction') }}
</van-button>
</template>
</van-popover>
</demo-block>
<demo-block :title="t('customContent')">
<van-popover
v-model="show.customContent"
placement="top-start"
style="margin-left: 16px;"
@select="onSelect"
>
<van-grid
square
clickable
:border="false"
column-num="3"
style="width: 240px;"
>
<van-grid-item
v-for="i in 6"
:key="i"
icon="photo-o"
:text="t('option')"
@click="show.customContent = false"
/>
</van-grid>
<template #reference>
<van-button type="primary" @click="show.customContent = true">
{{ t('customContent') }}
</van-button>
</template>
</van-popover>
</demo-block>
</demo-section>
</template>
<script>
import Toast from '../../toast';
export default {
i18n: {
'zh-CN': {
actions: [{ text: '选项一' }, { text: '选项二' }, { text: '选项三' }],
shortActions: [{ text: '选项一' }, { text: '选项二' }],
actionsWithIcon: [
{ text: '选项一', icon: 'add-o' },
{ text: '选项二', icon: 'music-o' },
{ text: '选项三', icon: 'more-o' },
],
actionsDisabled: [
{ text: '选项一', disabled: true },
{ text: '选项二', disabled: true },
{ text: '选项三' },
],
showIcon: '展示图标',
placement: '弹出位置',
darkTheme: '深色风格',
lightTheme: '浅色风格',
showPopover: '点击弹出气泡',
actionOptions: '选项配置',
customContent: '自定义内容',
disableAction: '禁用选项',
choosePlacement: '选择弹出位置',
},
'en-US': {
actions: [
{ text: 'Option 1' },
{ text: 'Option 2' },
{ text: 'Option 3' },
],
shortActions: [{ text: 'Option 1' }, { text: 'Option 2' }],
actionsWithIcon: [
{ text: 'Option 1', icon: 'add-o' },
{ text: 'Option 2', icon: 'music-o' },
{ text: 'Option 3', icon: 'more-o' },
],
actionsDisabled: [
{ text: 'Option 1', disabled: true },
{ text: 'Option 2', disabled: true },
{ text: 'Option 3' },
],
showIcon: 'Show Icon',
placement: 'Placement',
darkTheme: 'Dark Theme',
lightTheme: 'Light Theme',
showPopover: 'Show Popover',
actionOptions: 'Action Options',
customContent: 'Custom Content',
disableAction: 'Disable Action',
choosePlacement: 'Placement',
},
},
data() {
return {
show: {
showIcon: false,
placement: false,
darkTheme: false,
lightTheme: false,
customContent: false,
disableAction: false,
},
showPicker: false,
currentPlacement: '',
placements: [
'top',
'top-start',
'top-end',
'left',
'left-start',
'left-end',
'right',
'right-start',
'right-end',
'bottom',
'bottom-start',
'bottom-end',
],
};
},
methods: {
onPickerChange(picker, value) {
setTimeout(() => {
this.show.placement = true;
this.currentPlacement = value;
});
},
onSelect(action) {
Toast(action.text);
},
},
};
</script>
<style lang="less">
@import '../../style/var';
.demo-popover {
&-refer {
width: 60px;
height: 60px;
background-color: @blue;
border-radius: 8px;
}
.van-field {
width: auto;
margin: 0 12px;
overflow: hidden;
border-radius: 8px;
}
&-box {
display: flex;
justify-content: center;
margin: 110px 0;
}
}
</style>

201
src/popover/index.js Normal file
View File

@ -0,0 +1,201 @@
import { createPopper } from '@popperjs/core/lib/popper-lite';
import offsetModifier from '@popperjs/core/lib/modifiers/offset';
import extendsHelper from '@babel/runtime/helpers/esm/extends';
import { createNamespace } from '../utils';
import { BORDER_BOTTOM } from '../utils/constant';
// Mixins
import { ClickOutsideMixin } from '../mixins/click-outside';
// Components
import Icon from '../icon';
import Popup from '../popup';
// add Object.assign polyfill for popper.js
// see: https://popper.js.org/docs/v2/browser-support/
/* istanbul ignore if */
if (!Object.assign) {
Object.assign = extendsHelper;
}
const [createComponent, bem] = createNamespace('popover');
export default createComponent({
mixins: [
ClickOutsideMixin({
event: 'touchstart',
method: 'onClickOutside',
}),
],
props: {
value: Boolean,
overlay: Boolean,
textColor: String,
backgroundColor: String,
offset: {
type: Array,
default: () => [0, 8],
},
theme: {
type: String,
default: 'light',
},
actions: {
type: Array,
default: () => [],
},
placement: {
type: String,
default: 'bottom',
},
getContainer: {
type: [String, Function],
default: 'body',
},
closeOnClickAction: {
type: Boolean,
default: true,
},
},
watch: {
value: 'updateLocation',
placement: 'updateLocation',
},
mounted() {
this.updateLocation();
},
beforeDestroy() {
if (this.popper) {
this.popper.destroy();
this.popper = null;
}
},
methods: {
createPopper() {
return createPopper(this.$refs.wrapper, this.$refs.popover.$el, {
placement: this.placement,
modifiers: [
{
name: 'computeStyles',
options: {
adaptive: false,
gpuAcceleration: false,
},
},
{
...offsetModifier,
options: {
offset: this.offset,
},
},
],
});
},
updateLocation() {
this.$nextTick(() => {
if (!this.value) {
return;
}
if (!this.popper) {
this.popper = this.createPopper();
} else {
this.popper.setOptions({
placement: this.placement,
});
}
});
},
renderAction(action, index) {
const { icon, text, disabled, className } = action;
return (
<div
class={[bem('action', { disabled, 'with-icon': icon }), className]}
onClick={() => this.onClickAction(action, index)}
>
{icon && <Icon name={icon} class={bem('action-icon')} />}
<div class={[bem('action-text'), BORDER_BOTTOM]}>{text}</div>
</div>
);
},
onToggle(value) {
this.$emit('input', value);
},
onTouchstart(event) {
event.stopPropagation();
this.$emit('touchstart', event);
},
onClickAction(action, index) {
if (action.disabled) {
return;
}
this.$emit('select', action, index);
if (this.closeOnClickAction) {
this.$emit('input', false);
}
},
onClickOutside() {
this.$emit('input', false);
},
onOpen() {
this.$emit('open');
},
/* istanbul ignore next */
onOpened() {
this.$emit('opened');
},
onClose() {
this.$emit('close');
},
/* istanbul ignore next */
onClosed() {
this.$emit('closed');
},
},
render() {
return (
<span ref="wrapper" class={bem('wrapper')}>
<Popup
ref="popover"
value={this.value}
class={bem([this.theme])}
overlay={this.overlay}
position={null}
transition="van-popover-zoom"
lockScroll={false}
getContainer={this.getContainer}
onOpen={this.onOpen}
onClose={this.onClose}
onInput={this.onToggle}
onOpened={this.onOpened}
onClosed={this.onClosed}
nativeOnTouchstart={this.onTouchstart}
>
<div class={bem('arrow')} />
<div class={bem('content')}>
{this.slots('default') || this.actions.map(this.renderAction)}
</div>
</Popup>
{this.slots('reference')}
</span>
);
},
});

269
src/popover/index.less Normal file
View File

@ -0,0 +1,269 @@
@import '../style/var';
@import '../style/mixins/hairline';
.van-popover {
position: absolute;
overflow: visible;
background-color: transparent;
transition: opacity 0.15s, transform 0.15s;
&__wrapper {
display: inline-block;
}
&__arrow {
position: absolute;
width: 0;
height: 0;
border-color: transparent;
border-style: solid;
border-width: @popover-arrow-size;
}
&__content {
overflow: hidden;
border-radius: @popover-border-radius;
}
&__action {
position: relative;
display: flex;
align-items: center;
box-sizing: border-box;
width: @popover-action-width;
height: @popover-action-height;
padding: 0 @padding-md;
font-size: @popover-action-font-size;
line-height: @line-height-md;
&:last-child {
.van-popover__action-text::after {
display: none;
}
}
&-text {
display: flex;
flex: 1;
align-items: center;
justify-content: center;
height: 100%;
}
&-icon {
margin-right: @padding-xs;
font-size: @popover-action-icon-size;
}
&--with-icon {
.van-popover__action-text {
justify-content: flex-start;
}
}
}
&[data-popper-placement^='top'] {
.van-popover__arrow {
bottom: 0;
border-top-color: currentColor;
border-bottom-width: 0;
transform: translate(-50%, 100%);
}
}
&[data-popper-placement='top'] {
transform-origin: 50% 100%;
.van-popover__arrow {
left: 50%;
}
}
&[data-popper-placement='top-start'] {
transform-origin: 0 100%;
.van-popover__arrow {
left: @padding-md;
}
}
&[data-popper-placement='top-end'] {
transform-origin: 100% 100%;
.van-popover__arrow {
right: @padding-md;
}
}
&[data-popper-placement^='left'] {
.van-popover__arrow {
right: 0;
border-right-width: 0;
border-left-color: currentColor;
transform: translate(100%, -50%);
}
}
&[data-popper-placement='left'] {
transform-origin: 100% 50%;
.van-popover__arrow {
top: 50%;
}
}
&[data-popper-placement='left-start'] {
transform-origin: 100% 0;
.van-popover__arrow {
top: @padding-md;
}
}
&[data-popper-placement='left-end'] {
transform-origin: 100% 100%;
.van-popover__arrow {
bottom: @padding-md;
}
}
&[data-popper-placement^='right'] {
.van-popover__arrow {
left: 0;
border-right-color: currentColor;
border-left-width: 0;
transform: translate(-100%, -50%);
}
}
&[data-popper-placement='right'] {
transform-origin: 0 50%;
.van-popover__arrow {
top: 50%;
}
}
&[data-popper-placement='right-start'] {
transform-origin: 0 0;
.van-popover__arrow {
top: @padding-md;
}
}
&[data-popper-placement='right-end'] {
transform-origin: 0 100%;
.van-popover__arrow {
bottom: @padding-md;
}
}
&[data-popper-placement^='bottom'] {
.van-popover__arrow {
top: 0;
border-top-width: 0;
border-bottom-color: currentColor;
transform: translate(-50%, -100%);
}
}
&[data-popper-placement='bottom'] {
transform-origin: 50% 0;
.van-popover__arrow {
left: 50%;
}
}
&[data-popper-placement='bottom-start'] {
transform-origin: 0 0;
.van-popover__arrow {
left: @padding-md;
}
}
&[data-popper-placement='bottom-end'] {
transform-origin: 100% 0;
.van-popover__arrow {
right: @padding-md;
}
}
&--light {
color: @popover-light-text-color;
.van-popover__content {
background-color: @popover-light-background-color;
box-shadow: 0 2px 12px rgba(50, 50, 51, 0.12);
}
.van-popover__arrow {
color: @popover-light-background-color;
}
.van-popover__action {
&:active {
background-color: @active-color;
}
&--disabled {
color: @popover-light-action-disabled-text-color;
&:active {
background-color: transparent;
}
}
}
}
&--dark {
color: @popover-dark-text-color;
.van-popover__content {
background-color: @popover-dark-background-color;
}
.van-popover__arrow {
color: @popover-dark-background-color;
}
.van-popover__action {
&:active {
background-color: rgba(0, 0, 0, 0.2);
}
&--disabled {
color: @popover-dark-action-disabled-text-color;
&:active {
background-color: transparent;
}
}
}
.van-popover__action-text {
&::after {
border-color: @gray-7;
}
}
}
&-zoom-enter,
&-zoom-leave-active {
transform: scale(0.8);
opacity: 0;
}
&-zoom-enter-active {
transition-timing-function: @animation-timing-function-enter;
}
&-zoom-leave-active {
transition-timing-function: @animation-timing-function-leave;
}
}

View File

@ -0,0 +1,29 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`renders demo correctly 1`] = `
<div>
<div><span class="van-popover__wrapper" style="margin-left: 16px;"><button class="van-button van-button--primary van-button--normal"><div class="van-button__content"><span class="van-button__text">
浅色风格
</span></div></button></span> <span class="van-popover__wrapper" style="margin-left: 16px;"><button class="van-button van-button--primary van-button--normal"><div class="van-button__content"><span class="van-button__text">
深色风格
</span>
</div></button></span></div>
<div>
<div role="button" tabindex="0" class="van-cell van-cell--clickable van-field">
<div class="van-cell__title van-field__label"><span>选择弹出位置</span></div>
<div class="van-cell__value van-field__value">
<div class="van-field__body"><input type="text" name="picker" readonly="readonly" class="van-field__control"></div>
</div><i class="van-icon van-icon-arrow van-cell__right-icon">
<!----></i>
</div>
</div>
<div><span class="van-popover__wrapper" style="margin-left: 16px;"><button class="van-button van-button--primary van-button--normal"><div class="van-button__content"><span class="van-button__text">
展示图标
</span></div></button></span> <span class="van-popover__wrapper" style="margin-left: 16px;"><button class="van-button van-button--primary van-button--normal"><div class="van-button__content"><span class="van-button__text">
禁用选项
</span></div></button></span></div>
<div><span class="van-popover__wrapper" style="margin-left: 16px;"><button class="van-button van-button--primary van-button--normal"><div class="van-button__content"><span class="van-button__text">
自定义内容
</span></div></button></span></div>
</div>
`;

View File

@ -0,0 +1,30 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`should allow to custom the className of action 1`] = `
<div class="van-popover__action foo">
<div class="van-popover__action-text van-hairline--bottom">Option</div>
</div>
`;
exports[`should locate to reference element when showed 1`] = `<!---->`;
exports[`should locate to reference element when showed 2`] = `
<div class="van-popup van-popover van-popover--light" name="van-popover-zoom" style="position: absolute; left: 0px; top: 8px; margin: 0px; z-index: 2006;" data-popper-placement="bottom">
<div class="van-popover__arrow"></div>
<div class="van-popover__content"></div>
</div>
`;
exports[`should locate to reference element when showed 3`] = `
<div class="van-popup van-popover van-popover--light" name="van-popover-zoom" style="position: absolute; left: 0px; top: 8px; margin: 0px; z-index: 2006; display: none;" data-popper-placement="bottom">
<div class="van-popover__arrow"></div>
<div class="van-popover__content"></div>
</div>
`;
exports[`should watch placement prop and update location 1`] = `
<div class="van-popup van-popover van-popover--light" name="van-popover-zoom" style="z-index: 2007; position: absolute; left: 0px; top: -8px; margin: 0px;" data-popper-placement="top">
<div class="van-popover__arrow"></div>
<div class="van-popover__content"></div>
</div>
`;

View File

@ -0,0 +1,4 @@
import Demo from '../demo';
import { snapshotDemo } from '../../../test/demo';
snapshotDemo(Demo);

View File

@ -0,0 +1,131 @@
import Popover from '..';
import { later, mount, trigger } from '../../../test';
const baseActions = [
{ text: 'Option 1' },
{ text: 'Option 2' },
{ text: 'Option 3' },
];
test('should emit select event when clicking the action', () => {
const wrapper = mount(Popover, {
propsData: {
value: true,
actions: baseActions,
},
});
wrapper.find('.van-popover__action').trigger('click');
expect(wrapper.emitted('select')[0]).toEqual([baseActions[0], 0]);
});
test('should not emit select event when the action is disabled', () => {
const wrapper = mount(Popover, {
propsData: {
value: true,
actions: [{ text: 'Option', disabled: true }],
},
});
wrapper.find('.van-popover__action').trigger('click');
expect(wrapper.emitted('select')).toBeFalsy();
});
test('should close popover when clicking the action', () => {
const wrapper = mount(Popover, {
propsData: {
value: true,
actions: baseActions,
},
});
wrapper.find('.van-popover__action').trigger('click');
expect(wrapper.emitted('input')[0][0]).toEqual(false);
wrapper.setProps({ closeOnClickAction: false });
wrapper.find('.van-popover__action').trigger('click');
expect(wrapper.emitted('input').length).toEqual(1);
});
test('should allow to custom the className of action', () => {
const wrapper = mount(Popover, {
propsData: {
value: true,
actions: [{ text: 'Option', className: 'foo' }],
},
});
expect(wrapper.find('.van-popover__action').html()).toMatchSnapshot();
});
test('should not init popper.js instance before showed', async () => {
const wrapper = mount(Popover);
expect(wrapper.vm.popper).toBeFalsy();
wrapper.destroy();
});
test('should destroy popper.js instance when unmouted', async () => {
const wrapper = mount(Popover, {
propsData: {
value: true,
},
});
await later();
expect(wrapper.vm.popper).toBeTruthy();
wrapper.destroy();
expect(wrapper.vm.popper).toEqual(null);
});
test('should locate to reference element when showed', async () => {
const root = document.createElement('div');
const wrapper = mount(Popover, {
propsData: {
getContainer: () => root,
},
});
expect(root.innerHTML).toMatchSnapshot();
wrapper.setProps({ value: true });
await later();
expect(root.innerHTML).toMatchSnapshot();
wrapper.setProps({ value: false });
expect(root.innerHTML).toMatchSnapshot();
});
test('should watch placement prop and update location', async () => {
const root = document.createElement('div');
const wrapper = mount(Popover, {
propsData: {
value: true,
getContainer: () => root,
},
});
wrapper.setProps({
placement: 'top',
});
await later();
expect(root.innerHTML).toMatchSnapshot();
});
test('should close popover when touch outside content', async () => {
const root = document.createElement('div');
const wrapper = mount(Popover, {
propsData: {
value: true,
getContainer: () => root,
},
});
const popover = root.querySelector('.van-popover');
trigger(popover, 'touchstart');
expect(wrapper.emitted('input')).toBeFalsy();
document.body.appendChild(root);
trigger(document.body, 'touchstart');
expect(wrapper.emitted('input')[0][0]).toEqual(false);
});

View File

@ -72,14 +72,14 @@
&-slide-left-enter-active,
&-slide-right-enter-active,
&-slide-bottom-enter-active {
transition-timing-function: ease-out;
transition-timing-function: @animation-timing-function-enter;
}
&-slide-top-leave-active,
&-slide-left-leave-active,
&-slide-right-leave-active,
&-slide-bottom-leave-active {
transition-timing-function: ease-in;
transition-timing-function: @animation-timing-function-leave;
}
&-slide-top-enter-from,

View File

@ -98,7 +98,7 @@ export default {
height: 100px;
margin: -50px 0 0 -50px;
background-color: @blue;
border-radius: 3px;
border-radius: 8px;
}
}
</style>

View File

@ -550,6 +550,21 @@
@picker-loading-icon-color: @blue;
@picker-loading-mask-color: rgba(255, 255, 255, 0.9);
// Popover
@popover-arrow-size: 6px;
@popover-border-radius: @border-radius-lg;
@popover-action-width: 128px;
@popover-action-height: 44px;
@popover-action-font-size: @font-size-md;
@popover-action-line-height: @line-height-md;
@popover-action-icon-size: 20px;
@popover-light-text-color: @text-color;
@popover-light-background-color: @white;
@popover-light-action-disabled-text-color: @gray-5;
@popover-dark-text-color: @white;
@popover-dark-background-color: #4a4a4a;
@popover-dark-action-disabled-text-color: @gray-6;
// Popup
@popup-background-color: @white;
@popup-transition: transform @animation-duration-base;

1
types/index.d.ts vendored
View File

@ -66,6 +66,7 @@ export class Overlay extends VanComponent {}
export class Pagination extends VanComponent {}
export class Panel extends VanComponent {}
export class PasswordInput extends VanComponent {}
export class Popover extends VanComponent {}
export class Popup extends VanComponent {}
export class PullRefresh extends VanComponent {}
export class Radio extends VanComponent {}

View File

@ -286,6 +286,10 @@ module.exports = {
path: 'notice-bar',
title: 'NoticeBar 通知栏',
},
{
path: 'popover',
title: 'Popover 气泡弹出框',
},
{
path: 'progress',
title: 'Progress 进度条',
@ -632,6 +636,10 @@ module.exports = {
path: 'notice-bar',
title: 'NoticeBar',
},
{
path: 'popover',
title: 'Popover',
},
{
path: 'progress',
title: 'Progress',

View File

@ -1319,6 +1319,11 @@
dependencies:
"@types/node" ">= 8"
"@popperjs/core@^2.5.4":
version "2.5.4"
resolved "https://registry.npm.taobao.org/@popperjs/core/download/@popperjs/core-2.5.4.tgz#de25b5da9f727985a3757fd59b5d028aba75841a"
integrity sha1-3iW12p9yeYWjdX/Vm10Cirp1hBo=
"@sindresorhus/is@^0.14.0":
version "0.14.0"
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"