Files
whispering-tree/components/ui/page-transition.tsx
2025-11-10 17:10:34 +05:30

158 lines
4.5 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { useEffect, useRef, useState } from "react";
import { gsap } from "gsap";
interface PageTransitionProps {
isOpen: boolean;
onComplete?: () => void;
direction?: "reveal" | "unreveal";
}
/* ------------------------------------------------------------------
1. Path data identical to the original demo
------------------------------------------------------------------ */
const paths = {
step1: {
unfilled: "M 0 100 V 100 Q 50 100 100 100 V 100 z",
inBetween: {
curve1: "M 0 100 V 50 Q 50 0 100 50 V 100 z",
curve2: "M 0 100 V 50 Q 50 100 100 50 V 100 z",
},
filled: "M 0 100 V 0 Q 50 0 100 0 V 100 z",
},
step2: {
filled: "M 0 0 V 100 Q 50 100 100 100 V 0 z",
inBetween: {
curve1: "M 0 0 V 50 Q 50 0 100 50 V 0 z",
curve2: "M 0 0 V 50 Q 50 100 100 50 V 0 z",
},
unfilled: "M 0 0 V 0 Q 50 0 100 0 V 0 z",
},
};
export const PageTransition = ({
isOpen,
onComplete,
direction = "reveal",
}: PageTransitionProps) => {
const pathRef = useRef<SVGPathElement>(null);
const svgRef = useRef<SVGSVGElement>(null); // 👈 new ref for hiding
const [animating, setAnimating] = useState(false);
/* --------------------------------------------------------------
REVEAL open second view
-------------------------------------------------------------- */
const reveal = () => {
if (animating) return;
setAnimating(true);
// 👇 Show overlay before animation starts
gsap.set(svgRef.current, { display: "block" });
const tl = gsap.timeline({
onComplete: () => {
setAnimating(false);
onComplete?.();
// 👇 Hide overlay after animation completes
gsap.set(svgRef.current, { display: "none" });
},
});
tl.set(pathRef.current, { attr: { d: paths.step1.unfilled } })
.to(pathRef.current, {
duration: 0.8,
ease: "power4.in",
attr: { d: paths.step1.inBetween.curve1 },
})
.to(pathRef.current, {
duration: 0.2,
ease: "power1",
attr: { d: paths.step1.filled },
onComplete: () => onComplete?.(),
})
.set(pathRef.current, { attr: { d: paths.step2.filled } })
.to(pathRef.current, {
duration: 0.2,
ease: "sine.in",
attr: { d: paths.step2.inBetween.curve1 },
})
.to(pathRef.current, {
duration: 1,
ease: "power4",
attr: { d: paths.step2.unfilled },
});
};
/* --------------------------------------------------------------
UNREVEAL back to first view
-------------------------------------------------------------- */
const unreveal = () => {
if (animating) return;
setAnimating(true);
// 👇 Show overlay before animation starts
gsap.set(svgRef.current, { display: "block" });
const tl = gsap.timeline({
onComplete: () => {
setAnimating(false);
onComplete?.();
// 👇 Hide overlay after animation completes
gsap.set(svgRef.current, { display: "none" });
},
});
tl.set(pathRef.current, { attr: { d: paths.step2.unfilled } })
.to(pathRef.current, {
duration: 0.8,
ease: "power4.in",
attr: { d: paths.step2.inBetween.curve2 },
})
.to(pathRef.current, {
duration: 0.2,
ease: "power1",
attr: { d: paths.step2.filled },
onComplete: () => onComplete?.(),
})
.set(pathRef.current, { attr: { d: paths.step1.filled } })
.to(pathRef.current, {
duration: 0.2,
ease: "sine.in",
attr: { d: paths.step1.inBetween.curve2 },
})
.to(pathRef.current, {
duration: 1,
ease: "power4",
attr: { d: paths.step1.unfilled },
});
};
/* --------------------------------------------------------------
React to prop changes
-------------------------------------------------------------- */
useEffect(() => {
if (!isOpen) return;
if (direction === "reveal") reveal();
else if (direction === "unreveal") unreveal();
}, [isOpen, direction]);
return (
<svg
ref={svgRef}
className="pointer-events-none fixed top-0 left-0 z-[9999] h-screen w-screen"
viewBox="0 0 100 100"
preserveAspectRatio="none"
style={{ display: "none" }} // 👈 hidden by default
>
<path
ref={pathRef}
className="overlay__path"
d="M 0 100 V 100 Q 50 100 100 100 V 100 z"
vectorEffect="non-scaling-stroke"
fill="#274b2d"
/>
</svg>
);
};