feat: add popup component

This commit is contained in:
JiwenBai 2019-06-28 18:14:06 +08:00
parent c6996a818a
commit 8e51b833c1
12 changed files with 585 additions and 37 deletions

View File

@ -0,0 +1,170 @@
/**
有待解决一个bug在nimbus新的layout下不使用transfer 弹出的定位是不准的所以这里默认使用了transfer,经过查询可能是 overflow:auto造成的影响但是还是没有解决
*/
<template>
<div ref="doc" class="n-doc">
<div class="n-doc-header">
<n-gradient-text :font-size="20">
Popup
</n-gradient-text>
</div>
<div class="n-doc-body">
<div class="n-doc-section">
<div class="n-doc-section__header">
Basic Usage
</div>
<div class="n-doc-section__view">
<n-popup>
<n-button style="margin:0">hover</n-button>
<span slot="content">hello宝贝</span>
</n-popup>
</div>
<div class="n-doc-section__source">
<textarea v-pre>
<n-popup>
<n-button>hover</n-button>
<span slot="content">hello宝贝</span>
</n-popup>
</textarea>
</div>
</div>
<div class="n-doc-section">
<div class="n-doc-section__header">
word wrap
</div>
<div class="n-doc-section__view">
<n-popup placement="bottom" :width="200">
<n-button style="margin:0">word wrap</n-button>
<span slot="content">
"Beautiful designs at up to 70% Off. Article offers stylish
modern, mid-century and scandinavian furniture from world renowned
designers at accessible prices."</span
>
</n-popup>
</div>
<div class="n-doc-section__source">
<textarea v-pre>
<n-popup placement="left">
<n-button style="margin:0">hover show left</n-button>
<span slot="content">hello宝贝</span>
</n-popup>
</textarea>
</div>
</div>
<div class="n-doc-section">
<div class="n-doc-section__header">
Change placement
</div>
<div class="n-doc-section__view">
<n-popup placement="left">
<n-button style="margin:0">hover show left</n-button>
<span slot="content">hello宝贝</span>
</n-popup>
</div>
<div class="n-doc-section__source">
<textarea v-pre>
/*
support:
'top',
'top-start',
'top-end',
'bottom',
'bottom-start',
'bottom-end',
'left',
'left-start',
'left-end',
'right',
'right-start',
'right-end'
*/
<n-popup placement="left">
<n-button style="margin:0">hover show left</n-button>
<span slot="content">hello宝贝</span>
</n-popup>
</textarea>
</div>
</div>
<!-- <div class="n-doc-section">
<div class="n-doc-section__header">
Transfer dom to body
</div>
<div class="n-doc-section__view">
<n-popup placement="top-end">
<n-button style="margin:0">transfer to body</n-button>
<span slot="content">hello宝贝</span>
</n-popup>
</div>
<div class="n-doc-section__source">
<textarea v-pre>
<n-popup placement="top-end">
<n-button style="margin:0">transfer to body</n-button>
<span slot="content">hello宝贝</span>
</n-popup>
</textarea>
</div>
</div> -->
<div class="n-doc-section">
<div class="n-doc-section__header">
Click to show
</div>
<div class="n-doc-section__view">
<n-popup placement="left" trigger="click" transfer>
<n-button style="margin:0">click to show</n-button>
<span slot="content">click out side to hide</span>
</n-popup>
</div>
<div class="n-doc-section__source">
<textarea v-pre>
<n-popup placement="left" trigger="click" transfer>
<n-button style="margin:0">click to show</n-button>
<span slot="content">click out side to hide</span>
</n-popup>
</textarea>
</div>
</div>
</div>
</div>
</template>
<script>
import docCodeEditorMixin from './docCodeEditorMixin'
export default {
mixins: [docCodeEditorMixin],
data () {
return { content: 'hello', visible: false }
}
}
</script>
<style scoped lang="scss">
.doc {
width: 780px;
margin: 0 auto;
.doc-header {
display: flex;
height: 60px;
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
align-items: center;
}
.doc-body {
padding-top: 14px;
}
.section-header {
font-size: 16px;
font-weight: bold;
margin-bottom: 12px;
}
.section {
background: #5c657eff;
padding: 18px;
border-radius: 8px;
justify-content: center;
display: flex;
margin-bottom: 12px;
}
}
</style>

View File

@ -101,6 +101,10 @@ export default {
{
name: 'Tooltip',
path: '/n-tooltip'
},
{
name: 'Popup',
path: '/n-popup'
}
]
}
@ -111,7 +115,6 @@ export default {
</script>
<style lang="scss" scoped>
.demo {
position: absolute;
left: 0;
@ -170,10 +173,7 @@ body {
font-size: 16px;
}
.n-doc-section__source {
<<<<<<< HEAD
=======
position: relative;
>>>>>>> ca1d208f1357cc1eff2bd4829bad43064242edb5
}
}
}

