Skip to content

Commit c0e94fc

Browse files
committed
feat: Implement dynamic clip-path animation for the active demo tab indicator and refine active button styling.
1 parent 8dd4611 commit c0e94fc

1 file changed

Lines changed: 34 additions & 34 deletions

File tree

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

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"use client";
22

3-
import { useState } from "react";
3+
import { useState, useRef, useEffect } from "react";
44
import { cn } from "@/lib/utils";
55
import { Copy, Check, RotateCw } from "lucide-react";
66
import { motion } from "framer-motion";
@@ -25,6 +25,38 @@ export function DemoTabs({
2525
const [isRefreshing, setIsRefreshing] = useState(false);
2626
const [isDemoing, setIsDemoing] = useState(false);
2727

28+
// Clip-path animation state
29+
const [clipPath, setClipPath] = useState("inset(0 100% 0 0 round 9999px)");
30+
const containerRef = useRef<HTMLDivElement>(null);
31+
const buttonRefs = useRef<(HTMLButtonElement | null)[]>([]);
32+
33+
useEffect(() => {
34+
const updateClipPath = () => {
35+
if (!containerRef.current) return;
36+
37+
const modeIndex = (Object.keys(modes) as DemoMode[]).indexOf(mode);
38+
const activeEl = buttonRefs.current[modeIndex];
39+
40+
if (activeEl) {
41+
const containerRect = containerRef.current.getBoundingClientRect();
42+
const activeRect = activeEl.getBoundingClientRect();
43+
44+
// Calculate relative positions
45+
const left = activeRect.left - containerRect.left;
46+
const right = containerRect.width - (left + activeRect.width);
47+
48+
setClipPath(`inset(0 ${right}px 0 ${left}px round 9999px)`);
49+
}
50+
};
51+
52+
// Initial update and on mode change
53+
updateClipPath();
54+
55+
// Update on resize
56+
window.addEventListener('resize', updateClipPath);
57+
return () => window.removeEventListener('resize', updateClipPath);
58+
}, [mode]);
59+
2860
const handleRefresh = () => {
2961
setIsRefreshing(true);
3062
setIsDemoing(true);
@@ -71,38 +103,6 @@ export function DemoTabs({
71103
setTimeout(() => setCopied(false), 2000);
72104
};
73105

74-
return (
75-
const [clipPath, setClipPath] = useState("inset(0 100% 0 0 round 9999px)");
76-
const containerRef = useRef<HTMLDivElement>(null);
77-
const buttonRefs = useRef<(HTMLButtonElement | null)[]>([]);
78-
79-
useEffect(() => {
80-
const updateClipPath = () => {
81-
if (!containerRef.current) return;
82-
83-
const modeIndex = (Object.keys(modes) as DemoMode[]).indexOf(mode);
84-
const activeEl = buttonRefs.current[modeIndex];
85-
86-
if (activeEl) {
87-
const containerRect = containerRef.current.getBoundingClientRect();
88-
const activeRect = activeEl.getBoundingClientRect();
89-
90-
// Calculate relative positions
91-
const left = activeRect.left - containerRect.left;
92-
const right = containerRect.width - (left + activeRect.width);
93-
94-
setClipPath(`inset(0 ${right}px 0 ${left}px round 9999px)`);
95-
}
96-
};
97-
98-
// Initial update and on mode change
99-
updateClipPath();
100-
101-
// Update on resize
102-
window.addEventListener('resize', updateClipPath);
103-
return () => window.removeEventListener('resize', updateClipPath);
104-
}, [mode]);
105-
106106
return (
107107
<div className="w-full max-w-5xl mx-auto">
108108
<div className="grid grid-cols-1 lg:grid-cols-2 divide-y lg:divide-y-0 lg:divide-x divide-neutral-200 dark:divide-neutral-800 border border-neutral-200 dark:border-neutral-800 rounded-xl bg-white dark:bg-neutral-950 shadow-sm overflow-hidden">
@@ -218,7 +218,7 @@ export function DemoTabs({
218218
{(Object.keys(modes) as DemoMode[]).map((key) => (
219219
<button
220220
key={`active-${key}`}
221-
className="px-4 py-2 rounded-full text-xs font-medium whitespace-nowrap text-white shadow-sm ring-1 ring-white/10"
221+
className="px-4 py-2 rounded-full text-xs font-medium whitespace-nowrap text-white"
222222
aria-hidden="true"
223223
tabIndex={-1}
224224
>

0 commit comments

Comments
 (0)