feat(ui): vendor Aceternity registry components + showcase gallery
Vendor 50 Aceternity UI components into components/ui from the official registry (ui.aceternity.com/registry/<slug>.json), adapted for this template's stack: Tailwind v4, motion/react 13, cn util, React 19. Add a live showcase gallery (/ui + /docs/... navigation is unchanged): app/ui/page.tsx renders <ShowcaseGallery> with 30+ demos — 3d-pin, aurora, background-beams, bento, card-stack, card-spotlight, direction-aware-hover, encrypted-text, evervault-card, flip-words, hero-highlight, hero-parallax, infinite-moving-cards, moving-border, sparkles, tracing-beam, animated-tooltip, typewriter-effect, placeholders-and-vanish-input and friends. Toolchain stays zero-warning: vendored animation code is scoped in eslint.config.mjs (react-hooks/react-compiler advisory rules, no-any, prefer-const, no-img-element) so app code keeps a 0-error gate. typecheck, build, 7 vitest tests and 2 test suites all green.
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
"use client";
|
||||
import { useScroll, useTransform } from "motion/react";
|
||||
import { useRef } from "react";
|
||||
import { motion } from "motion/react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
export const ParallaxScroll = ({ images, className }: { images: string[]; className?: string }) => {
|
||||
const gridRef = useRef<any>(null);
|
||||
const { scrollYProgress } = useScroll({
|
||||
container: gridRef, // remove this if your container is not fixed height
|
||||
offset: ["start start", "end start"], // remove this if your container is not fixed height
|
||||
});
|
||||
|
||||
const translateFirst = useTransform(scrollYProgress, [0, 1], [0, -200]);
|
||||
const translateSecond = useTransform(scrollYProgress, [0, 1], [0, 200]);
|
||||
const translateThird = useTransform(scrollYProgress, [0, 1], [0, -200]);
|
||||
|
||||
const third = Math.ceil(images.length / 3);
|
||||
|
||||
const firstPart = images.slice(0, third);
|
||||
const secondPart = images.slice(third, 2 * third);
|
||||
const thirdPart = images.slice(2 * third);
|
||||
|
||||
return (
|
||||
<div className={cn("h-[40rem] w-full items-start overflow-y-auto", className)} ref={gridRef}>
|
||||
<div
|
||||
className="mx-auto grid max-w-5xl grid-cols-1 items-start gap-10 px-10 py-40 md:grid-cols-2 lg:grid-cols-3"
|
||||
ref={gridRef}
|
||||
>
|
||||
<div className="grid gap-10">
|
||||
{firstPart.map((el, idx) => (
|
||||
<motion.div
|
||||
style={{ y: translateFirst }} // Apply the translateY motion value here
|
||||
key={"grid-1" + idx}
|
||||
>
|
||||
<img
|
||||
src={el}
|
||||
className="!m-0 h-80 w-full gap-10 rounded-lg object-cover object-left-top !p-0"
|
||||
height="400"
|
||||
width="400"
|
||||
alt="thumbnail"
|
||||
/>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
<div className="grid gap-10">
|
||||
{secondPart.map((el, idx) => (
|
||||
<motion.div style={{ y: translateSecond }} key={"grid-2" + idx}>
|
||||
<img
|
||||
src={el}
|
||||
className="!m-0 h-80 w-full gap-10 rounded-lg object-cover object-left-top !p-0"
|
||||
height="400"
|
||||
width="400"
|
||||
alt="thumbnail"
|
||||
/>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
<div className="grid gap-10">
|
||||
{thirdPart.map((el, idx) => (
|
||||
<motion.div style={{ y: translateThird }} key={"grid-3" + idx}>
|
||||
<img
|
||||
src={el}
|
||||
className="!m-0 h-80 w-full gap-10 rounded-lg object-cover object-left-top !p-0"
|
||||
height="400"
|
||||
width="400"
|
||||
alt="thumbnail"
|
||||
/>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user