1
0

Build custom v-tooltip (#290)

Remove tooltips when their elements are unbound

Add support for .bottom modifier

Remove v-tooltip from dependencies

Add comments

Fix usage with bigger tooltips

Add very basic vanilla js tooltip

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: https://kolaente.dev/vikunja/frontend/pulls/290
Co-Authored-By: konrad <konrad@kola-entertainments.de>
Co-Committed-By: konrad <konrad@kola-entertainments.de>
This commit is contained in:
konrad
2020-11-10 20:47:39 +00:00
parent 70508202c0
commit 092e5165dc
6 changed files with 115 additions and 37 deletions

View File

@ -63,7 +63,7 @@
:to="{name: 'namespace.edit', params: {id: n.id} }"
class="nsettings"
v-if="n.id > 0"
v-tooltip.right="'Settings'">
v-tooltip="'Settings'">
<span class="icon">
<icon icon="cog"/>
</span>

83
src/directives/tooltip.js Normal file
View File

@ -0,0 +1,83 @@
const calculateTop = (coords, tooltip) => {
// Bottom tooltip use the exact inverse calculation compared to the default.
if (tooltip.classList.contains('bottom')) {
return coords.top + tooltip.offsetHeight + 5
}
// The top position of the tooltip is the coordinates of the bound element - the height of the tooltip -
// 5px spacing for the arrow (which is exactly 5px high)
return coords.top - tooltip.offsetHeight - 5
}
const calculateArrowTop = (top, tooltip) => {
if (tooltip.classList.contains('bottom')) {
return `${top - 5}px` // 5px arrow height
}
return `${top + tooltip.offsetHeight}px`
}
// This global object holds all created tooltip elements (and their arrows) using the element they were created for as
// key. This allows us to find the tooltip elements if the element the tooltip was created for is unbound so that
// we can remove the tooltip element.
const createdTooltips = {}
export default {
inserted: (el, {value, modifiers}) => {
// First, we create the tooltip and arrow elements
const tooltip = document.createElement('div')
tooltip.style.position = 'fixed'
tooltip.innerText = value
tooltip.classList.add('tooltip')
const arrow = document.createElement('div')
arrow.classList.add('tooltip-arrow')
arrow.style.position = 'fixed'
if (typeof modifiers.bottom !== 'undefined') {
tooltip.classList.add('bottom')
arrow.classList.add('bottom')
}
// We don't append the element until hovering over it because that's the most reliable way to determine
// where the parent elemtent is located at the time the user hovers over it.
el.addEventListener('mouseover', () => {
// Appending the element right away because we can only calculate the height of the element if it is
// already in the DOM.
document.body.appendChild(tooltip)
document.body.appendChild(arrow)
const coords = el.getBoundingClientRect()
const top = calculateTop(coords, tooltip)
// The left position of the tooltip is calculated so that the middle point of the tooltip
// (where the arrow will be) is the middle of the bound element
const left = coords.left - (tooltip.offsetWidth / 2) + (el.offsetWidth / 2)
// Now setting all the values
tooltip.style.top = `${top}px`
tooltip.style.left = `${coords.left}px`
tooltip.style.left = `${left}px`
arrow.style.left = `${left + (tooltip.offsetWidth / 2) - (arrow.offsetWidth / 2)}px`
arrow.style.top = calculateArrowTop(top, tooltip)
// And finally make it visible to the user. This will also trigger a nice fade-in animation through
// css transitions
tooltip.classList.add('visible')
arrow.classList.add('visible')
})
el.addEventListener('mouseout', () => {
tooltip.classList.remove('visible')
arrow.classList.remove('visible')
})
createdTooltips[el] = {
tooltip: tooltip,
arrow: arrow,
}
},
unbind: el => {
if (typeof createdTooltips[el] !== 'undefined') {
createdTooltips[el].tooltip.remove()
createdTooltips[el].arrow.remove()
}
},
}

View File

@ -56,8 +56,6 @@ import {
} from '@fortawesome/free-solid-svg-icons'
import {faCalendarAlt, faClock, faComments, faSave, faStar, faTimesCircle} from '@fortawesome/free-regular-svg-icons'
import {FontAwesomeIcon} from '@fortawesome/vue-fontawesome'
// Tooltip
import VTooltip from 'v-tooltip'
// PWA
import './registerServiceWorker'
@ -140,13 +138,14 @@ library.add(faStarSolid)
Vue.component('icon', FontAwesomeIcon)
Vue.use(VTooltip, {defaultHtml: false})
Vue.use(vueShortkey)
import focus from '@/directives/focus'
Vue.directive('focus', focus)
import tooltip from '@/directives/tooltip'
Vue.directive('tooltip', tooltip)
Vue.mixin({
methods: {
formatDateSince: date => {

View File

@ -3,22 +3,38 @@
z-index: 10000;
font-size: 0.8em;
text-align: center;
background: $dark;
color: white;
border-radius: 5px;
padding: 5px 10px 5px;
opacity: 0;
transition: opacity $transition;
.tooltip-inner {
background: $dark;
color: white;
border-radius: 5px;
padding: 5px 10px 5px;
}
// If the tooltip is multiline, it would make the height calculations needed to properly position it a lot harder.
white-space: nowrap;
overflow: hidden;
&-arrow {
opacity: 0;
content: '';
display: block;
position: absolute;
transition: opacity $transition;
z-index: 10000;
.tooltip-arrow {
width: 0;
height: 0;
border-style: solid;
position: absolute;
margin: 5px;
border-color: $dark;
z-index: 1;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-top: 5px solid $dark;
&.bottom {
transform: rotate(180deg);
}
}
&.visible, &-arrow.visible {
opacity: 1;
}
&[x-placement^="top"] {