From 91580f97a1cde14267190267c5518f2a51e033e3 Mon Sep 17 00:00:00 2001 From: dpschen Date: Tue, 23 Nov 2021 07:08:21 +0000 Subject: [PATCH] feat: use popper.js v2 vue3 version of v-tooltip (#1038) Co-authored-by: Dominik Pschenitschni Reviewed-on: https://kolaente.dev/vikunja/frontend/pulls/1038 Reviewed-by: konrad Co-authored-by: dpschen Co-committed-by: dpschen --- package.json | 1 + src/directives/tooltip.js | 83 ------------------------------ src/main.ts | 11 ++-- src/styles/components/tooltip.scss | 48 ++++------------- yarn.lock | 18 +++++++ 5 files changed, 34 insertions(+), 127 deletions(-) delete mode 100644 src/directives/tooltip.js diff --git a/package.json b/package.json index ee67f86b1..2b2f4f0d0 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "register-service-worker": "1.7.2", "snake-case": "3.0.4", "ufo": "0.7.9", + "v-tooltip": "4.0.0-beta.2", "vue": "3.2.22", "vue-advanced-cropper": "2.7.0", "vue-drag-resize": "2.0.3", diff --git a/src/directives/tooltip.js b/src/directives/tooltip.js deleted file mode 100644 index cfdf05cb4..000000000 --- a/src/directives/tooltip.js +++ /dev/null @@ -1,83 +0,0 @@ -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 { - mounted(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, - } - }, - unmounted(el) { - if (typeof createdTooltips[el] !== 'undefined') { - createdTooltips[el].tooltip.remove() - createdTooltips[el].arrow.remove() - } - }, -} diff --git a/src/main.ts b/src/main.ts index c19374d2d..48a6590d3 100644 --- a/src/main.ts +++ b/src/main.ts @@ -55,20 +55,21 @@ app.use(Notifications) // directives import focus from '@/directives/focus' -import tooltip from '@/directives/tooltip' +import { VTooltip } from 'v-tooltip' +import 'v-tooltip/dist/v-tooltip.css' import shortcut from '@/directives/shortcut' import cypress from '@/directives/cypress' app.directive('focus', focus) -app.directive('tooltip', tooltip) +app.directive('tooltip', VTooltip) app.directive('shortcut', shortcut) app.directive('cy', cypress) // global components import FontAwesomeIcon from './icons' -import Button from './components/input/button.vue' -import Modal from './components/modal/modal.vue' -import Card from './components/misc/card.vue' +import Button from '@/components/input/button.vue' +import Modal from '@/components/modal/modal.vue' +import Card from '@/components/misc/card.vue' app.component('icon', FontAwesomeIcon) app.component('x-button', Button) diff --git a/src/styles/components/tooltip.scss b/src/styles/components/tooltip.scss index a5b77b3d1..f56be0238 100644 --- a/src/styles/components/tooltip.scss +++ b/src/styles/components/tooltip.scss @@ -1,42 +1,12 @@ -// FIXME: https://www.bram.us/2021/09/13/dont-attach-tooltips-to-document-body/ - -.tooltip { - visibility: collapse; - z-index: 10000; - font-size: 0.8rem; - text-align: center; +.v-popper--theme-tooltip .v-popper__inner { + padding: 5px 10px; + font-size: 0.8rem; background: var(--dark); - color: white; + color: var(--white); border-radius: 5px; - padding: 5px 10px 5px; - opacity: 0; - transition: opacity $transition; - - // 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: ''; - visibility: collapse; - position: absolute; - transition: opacity $transition; - z-index: 10000; - - width: 0; - height: 0; - border-left: 5px solid transparent; - border-right: 5px solid transparent; - border-top: 5px solid var(--dark); - - &.bottom { - transform: rotate(180deg); - } - } - - &.visible, &-arrow.visible { - opacity: 1; - visibility: visible; - } } + +.dark .v-popper--theme-tooltip .v-popper__inner { + background: var(--white); + color: var(--dark); +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 7d1a5b481..4f8b17e02 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2794,6 +2794,11 @@ dependencies: "@octokit/openapi-types" "^11.2.0" +"@popperjs/core@^2.10.2": + version "2.10.2" + resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.10.2.tgz#0798c03351f0dea1a5a4cabddf26a55a7cbee590" + integrity sha512-IXf3XA7+XyN7CP9gGh/XB0UxVMlvARGEgGXLubFICsUMGz6Q+DU+i4gGlpOxTjKvXjkJDJC8YdqdKkDj9qZHEQ== + "@rollup/plugin-babel@^5.2.0": version "5.3.0" resolved "https://registry.yarnpkg.com/@rollup/plugin-babel/-/plugin-babel-5.3.0.tgz#9cb1c5146ddd6a4968ad96f209c50c62f92f9879" @@ -13772,6 +13777,14 @@ uuid@^8.0.0, uuid@^8.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== +v-tooltip@4.0.0-beta.2: + version "4.0.0-beta.2" + resolved "https://registry.yarnpkg.com/v-tooltip/-/v-tooltip-4.0.0-beta.2.tgz#c5cb8d4b703207f588965e32bbec54ada0e43643" + integrity sha512-T1cMnFwjRy41YFcoRTNTwXI2TnkdIxwmDVjzUpCxyK5KJAB29FCwuW+usXOaZHJouEI5NOZ/3LnAgxP18xFkww== + dependencies: + "@popperjs/core" "^2.10.2" + vue-resize "^2.0.0-alpha.1" + v8-compile-cache@^2.0.3: version "2.3.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" @@ -14044,6 +14057,11 @@ vue-i18n@9.2.0-beta.20: "@intlify/vue-devtools" "9.2.0-beta.20" "@vue/devtools-api" "^6.0.0-beta.13" +vue-resize@^2.0.0-alpha.1: + version "2.0.0-alpha.1" + resolved "https://registry.yarnpkg.com/vue-resize/-/vue-resize-2.0.0-alpha.1.tgz#43eeb79e74febe932b9b20c5c57e0ebc14e2df3a" + integrity sha512-7+iqOueLU7uc9NrMfrzbG8hwMqchfVfSzpVlCMeJQe4pyibqyoifDNbKTZvwxZKDvGkB+PdFeKvnGZMoEb8esg== + vue-router@4.0.12: version "4.0.12" resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.0.12.tgz#8dc792cddf5bb1abcc3908f9064136de7e13c460"