Skip to content

Commit d8b7e42

Browse files
author
Hilary017
committed
designed reuseable tooltp component
1 parent b9515bb commit d8b7e42

3 files changed

Lines changed: 100 additions & 3 deletions

File tree

app/dashboard/page.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ export default function DashboardPage() {
6363
<PluginGrid />
6464
</ErrorBoundary>
6565
</div>
66-
6766
<SendModal isOpen={sendOpen} onClose={() => setSendOpen(false)} />
6867
</DashboardLayout>
6968
);

components/MobileNav.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ const NAV_LINKS = [
1010
href: "/",
1111
},
1212
{
13-
label: "Marketplace",
14-
href: "/marketplace",
13+
label: "Dashboard",
14+
href: "/dashboard",
1515
},
1616
{
1717
label: "About",

components/Tooltip.tsx

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import {
2+
ReactNode,
3+
useEffect,
4+
useId,
5+
useRef,
6+
useState,
7+
} from "react";
8+
9+
interface TooltipProps {
10+
content: ReactNode;
11+
children: ReactNode;
12+
side?: "top" | "bottom";
13+
}
14+
15+
export default function Tooltip({
16+
content,
17+
children,
18+
side = "top",
19+
}: TooltipProps) {
20+
const id = useId();
21+
22+
const triggerRef = useRef<HTMLSpanElement>(null);
23+
24+
const [visible, setVisible] = useState(false);
25+
26+
const [position, setPosition] = useState({
27+
top: 0,
28+
left: 0,
29+
});
30+
31+
const updatePosition = () => {
32+
if (!triggerRef.current) return;
33+
34+
const rect = triggerRef.current.getBoundingClientRect();
35+
36+
const GAP = 10;
37+
38+
const top =
39+
side === "top"
40+
? rect.top - GAP
41+
: rect.bottom + GAP;
42+
43+
setPosition({
44+
top,
45+
left: rect.left + rect.width / 2,
46+
});
47+
};
48+
49+
useEffect(() => {
50+
if (!visible) return;
51+
52+
updatePosition();
53+
54+
window.addEventListener("resize", updatePosition);
55+
window.addEventListener("scroll", updatePosition, true);
56+
57+
return () => {
58+
window.removeEventListener("resize", updatePosition);
59+
window.removeEventListener("scroll", updatePosition, true);
60+
};
61+
// eslint-disable-next-line react-hooks/exhaustive-deps
62+
}, [visible]);
63+
64+
return (
65+
<>
66+
<span
67+
ref={triggerRef}
68+
tabIndex={0}
69+
aria-describedby={id}
70+
className="inline-flex focus-visible:outline focus-visible:outline-2 focus-visible:outline-cyan-400 rounded"
71+
onMouseEnter={() => setVisible(true)}
72+
onMouseLeave={() => setVisible(false)}
73+
onFocus={() => setVisible(true)}
74+
onBlur={() => setVisible(false)}
75+
>
76+
{children}
77+
</span>
78+
79+
{visible && (
80+
<div
81+
id={id}
82+
role="tooltip"
83+
className="fixed z-[9999] max-w-xs rounded-md bg-slate-900 px-3 py-2 text-xs leading-relaxed text-white shadow-xl border border-slate-700 pointer-events-none"
84+
style={{
85+
left: position.left,
86+
top: position.top,
87+
transform:
88+
side === "top"
89+
? "translate(-50%, -100%)"
90+
: "translate(-50%, 0)",
91+
}}
92+
>
93+
{content}
94+
</div>
95+
)}
96+
</>
97+
);
98+
}

0 commit comments

Comments
 (0)