From 00d3876c8582e6b706f5f21a7c57dfee209a017c Mon Sep 17 00:00:00 2001 From: silverwind Date: Tue, 19 Jul 2022 00:33:34 +0200 Subject: [PATCH] Use tippy.js for context popup (#20393) By appending the tooltips to `document.body`, we can avoid any stacking context issues caused by surrounding element's CSS. This uses [tippy.js](https://github.com/atomiks/tippyjs) instead of Fomantic popups. We should aim to replace all Fomantic popups with this eventually and then get rid of the Fomantic `popup` module completely. --- package-lock.json | 31 +++++++++++ package.json | 1 + web_src/js/features/contextpopup.js | 17 +++--- web_src/js/modules/tippy.js | 12 +++++ web_src/less/index.less | 1 + web_src/less/modules/tippy.less | 84 +++++++++++++++++++++++++++++ 6 files changed, 135 insertions(+), 11 deletions(-) create mode 100644 web_src/js/modules/tippy.js create mode 100644 web_src/less/modules/tippy.less diff --git a/package-lock.json b/package-lock.json index 605134201..05d728ccd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,6 +29,7 @@ "pretty-ms": "7.0.1", "sortablejs": "1.15.0", "swagger-ui-dist": "4.11.1", + "tippy.js": "6.3.7", "tributejs": "5.1.3", "uint8-to-base64": "0.2.0", "vue": "2.6.14", @@ -1599,6 +1600,15 @@ "node": ">= 8" } }, + "node_modules/@popperjs/core": { + "version": "2.11.5", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.5.tgz", + "integrity": "sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@primer/octicons": { "version": "17.2.0", "resolved": "https://registry.npmjs.org/@primer/octicons/-/octicons-17.2.0.tgz", @@ -11400,6 +11410,14 @@ "integrity": "sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==", "dev": true }, + "node_modules/tippy.js": { + "version": "6.3.7", + "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz", + "integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==", + "dependencies": { + "@popperjs/core": "^2.9.0" + } + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -13567,6 +13585,11 @@ "fastq": "^1.6.0" } }, + "@popperjs/core": { + "version": "2.11.5", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.5.tgz", + "integrity": "sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==" + }, "@primer/octicons": { "version": "17.2.0", "resolved": "https://registry.npmjs.org/@primer/octicons/-/octicons-17.2.0.tgz", @@ -21235,6 +21258,14 @@ "integrity": "sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==", "dev": true }, + "tippy.js": { + "version": "6.3.7", + "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz", + "integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==", + "requires": { + "@popperjs/core": "^2.9.0" + } + }, "tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", diff --git a/package.json b/package.json index afc8d2a37..3ba0b9563 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "pretty-ms": "7.0.1", "sortablejs": "1.15.0", "swagger-ui-dist": "4.11.1", + "tippy.js": "6.3.7", "tributejs": "5.1.3", "uint8-to-base64": "0.2.0", "vue": "2.6.14", diff --git a/web_src/js/features/contextpopup.js b/web_src/js/features/contextpopup.js index 0cd47bf2b..f4e660be3 100644 --- a/web_src/js/features/contextpopup.js +++ b/web_src/js/features/contextpopup.js @@ -2,6 +2,7 @@ import $ from 'jquery'; import Vue from 'vue'; import ContextPopup from '../components/ContextPopup.vue'; import {parseIssueHref} from '../utils.js'; +import {createTippy} from '../modules/tippy.js'; export default function initContextPopups() { const refIssues = $('.ref-issue'); @@ -16,7 +17,6 @@ export default function initContextPopups() { if (!owner) return; const el = document.createElement('div'); - el.className = 'ui custom popup hidden'; el.innerHTML = '
'; this.parentNode.insertBefore(el, this.nextSibling); @@ -33,17 +33,12 @@ export default function initContextPopups() { el.textContent = 'ContextPopup failed to load'; } - $(this).popup({ - variation: 'wide', - delay: { - show: 250 - }, + createTippy(this, { + content: el, + interactive: true, onShow: () => { - view.$emit('load-context-popup', {owner, repo, index}, () => { - $(this).popup('reposition'); - }); - }, - popup: $(el), + view.$emit('load-context-popup', {owner, repo, index}); + } }); }); } diff --git a/web_src/js/modules/tippy.js b/web_src/js/modules/tippy.js new file mode 100644 index 000000000..6fd466cd9 --- /dev/null +++ b/web_src/js/modules/tippy.js @@ -0,0 +1,12 @@ +import tippy from 'tippy.js'; + +export function createTippy(target, opts) { + return tippy(target, { + appendTo: document.body, + placement: 'top-start', + animation: false, + allowHTML: true, + arrow: ``, + ...opts, + }); +} diff --git a/web_src/less/index.less b/web_src/less/index.less index 4a6bd330f..4823c5617 100644 --- a/web_src/less/index.less +++ b/web_src/less/index.less @@ -9,6 +9,7 @@ @import "./features/imagediff.less"; @import "./features/codeeditor.less"; @import "./features/projects.less"; +@import "./modules/tippy.less"; @import "./markup/content.less"; @import "./markup/codecopy.less"; @import "./code/linebutton.less"; diff --git a/web_src/less/modules/tippy.less b/web_src/less/modules/tippy.less new file mode 100644 index 000000000..aa2aed6ce --- /dev/null +++ b/web_src/less/modules/tippy.less @@ -0,0 +1,84 @@ +/* styles are based on node_modules/tippy.js/dist/tippy.css */ + +.tippy-box[data-animation="fade"][data-state="hidden"] { + opacity: 0; +} + +[data-tippy-root] { + max-width: calc(100vw - 10px); +} + +.tippy-box { + position: relative; + background-color: var(--color-body); + color: var(--color-secondary-dark-6); + border: 1px solid var(--color-secondary); + border-radius: var(--border-radius); + font-size: 1rem; + transition-property: transform, visibility, opacity; +} + +.tippy-content { + position: relative; + padding: 1rem; + z-index: 1; +} + +.tippy-box[data-placement^="top"] > .tippy-svg-arrow { + bottom: 0; +} + +.tippy-box[data-placement^="top"] > .tippy-svg-arrow::after, +.tippy-box[data-placement^="top"] > .tippy-svg-arrow > svg { + top: 16px; + transform: rotate(180deg); +} + +.tippy-box[data-placement^="bottom"] > .tippy-svg-arrow { + top: 0; +} + +.tippy-box[data-placement^="bottom"] > .tippy-svg-arrow > svg { + bottom: 16px; +} + +.tippy-box[data-placement^="left"] > .tippy-svg-arrow { + right: 0; +} + +.tippy-box[data-placement^="left"] > .tippy-svg-arrow::after, +.tippy-box[data-placement^="left"] > .tippy-svg-arrow > svg { + transform: rotate(90deg); + top: calc(50% - 3px); + left: 11px; +} + +.tippy-box[data-placement^="right"] > .tippy-svg-arrow { + left: 0; +} + +.tippy-box[data-placement^="right"] > .tippy-svg-arrow::after, +.tippy-box[data-placement^="right"] > .tippy-svg-arrow > svg { + transform: rotate(-90deg); + top: calc(50% - 3px); + right: 11px; +} + +.tippy-svg-arrow { + width: 16px; + height: 16px; + text-align: initial; +} + +.tippy-svg-arrow, +.tippy-svg-arrow > svg { + position: absolute; +} + +.tippy-svg-arrow-outer { + fill: var(--color-secondary); +} + +.tippy-svg-arrow-inner { + fill: var(--color-body); +}