Skip to content

Commit 1990847

Browse files
committed
feat: add a new Tooltip component and integrate it into the demo tabs for refresh and copy buttons.
1 parent c0e94fc commit 1990847

2 files changed

Lines changed: 77 additions & 19 deletions

File tree

‎apps/web/src/components/demo-tabs.tsx‎

Lines changed: 24 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { motion } from "framer-motion";
77
import NextImage from "next/image";
88
import { usePlaceholder, PlaceholderImage } from "next-image-placeholder/react";
99
import { getPlaceholderAction } from "@/app/actions";
10+
import { Tooltip } from "@/components/ui/tooltip";
1011

1112
type DemoMode = "blur" | "color" | "client";
1213

@@ -41,9 +42,10 @@ export function DemoTabs({
4142
const containerRect = containerRef.current.getBoundingClientRect();
4243
const activeRect = activeEl.getBoundingClientRect();
4344

44-
// Calculate relative positions
45-
const left = activeRect.left - containerRect.left;
46-
const right = containerRect.width - (left + activeRect.width);
45+
// Calculate relative positions (accounting for container border + padding)
46+
const offset = 3; // 1px border + 2px padding (p-0.5)
47+
const left = (activeRect.left - containerRect.left) - offset;
48+
const right = (containerRect.width - (activeRect.left - containerRect.left + activeRect.width)) - offset;
4749

4850
setClipPath(`inset(0 ${right}px 0 ${left}px round 9999px)`);
4951
}
@@ -228,22 +230,25 @@ export function DemoTabs({
228230
</div>
229231
</div>
230232
<div className="flex items-center gap-1">
231-
<motion.button
232-
onClick={handleRefresh}
233-
animate={{ rotate: isRefreshing ? 360 : 0 }}
234-
transition={{ duration: 0.5, ease: "easeInOut" }}
235-
className="text-neutral-400 hover:text-white transition-colors p-2 hover:bg-neutral-800 rounded-md"
236-
title="Refresh Demo"
237-
>
238-
<RotateCw size={14} />
239-
</motion.button>
240-
<button
241-
onClick={copyCode}
242-
className="text-neutral-400 hover:text-white transition-colors p-2 hover:bg-neutral-800 rounded-md"
243-
title="Copy Code"
244-
>
245-
{copied ? <Check size={14} /> : <Copy size={14} />}
246-
</button>
233+
<Tooltip content="Refresh Demo">
234+
<motion.button
235+
onClick={handleRefresh}
236+
animate={{ rotate: isRefreshing ? 360 : 0 }}
237+
transition={{ duration: 0.5, ease: "easeInOut" }}
238+
className="text-neutral-400 hover:text-white transition-colors p-2 hover:bg-neutral-800 rounded-md"
239+
>
240+
<RotateCw size={14} />
241+
</motion.button>
242+
</Tooltip>
243+
244+
<Tooltip content="Copy Code">
245+
<button
246+
onClick={copyCode}
247+
className="text-neutral-400 hover:text-white transition-colors p-2 hover:bg-neutral-800 rounded-md"
248+
>
249+
{copied ? <Check size={14} /> : <Copy size={14} />}
250+
</button>
251+
</Tooltip>
247252
</div>
248253
</div>
249254
<div className="flex-1 p-6 overflow-x-auto custom-scrollbar">
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
"use client";
2+
3+
import { useState } from "react";
4+
import { AnimatePresence, motion } from "framer-motion";
5+
6+
export function Tooltip({
7+
children,
8+
content,
9+
delay = 0.2
10+
}: {
11+
children: React.ReactNode;
12+
content: string;
13+
delay?: number;
14+
}) {
15+
const [isVisible, setIsVisible] = useState(false);
16+
const [timeoutId, setTimeoutId] = useState<NodeJS.Timeout | null>(null);
17+
18+
const handleMouseEnter = () => {
19+
const id = setTimeout(() => setIsVisible(true), delay * 1000);
20+
setTimeoutId(id);
21+
};
22+
23+
const handleMouseLeave = () => {
24+
if (timeoutId) clearTimeout(timeoutId);
25+
setIsVisible(false);
26+
};
27+
28+
return (
29+
<div
30+
className="relative flex items-center justify-center"
31+
onMouseEnter={handleMouseEnter}
32+
onMouseLeave={handleMouseLeave}
33+
onFocus={handleMouseEnter}
34+
onBlur={handleMouseLeave}
35+
>
36+
{children}
37+
<AnimatePresence>
38+
{isVisible && (
39+
<motion.div
40+
initial={{ opacity: 0, y: 5, scale: 0.9 }}
41+
animate={{ opacity: 1, y: 0, scale: 1 }}
42+
exit={{ opacity: 0, y: 5, scale: 0.9 }}
43+
transition={{ duration: 0.15, ease: "easeOut" }}
44+
className="absolute top-full mt-2 px-2 py-1 bg-neutral-800 text-neutral-200 text-[10px] rounded-md whitespace-nowrap z-50 shadow-xl border border-neutral-700 font-medium"
45+
>
46+
{content}
47+
<div className="absolute -top-1 left-1/2 -translate-x-1/2 w-2 h-2 bg-neutral-800 rotate-45 border-t border-l border-neutral-700" />
48+
</motion.div>
49+
)}
50+
</AnimatePresence>
51+
</div>
52+
);
53+
}

0 commit comments

Comments
 (0)