Skip to content

Instantly share code, notes, and snippets.

@james2doyle
Last active July 24, 2020 16:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save james2doyle/9ae0e74ab45d7e80d1953a3c7d8e0a1d to your computer and use it in GitHub Desktop.
Save james2doyle/9ae0e74ab45d7e80d1953a3c7d8e0a1d to your computer and use it in GitHub Desktop.
A popover component for Vue.js
<!--
Popover Component
Usage:
<popover class="ml-3 inline-block align-text-top" v-cloak>
<template #icon>
<div class="w-5 icon-plus"></div>
</template>
<template #content>
<strong>Title</strong>
<p>Some content is here</p>
</template>
</popover>
-->
<template>
<div ref="popover" class="popover" @mouseenter="onEnter" @mouseleave="onLeave" @touchstart="onEnter">
<slot name="icon">
<strong>Icon slot required</strong>
</slot>
<transition name="fade">
<div v-show="open" class="popover-content" :class="style" @mouseover="onEnter">
<slot name="content">
<strong>Content slot required</strong>
</slot>
<div class="popover-arrow"></div>
</div>
</transition>
</div>
</template>
<script>
import { debounce } from 'lodash';
const POPOVER_TIMEOUT = 300;
export default {
name: 'Popover',
props: {
type: {
type: String,
required: false,
default: '',
},
},
data() {
return {
open: false,
};
},
computed: {
style() {
return ['negative', 'danger', 'warning'].indexOf(this.type) > -1 ? 'popover-content--negative' : '';
},
},
mounted() {
document.body.addEventListener('click', this.handleOutsideAction.bind(this));
},
destroyed() {
document.body.removeEventListener('click', this.handleOutsideAction.bind(this));
},
methods: {
handleOutsideAction({ target }) {
// close the popover if you click outside it
if (!this.$refs.popover.contains(target)) {
this.open = false;
}
},
onEnter() {
this.open = true;
},
onLeave: debounce(function() {
this.open = false;
}, POPOVER_TIMEOUT),
},
};
</script>
<style lang="scss">
$popover-arrow-size: 0.5rem;
$color-mono-400: #333;
$color-negative: #fd6666;
.popover {
position: relative;
&-content {
@apply .absolute .left-0 .bottom-0 .border .border-mono-400 .bg-white .z-20 .p-4 .shadow-sm;
min-width: 200px;
// remove the width and the border size
transform: translate(calc(-70% + #{$popover-arrow-size} + 2px), calc(#{$popover-arrow-size} * 4 * -1));
@screen md {
transform: translate(calc(-50% + #{$popover-arrow-size} + 2px), calc(#{$popover-arrow-size} * 4 * -1));
}
.popover-arrow {
@apply .absolute .z-10 .w-2 .h-2;
border-top: $popover-arrow-size solid $color-mono-400;
border-right: $popover-arrow-size solid transparent;
border-bottom: $popover-arrow-size solid transparent;
border-left: $popover-arrow-size solid transparent;
bottom: calc(#{$popover-arrow-size} * 2 * -1); // same as the border width * 2
// add border width to left position
left: calc(70% - #{$popover-arrow-size});
@screen md {
// add border width to left position
left: calc(50% - #{$popover-arrow-size});
}
}
&--warning,
&--danger,
&--negative {
@apply .border-negative;
.popover-arrow {
border-top: $popover-arrow-size solid $color-negative;
border-right: $popover-arrow-size solid transparent;
border-bottom: $popover-arrow-size solid transparent;
border-left: $popover-arrow-size solid transparent;
}
}
}
}
</style>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment