Tooltip.jsx 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263
  1. import { h } from 'preact';
  2. import { createPortal } from 'preact/compat';
  3. import { useLayoutEffect, useRef, useState } from 'preact/hooks';
  4. const TIP_SPACE = 20;
  5. export default function Tooltip({ relativeTo, text }) {
  6. const [position, setPosition] = useState({ top: -9999, left: -9999 });
  7. const portalRoot = document.getElementById('tooltips');
  8. const ref = useRef();
  9. useLayoutEffect(() => {
  10. if (ref && ref.current && relativeTo && relativeTo.current) {
  11. const windowWidth = window.innerWidth;
  12. const {
  13. x: relativeToX,
  14. y: relativeToY,
  15. width: relativeToWidth,
  16. height: relativeToHeight,
  17. } = relativeTo.current.getBoundingClientRect();
  18. const { width: _tipWidth, height: _tipHeight } = ref.current.getBoundingClientRect();
  19. const tipWidth = _tipWidth * 1.1;
  20. const tipHeight = _tipHeight * 1.1;
  21. const left = relativeToX + Math.round(relativeToWidth / 2) + window.scrollX;
  22. const top = relativeToY + Math.round(relativeToHeight / 2) + window.scrollY;
  23. let newTop = top - TIP_SPACE - tipHeight;
  24. let newLeft = left - Math.round(tipWidth / 2);
  25. // too far right
  26. if (newLeft + tipWidth + TIP_SPACE > windowWidth - window.scrollX) {
  27. newLeft = left - tipWidth - TIP_SPACE;
  28. newTop = top - Math.round(tipHeight / 2);
  29. }
  30. // too far left
  31. else if (newLeft < TIP_SPACE + window.scrollX) {
  32. newLeft = left + TIP_SPACE;
  33. newTop = top - Math.round(tipHeight / 2);
  34. }
  35. // too close to top
  36. else if (newTop <= TIP_SPACE + window.scrollY) {
  37. newTop = top + tipHeight + TIP_SPACE;
  38. }
  39. setPosition({ left: newLeft, top: newTop });
  40. }
  41. }, [relativeTo, ref]);
  42. const tooltip = (
  43. <div
  44. role="tooltip"
  45. className={`shadow max-w-lg absolute pointer-events-none bg-gray-900 dark:bg-gray-200 bg-opacity-80 rounded px-2 py-1 transition-transform transition-opacity duration-75 transform scale-90 opacity-0 text-gray-100 dark:text-gray-900 text-sm ${
  46. position.top >= 0 ? 'opacity-100 scale-100' : ''
  47. }`}
  48. ref={ref}
  49. style={position}
  50. >
  51. {text}
  52. </div>
  53. );
  54. return portalRoot ? createPortal(tooltip, portalRoot) : tooltip;
  55. }