pkmntrade.club/theme/static/js/tooltip.js

123 lines
No EOL
4.6 KiB
JavaScript

/**
* tooltip.js
*
* This script uses FloatingUI to create modern, styled tooltips for elements with the
* custom attribute "data-tooltip-html". The tooltips are styled using Tailwind CSS classes
* to support both light and dark themes and include a dynamically positioned arrow.
*
* Make sure the FloatingUIDOM global is available.
* For example, include in your base template:
* <script src="https://unpkg.com/@floating-ui/dom"></script>
*/
const { computePosition, offset, flip, shift, arrow } = FloatingUIDOM;
document.addEventListener('DOMContentLoaded', () => {
document.querySelectorAll('[data-tooltip-html]').forEach((el) => {
let tooltipContainer = null;
let arrowElement = null;
let fadeOutTimeout;
const showTooltip = () => {
if (tooltipContainer) return; // Tooltip already visible
// Retrieve the custom HTML content from the data attribute
const tooltipContent = el.getAttribute('data-tooltip-html');
// Create a container for the tooltip (with modern styling)
tooltipContainer = document.createElement('div');
tooltipContainer.classList.add(
'bg-black', 'text-white',
'shadow-lg', 'rounded-lg', 'p-2',
// Transition classes for simple fade in/out
'transition-opacity', 'duration-200', 'opacity-0'
);
tooltipContainer.style.position = 'absolute';
tooltipContainer.style.zIndex = '9999';
// Set the HTML content for the tooltip
tooltipContainer.innerHTML = '<div class="p-2">' + tooltipContent + '</div>';
// Create the arrow element. The arrow is styled as a small rotated square.
arrowElement = document.createElement('div');
arrowElement.classList.add(
'w-3', 'h-3',
'bg-black',
'transform', 'rotate-45'
);
arrowElement.style.position = 'absolute';
// Append the arrow into the tooltip container
tooltipContainer.appendChild(arrowElement);
// Append the tooltip container to the document body
document.body.appendChild(tooltipContainer);
// Use Floating UI to position the tooltip, including the arrow middleware
computePosition(el, tooltipContainer, {
middleware: [
offset(8),
flip(),
shift({ padding: 5 }),
arrow({ element: arrowElement })
]
}).then(({ x, y, placement, middlewareData }) => {
Object.assign(tooltipContainer.style, {
left: `${x}px`,
top: `${y}px`
});
// Position the arrow using the arrow middleware data
const { x: arrowX, y: arrowY } = middlewareData.arrow || {};
// Reset any previous inline values
arrowElement.style.left = '';
arrowElement.style.top = '';
arrowElement.style.right = '';
arrowElement.style.bottom = '';
// Adjust the arrow's position according to the placement
if (placement.startsWith('top')) {
arrowElement.style.bottom = '-4px';
arrowElement.style.left = arrowX !== undefined ? `${arrowX}px` : '50%';
} else if (placement.startsWith('bottom')) {
arrowElement.style.top = '-4px';
arrowElement.style.left = arrowX !== undefined ? `${arrowX}px` : '50%';
} else if (placement.startsWith('left')) {
arrowElement.style.right = '-4px';
arrowElement.style.top = arrowY !== undefined ? `${arrowY}px` : '50%';
} else if (placement.startsWith('right')) {
arrowElement.style.left = '-4px';
arrowElement.style.top = arrowY !== undefined ? `${arrowY}px` : '50%';
}
});
// Trigger a fade-in by moving from opacity-0 to opacity-100
requestAnimationFrame(() => {
tooltipContainer.classList.remove('opacity-0');
tooltipContainer.classList.add('opacity-100');
});
};
const hideTooltip = () => {
if (tooltipContainer) {
tooltipContainer.classList.remove('opacity-100');
tooltipContainer.classList.add('opacity-0');
// Remove the tooltip from the DOM after the transition duration
fadeOutTimeout = setTimeout(() => {
if (tooltipContainer && tooltipContainer.parentNode) {
tooltipContainer.parentNode.removeChild(tooltipContainer);
}
tooltipContainer = null;
arrowElement = null;
}, 200); // Matches the duration-200 class (200ms)
}
};
// Attach event listeners to show/hide the tooltip
el.addEventListener('mouseenter', showTooltip);
el.addEventListener('mouseleave', hideTooltip);
el.addEventListener('focus', showTooltip);
el.addEventListener('blur', hideTooltip);
});
});