Last active
February 15, 2023 10:21
-
-
Save austenc/ff2eefcf05b660d2675453d9fcfdf431 to your computer and use it in GitHub Desktop.
Alpine JS + Floating UI Tooltip Directive
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Alpine from 'alpinejs' | |
import tooltip from './tooltip' | |
Alpine.plugin(tooltip) | |
Alpine.start() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { computePosition, offset, flip, arrow } from "@floating-ui/dom" | |
export default function (Alpine) { | |
Alpine.directive('tooltip', (el, { expression }, { cleanup, effect }) => { | |
const classes = 'opacity-0 absolute pointer-events-none z-50 block bg-gray-700 dark:bg-black rounded px-2 py-1 max-w-max text-white text-sm transition duration-300' | |
const arrowClasses = 'absolute w-2.5 h-2.5 rotate-45 bg-gray-700 dark:bg-black' | |
const tooltip = document.createElement('div') | |
const arrowElement = document.createElement('div') | |
let cleanupPosition = null | |
effect(() => { | |
tooltip.classList.add(...classes.split(' ')) | |
tooltip.textContent = expression | |
el.before(tooltip) | |
arrowElement.classList.add(...arrowClasses.split(' ')) | |
tooltip.appendChild(arrowElement) | |
}) | |
const enter = async () => { | |
cleanupPosition = await computePosition(el, tooltip, { | |
placement: 'top', | |
middleware: [offset(10), flip(), arrow({ element: arrowElement })] | |
}).then(({ x, y, middlewareData, placement }) => { | |
Object.assign(tooltip.style, { left: `${x}px`, top: `${y}px` }) | |
tooltip.classList.toggle('opacity-0', false) | |
tooltip.classList.toggle('opacity-80', true) | |
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]: '-5px', | |
}); | |
}) | |
} | |
const exit = () => { | |
tooltip.classList.toggle('opacity-80', false) | |
tooltip.classList.toggle('opacity-0', true) | |
} | |
el.addEventListener('mouseenter', enter) | |
el.addEventListener('mouseleave', exit) | |
cleanup(() => { | |
el.removeEventListener('mouseenter', enter) | |
el.removeEventListener('mouseleave', exit) | |
cleanupPosition && cleanupPosition() | |
}) | |
}) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Floating UI is a positioning engine that can help you position stuff like tooltips precisely. It is particularly useful to make sure elements are always visible. In this case, we're configuring it to flip the placement of the tooltip when it overflows the bounds of the viewport. Happy coding!
Use this directive by applying like this:
Some assumptions:
npm install @floating-ui/dom