View File

@ -22,6 +22,8 @@ import Select from '../packages/common/Select'
import Modal from '../packages/common/Modal'
import Message from '../packages/common/Message'
import Tooltip from '../packages/common/Tooltip'
import Popup from '../packages/common/Popup'
import Notification from '../packages/common/Notification'
import Pagination from '../packages/common/Pagination'
@ -47,6 +49,8 @@ import modalDemo from './components/modalDemo'
import nimbusFormCardDemo from './components/nimbusFormCardDemo'
import messageDemo from './components/messageDemo'
import tooltipDemo from './components/tooltipDemo'
import popupDemo from './components/popupDemo'
import notificationDemo from './components/notificationDemo'
import nimbusConfirmCardDemo from './components/nimbusConfirmCardDemo'
import paginationDemo from './components/paginationDemo'
@ -81,17 +85,15 @@ Tooltip.install(Vue)
Notification.install(Vue)
NimbusConfirmCard.install(Vue)
Pagination.install(Vue)
Popup.install(Vue)
const routes = [
{
<<<<<<< HEAD
path: '/start',
=======
path: '/home-demo',
component: homeDemo
},
{ path: '/start',
>>>>>>> ca1d208f1357cc1eff2bd4829bad43064242edb5
{
path: '/start',
component: demo,
children: [
{ path: '/start', component: startPage },
@ -110,6 +112,7 @@ const routes = [
{ path: '/n-nimbus-form-card', component: nimbusFormCardDemo },
{ path: '/n-message', component: messageDemo },
{ path: '/n-tooltip', component: tooltipDemo },
{ path: '/n-popup', component: popupDemo },
{ path: '/n-notification', component: notificationDemo },
{ path: '/n-nimbus-confirm-card', component: nimbusConfirmCardDemo },
{ path: '/n-pagination', component: paginationDemo }

View File

@ -18,6 +18,7 @@ import Message from './packages/common/Message'
import Notification from './packages/common/Notification'
import Pagination from './packages/common/Pagination'
import Tooltip from './packages/common/Tooltip'
import Popup from './packages/common/Popup'
import ServiceCard from './packages/nimbus/ServiceCard'
import HomeLayout from './packages/nimbus/HomeLayout'
@ -53,6 +54,7 @@ function installUiToVue (Vue) {
NimbusConfirmCard.install(Vue)
Pagination.install(Vue)
Tooltip.install(Vue)
Popup.install(Popup)
}
export default installUiToVue

View File

@ -56,12 +56,14 @@
"karma-spec-reporter": "0.0.32",
"karma-webpack": "^3.0.5",
"mocha": "^6.1.4",
"popper.js": "^1.15.0",
"prettier-eslint": "^9.0.0",
"progress-bar-webpack-plugin": "^1.12.1",
"sinon": "^7.3.2",
"sinon-chai": "^3.3.0",
"style-loader": "^0.23.1",
"url-loader": "^1.1.2",
"v-click-outside-x": "^4.0.5",
"vue": "^2.6.10",
"vue-loader": "^15.7.0",
"vue-router": "^3.0.6",

View File

@ -0,0 +1,7 @@
import Scaffold from './src/main.vue'
Scaffold.install = function (Vue) {
Vue.component(Scaffold.name, Scaffold)
}
export default Scaffold

View File

@ -0,0 +1,122 @@
<template>
<div
style="postion:relative"
@mouseenter="handleMouseEnter"
@mouseleave="handleMouseLeave"
v-click-outside="handleClickOut"
>
<div ref="reference" @click="handleClickRef" style="position:relative;">
<slot></slot>
</div>
<transition name="fade">
<div
@mouseenter="handleMouseEnter"
@mouseleave="handleMouseLeave"
v-show="visible"
:data-transfer="transfer"
v-transfer-dom
ref="popper"
class="popper n-popup__content__wrapper"
style=" overflow: auto"
>
<div
class="n-popup__content"
:style="{
width: width + 'px',
'box-sizing': 'border-box'
}"
:class="{
'n-popup__word_wrap': width ? true : false
}"
>
<slot name="content" />
</div>
</div>
</transition>
</div>
</template>
<script>
import Popper from 'packages/utils/popper.js'
import { directive as clickOutside } from 'v-click-outside-x'
import TransferDom from 'packages/directives/transfer-dom'
export default {
mixins: [Popper],
name: 'NPopup',
directives: { clickOutside, TransferDom },
props: {
placement: {
validator (value) {
return [
'top',
'top-start',
'top-end',
'bottom',
'bottom-start',
'bottom-end',
'left',
'left-start',
'left-end',
'right',
'right-start',
'right-end'
].includes(value)
},
default: 'bottom'
},
transfer: {
default: true,
type: Boolean
},
width: {
default: null,
type: Number
},
trigger: {
validator (value) {
return ['click', 'hover'].includes(value)
},
default: 'hover'
}
},
data () {
return {
timerId: null,
leaveTimer: null
}
},
methods: {
handleClickRef () {
if (this.trigger === 'click') {
this.show()
}
},
handleClickOut () {
this.hide()
},
hide () {
if (this.timerId) {
clearTimeout(this.timerId)
this.timerId = setTimeout(() => {
this.visible = false
}, 100)
}
},
show () {
if (this.timerId) {
clearTimeout(this.timerId)
}
this.timerId = setTimeout(() => {
this.visible = true
}, 100)
},
handleMouseEnter () {
if (this.trigger === 'hover') this.show()
},
handleMouseLeave () {
if (this.trigger === 'hover') this.hide()
}
}
}
</script>

View File

@ -0,0 +1,89 @@
// Thanks to: https://github.com/airyland/vux/blob/v2/src/directives/transfer-dom/index.js
// Thanks to: https://github.com/calebroseland/vue-dom-portal
// Thanks to: https://github.com/iview/iview/blob/2.0/src/directives/transfer-dom.js
/**
* Get target DOM Node
* @param {(Node|string|Boolean)} [node=document.body] DOM Node, CSS selector, or Boolean
* @return {Node} The target that the el will be appended to
*/
function getTarget (node) {
if (node === void 0) {
node = document.body
}
if (node === true) {
return document.body
}
return node instanceof window.Node ? node : document.querySelector(node)
}
const directive = {
inserted (el, { value }, vnode) {
if (el.dataset && el.dataset.transfer !== 'true') return false
el.className = el.className
? el.className + ' v-transfer-dom'
: 'v-transfer-dom'
const parentNode = el.parentNode
if (!parentNode) return
const home = document.createComment('')
let hasMovedOut = false
if (value !== false) {
parentNode.replaceChild(home, el) // moving out, el is no longer in the document
getTarget(value).appendChild(el) // moving into new place
hasMovedOut = true
}
if (!el.__transferDomData) {
el.__transferDomData = {
parentNode: parentNode,
home: home,
target: getTarget(value),
hasMovedOut: hasMovedOut
}
}
},
componentUpdated (el, { value }) {
if (el.dataset && el.dataset.transfer !== 'true') return false
// need to make sure children are done updating (vs. `update`)
const ref$1 = el.__transferDomData
if (!ref$1) return
// homes.get(el)
const parentNode = ref$1.parentNode
const home = ref$1.home
const hasMovedOut = ref$1.hasMovedOut // recall where home is
if (!hasMovedOut && value) {
// remove from document and leave placeholder
parentNode.replaceChild(home, el)
// append to target
getTarget(value).appendChild(el)
el.__transferDomData = Object.assign({}, el.__transferDomData, {
hasMovedOut: true,
target: getTarget(value)
})
} else if (hasMovedOut && value === false) {
// previously moved, coming back home
parentNode.replaceChild(el, home)
el.__transferDomData = Object.assign({}, el.__transferDomData, {
hasMovedOut: false,
target: getTarget(value)
})
} else if (value) {
// already moved, going somewhere else
getTarget(value).appendChild(el)
}
},
unbind (el) {
if (el.dataset && el.dataset.transfer !== 'true') return false
el.className = el.className.replace('v-transfer-dom', '')
const ref$1 = el.__transferDomData
if (!ref$1) return
if (el.__transferDomData.hasMovedOut === true) {
el.__transferDomData.parentNode &&
el.__transferDomData.parentNode.appendChild(el)
}
el.__transferDomData = null
}
}
export default directive

123
packages/utils/popper.js Normal file
View File

@ -0,0 +1,123 @@
/**
* https://github.com/freeze-component/vue-popper
* */
import Vue from 'vue'
const isServer = Vue.prototype.$isServer
const Popper = isServer
? function () {}
: require('popper.js/dist/umd/popper.js') // eslint-disable-line
export default {
props: {
placement: {
type: String,
default: 'bottom'
},
boundariesPadding: {
type: Number,
default: 5
},
reference: Object,
popper: Object,
offset: {
default: 0
},
value: {
type: Boolean,
default: false
},
transition: String,
options: {
type: Object,
default () {
return {
modifiers: {
computeStyle: {
gpuAcceleration: false
},
preventOverflow: {
boundariesElement: 'window'
}
}
}
}
}
// visible: {
// type: Boolean,
// default: false
// }
},
data () {
return {
visible: this.value
}
},
watch: {
value: {
immediate: true,
handler (val) {
this.visible = val
this.$emit('input', val)
}
},
visible (val) {
if (val) {
if (this.handleIndexIncrease) this.handleIndexIncrease() // just use for Poptip
this.updatePopper()
this.$emit('on-popper-show')
} else {
this.$emit('on-popper-hide')
}
this.$emit('input', val)
}
},
methods: {
createPopper () {
if (isServer) return
if (!/^(top|bottom|left|right)(-start|-end)?$/g.test(this.placement)) {
return
}
const options = this.options
const popper = this.popper || this.$refs.popper
const reference = this.reference || this.$refs.reference
if (!popper || !reference) return
if (this.popperJS && this.popperJS.hasOwnProperty('destroy')) {
this.popperJS.destroy()
}
options.placement = this.placement
if (!options.modifiers.offset) {
options.modifiers.offset = {}
}
options.modifiers.offset.offset = this.offset
options.onCreate = () => {
this.$nextTick(this.updatePopper)
this.$emit('created', this)
}
this.popperJS = new Popper(reference, popper, options)
},
updatePopper () {
if (isServer) return
this.popperJS ? this.popperJS.update() : this.createPopper()
},
doDestroy () {
if (isServer) return
if (this.visible) return
this.popperJS.destroy()
this.popperJS = null
}
},
updated () {
this.$nextTick(() => this.updatePopper())
},
beforeDestroy () {
if (isServer) return
if (this.popperJS) {
this.popperJS.destroy()
}
}
}

27
styles/Popup.scss Normal file
View File

@ -0,0 +1,27 @@
@import "./mixins/mixins.scss";
@import "./theme/default.scss";
.n-popup__content__wrapper {
z-index: 1;
transition: opacity 0.3s $default-cubic-bezier,
transform 0.3s $fast-in-cubic-bezier;
opacity: 1;
margin: 5px;
}
.n-popup__content {
border-radius: 6px;
background-color: rgba(75, 81, 106, 1);
color: #ffffffe6;
padding: 8px 14px;
font-size: 12px;
}
.n-popup__word_wrap {
white-space: pre-wrap;
text-align: justify;
}
.fade-enter-active, .fade-leave-active {
transition: opacity .5s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
}

View File

@ -1,5 +1,5 @@
@import './mixins/mixins.scss';
@import './theme/default.scss';
@import "./mixins/mixins.scss";
@import "./theme/default.scss";
@include b(tooltip) {
& {
@ -10,7 +10,7 @@
display: none;
}
.n-tooltip__popup.has-emerged {
opacity: .5;
opacity: 0.5;
transform: translateX(-50%) translateY(4px);
}
.n-tooltip__popup {
@ -21,19 +21,21 @@
transform: translateX(-50%) translateY(0px);
padding-top: 4px;
opacity: 1;
transition: opacity .3s $default-cubic-bezier, transform .3s $fast-in-cubic-bezier;
transition: opacity 0.3s $default-cubic-bezier,
transform 0.3s $fast-in-cubic-bezier;
.n-tooltip-popup__content {
white-space: nowrap;
border-radius: 6px;
background-color: rgba(75,81,106,1);
color: #FFFFFFE6;
background-color: rgba(75, 81, 106, 1);
color: #ffffffe6;
padding: 8px 14px;
font-size: 12px;
}
}
.n-tooltip__popup.is-vanishing {
.n-tooltip__popup.is-vanishing {
opacity: 0;
transition: opacity .3s $default-cubic-bezier, transform .3s $slow-out-cubic-bezier!important;
transition: opacity 0.3s $default-cubic-bezier,
transform 0.3s $slow-out-cubic-bezier !important;
transform: translateX(-50%) translateY(4px);
}
}
}

View File

@ -1,21 +1,22 @@
@import './base.scss';
@import "./base.scss";
@import './Card.scss';
@import './Icon.scss';
@import './GradientText.scss';
@import './ColumnGroup.scss';
@import './WithPadding.scss';
@import './ServiceCard.scss';
@import './Table.scss';
@import './WithMargin.scss';
@import './Checkbox.scss';
@import './Button.scss';
@import './Switch.scss';
@import './Input.scss';
@import './Select.scss';
@import './Message.scss';
@import './Tooltip.scss';
@import './Notification.scss';
@import './Pagination.scss';
@import "./Card.scss";
@import "./Icon.scss";
@import "./GradientText.scss";
@import "./ColumnGroup.scss";
@import "./WithPadding.scss";
@import "./ServiceCard.scss";
@import "./Table.scss";
@import "./WithMargin.scss";
@import "./Checkbox.scss";
@import "./Button.scss";
@import "./Switch.scss";
@import "./Input.scss";
@import "./Select.scss";
@import "./Message.scss";
@import "./Tooltip.scss";
@import "./Notification.scss";
@import "./Pagination.scss";
@import './NimbusServiceLayout.scss';
@import "./NimbusServiceLayout.scss";
@import "./Popup.scss";