diff --git a/assets/js/src/tooltip.js b/assets/js/src/tooltip.js new file mode 100644 index 0000000000..79db56cf43 --- /dev/null +++ b/assets/js/src/tooltip.js @@ -0,0 +1,66 @@ +import { computePosition, flip, shift, offset, arrow } from "@floating-ui/dom"; + +/* Regular tooltips (partial) */ + +const tooltipWrappers = Array.from( + document.querySelectorAll("[data-tooltip-wrapper]"), +); + +for (const tooltipWrapper of tooltipWrappers) { + const button = tooltipWrapper.firstElementChild; + const tooltip = button.nextElementSibling; + const arrowElement = tooltip.firstElementChild; + + function update() { + computePosition(button, tooltip, { + placement: "top", + middleware: [ + offset(6), + flip(), + shift({ padding: 5 }), + arrow({ element: arrowElement }), + ], + }).then(({ x, y, placement, middlewareData }) => { + Object.assign(tooltip.style, { + left: `${x}px`, + top: `${y}px`, + }); + + // Accessing the data + const { x: arrowX, y: arrowY } = middlewareData.arrow; + + const staticSide = { + top: "bottom", + right: "left", + bottom: "top", + left: "right", + }[placement.split("-")[0]]; + + Object.assign(arrowElement.style, { + left: arrowX != null ? `${arrowX}px` : "", + top: arrowY != null ? `${arrowY}px` : "", + right: "", + bottom: "", + [staticSide]: "-4px", + }); + }); + } + + function showTooltip() { + tooltip.classList.toggle("hidden"); + update(); + } + + function hideTooltip() { + tooltip.classList.toggle("hidden"); + } + + [ + ["mouseenter", showTooltip], + ["mouseleave", hideTooltip], + ["focus", showTooltip], + ["blur", hideTooltip], + ].forEach(([event, listener]) => { + button.addEventListener(event, listener); + }); +} diff --git a/layouts/partials/tooltip.html b/layouts/partials/tooltip.html new file mode 100644 index 0000000000..06fa905ffb --- /dev/null +++ b/layouts/partials/tooltip.html @@ -0,0 +1,11 @@ +
+
+ {{ partial "icon" "help" }} +
+ +
diff --git a/package-lock.json b/package-lock.json index 3ab08522d2..9e33f2957f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "@alpinejs/collapse": "^3.13.5", "@docsearch/js": "^3.5.2", + "@floating-ui/dom": "^1.6.3", "@material-symbols/svg-400": "^0.14.6", "@tailwindcss/nesting": "^0.0.0-insiders.565cd3e", "@tailwindcss/typography": "^0.5.10", @@ -244,6 +245,28 @@ } } }, + "node_modules/@floating-ui/core": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz", + "integrity": "sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==", + "dependencies": { + "@floating-ui/utils": "^0.2.1" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.3.tgz", + "integrity": "sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==", + "dependencies": { + "@floating-ui/core": "^1.0.0", + "@floating-ui/utils": "^0.2.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz", + "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==" + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", diff --git a/package.json b/package.json index 58a67a0476..51341f773e 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "dependencies": { "@alpinejs/collapse": "^3.13.5", "@docsearch/js": "^3.5.2", + "@floating-ui/dom": "^1.6.3", "@material-symbols/svg-400": "^0.14.6", "@tailwindcss/nesting": "^0.0.0-insiders.565cd3e", "@tailwindcss/typography": "^0.5.10",