86 lines
2.2 KiB
TypeScript
86 lines
2.2 KiB
TypeScript
"use client";
|
|
|
|
/**
|
|
* In-view reveal. Fades + lifts content when it scrolls into view (once).
|
|
* - Motion `whileInView` with a viewport margin so it triggers slightly early.
|
|
* - `stagger` cascades direct children via Motion variants.
|
|
* - Honors prefers-reduced-motion (renders fully visible, no transform).
|
|
*/
|
|
import { motion, type Variants } from "motion/react";
|
|
import { useReducedMotion } from "motion/react";
|
|
|
|
type Props = {
|
|
children: React.ReactNode;
|
|
className?: string;
|
|
delay?: number;
|
|
y?: number;
|
|
stagger?: number;
|
|
as?: "div" | "section" | "ul" | "ol" | "li" | "p" | "figure" | "header";
|
|
};
|
|
|
|
export default function Reveal({
|
|
children,
|
|
className,
|
|
delay = 0,
|
|
y = 26,
|
|
stagger,
|
|
as = "div",
|
|
}: Props) {
|
|
const reduce = useReducedMotion();
|
|
const MotionTag = motion[as] as typeof motion.div;
|
|
|
|
if (stagger) {
|
|
const parent: Variants = {
|
|
hidden: {},
|
|
show: { transition: { staggerChildren: reduce ? 0 : stagger, delayChildren: delay } },
|
|
};
|
|
return (
|
|
<MotionTag
|
|
className={className}
|
|
variants={parent}
|
|
initial="hidden"
|
|
whileInView="show"
|
|
viewport={{ once: true, margin: "0px 0px -12% 0px" }}
|
|
>
|
|
{children}
|
|
</MotionTag>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<MotionTag
|
|
className={className}
|
|
initial={reduce ? { opacity: 1 } : { opacity: 0, y }}
|
|
whileInView={{ opacity: 1, y: 0 }}
|
|
viewport={{ once: true, margin: "0px 0px -12% 0px" }}
|
|
transition={{ duration: 0.7, delay: delay / 1000, ease: [0.16, 1, 0.3, 1] }}
|
|
>
|
|
{children}
|
|
</MotionTag>
|
|
);
|
|
}
|
|
|
|
/** A child item that participates in a parent <Reveal stagger>. */
|
|
export function RevealItem({
|
|
children,
|
|
className,
|
|
y = 22,
|
|
as = "div",
|
|
}: {
|
|
children: React.ReactNode;
|
|
className?: string;
|
|
y?: number;
|
|
as?: "div" | "li" | "p";
|
|
}) {
|
|
const reduce = useReducedMotion();
|
|
const MotionTag = motion[as] as typeof motion.div;
|
|
const item: Variants = {
|
|
hidden: reduce ? { opacity: 1 } : { opacity: 0, y },
|
|
show: { opacity: 1, y: 0, transition: { duration: 0.65, ease: [0.16, 1, 0.3, 1] } },
|
|
};
|
|
return (
|
|
<MotionTag className={className} variants={item}>
|
|
{children}
|
|
</MotionTag>
|
|
);
|
|
}
|