File size: 3,484 Bytes
d530f14
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
"use client";

import { AnimatePresence, motion } from "motion/react";
import { nanoid } from "nanoid";
import { useEffect, useRef, useState } from "react";

import PortalToBody from "@/components/shared/utils/portal-to-body";
import { cn } from "@/utils/cn";

export default function Tooltip({
  delay = 0.5,
  offset = 8,
  wrapperClassName,
  className,
  ...props
}: {
  description?: string;
  children?: React.ReactNode;
  offset?: number;
  delay?: number;
  wrapperClassName?: string;
  className?: string;
}) {
  const [hovering, setHovering] = useState(false);
  const ref = useRef<HTMLDivElement>(null);

  const [bounds, setBounds] = useState<{
    x: number;
    y: number;
    width: number;
    height: number;
  } | null>(null);

  useEffect(() => {
    if (ref.current) {
      const element = ref.current;
      const parent = element.parentElement!;

      const onMouseEnter = () => {
        setBounds(parent.getBoundingClientRect());
        setHovering(true);
      };
      const onMouseLeave = () => setHovering(false);

      if (!parent) return;

      parent.addEventListener("mouseenter", onMouseEnter);
      parent.addEventListener("mouseleave", onMouseLeave);

      return () => {
        parent.removeEventListener("mouseenter", onMouseEnter);
        parent.removeEventListener("mouseleave", onMouseLeave);
      };
    }
  }, []);

  if (!props.description) return props.children;

  return (
    <div className="contents" ref={ref}>
      <PortalToBody>
        <AnimatePresence initial={false} mode="popLayout">
          {hovering && (
            <div
              className={cn(
                "fixed pointer-events-none flex-center z-[121]",
                wrapperClassName,
              )}
              style={{
                left: bounds?.x,
                top: bounds?.y,
                width: bounds?.width,
                height: bounds?.height,
              }}
            >
              <motion.div
                animate={{
                  y: 0,
                  opacity: 1,
                  filter: "blur(0px)",
                  transition: {
                    type: "spring",
                    stiffness: 240,
                    damping: 16,
                    filter: { duration: 0.4 },
                    delay,
                  },
                }}
                className={cn(
                  "py-10 px-16 rounded-12 max-w-248 absolute w-max text-body-medium text-accent-white bg-black-alpha-64 backdrop-blur-[6px] z-[121]",
                  className,
                )}
                dangerouslySetInnerHTML={{ __html: props.description }}
                exit={{
                  y: -8,
                  opacity: 0,
                  filter: "blur(4px)",
                  transition: { type: "spring", stiffness: 300, damping: 16 },
                }}
                initial={{ y: 8, opacity: 0, filter: "blur(4px)" }}
                key={nanoid()}
                style={{
                  boxShadow:
                    "0px 16px 24px -8px rgba(0, 0, 0, 0.06), 0px 8px 16px -4px rgba(0, 0, 0, 0.06)",
                  bottom: `calc(100% - ${offset}px)`,
                }}
                transition={{
                  type: "spring",
                  stiffness: 160,
                  damping: 13,
                  filter: { duration: 0.4 },
                }}
              />
            </div>
          )}
        </AnimatePresence>
      </PortalToBody>
    </div>
  );
}