48 lines
1.3 KiB
TypeScript
48 lines
1.3 KiB
TypeScript
"use client";
|
|
|
|
import React, { useRef } from "react";
|
|
import { type MotionValue } from "motion/react";
|
|
import { useSectionProgress } from "@/lib/scroll";
|
|
|
|
type PinProps = {
|
|
/**
|
|
* Height of the pin section in viewport heights.
|
|
* 300 means the section is 300vh tall, so the sticky area lasts for 3 screens.
|
|
*/
|
|
heightVH?: number;
|
|
className?: string;
|
|
/**
|
|
* Render prop that receives a MotionValue<number> progress [0..1]
|
|
* representing how far through the pin section the user has scrolled.
|
|
*/
|
|
children: (progress: MotionValue<number>) => React.ReactNode;
|
|
};
|
|
|
|
/**
|
|
* Pin creates a tall section with an inner sticky container.
|
|
* It computes a normalized progress [0..1] across the entire section using Lenis-driven scroll updates.
|
|
*/
|
|
export function Pin({ heightVH = 300, className, children }: PinProps) {
|
|
const sectionRef = useRef<HTMLElement>(null!);
|
|
const progress = useSectionProgress(sectionRef);
|
|
|
|
return (
|
|
<section
|
|
ref={sectionRef}
|
|
className={className}
|
|
style={{
|
|
height: `${heightVH}vh`,
|
|
position: "relative",
|
|
}}
|
|
aria-hidden={false}
|
|
>
|
|
<div
|
|
className="sticky top-0 h-screen w-full"
|
|
style={{ willChange: "transform" }}
|
|
>
|
|
{children(progress)}
|
|
</div>
|
|
</section>
|
|
);
|
|
}
|