"use client";
import * as React from "react";
import {
Carousel,
CarouselContent,
CarouselSlide,
CarouselPrev,
CarouselNext,
type CarouselApi,
} from "@/components/ui/carousel";
import { Button } from "@appica/ui-react/button";
import { ChevronLeft, ChevronRight } from "@appica/icons-react";
const SLIDES = [1, 2, 3, 4, 5, 6];
const PARALLAX_FACTOR = 1.7;
export default function CarouselParallax() {
const [api, setApi] = React.useState<CarouselApi>();
React.useEffect(() => {
if (!api) return;
const layers = api
.slideNodes()
.map((slide) =>
slide.querySelector<HTMLElement>("[data-parallax-layer]"),
);
const tween = (_api: CarouselApi, event?: { type: string }) => {
const engine = api.internalEngine();
const scrollProgress = api.scrollProgress();
const slidesInView = api.slidesInView();
const isScroll = event?.type === "scroll";
api.snapList().forEach((snap, snapIndex) => {
let diffToTarget = snap - scrollProgress;
const slidesInSnap =
engine.scrollSnapList.slidesBySnap[snapIndex] ?? [];
slidesInSnap.forEach((slideIndex) => {
if (isScroll && !slidesInView.includes(slideIndex)) return;
if (engine.options.loop) {
engine.slideLooper.loopPoints.forEach((loopItem) => {
const target = loopItem.target();
if (slideIndex === loopItem.index && target !== 0) {
const sign = Math.sign(target);
if (sign === -1) diffToTarget = snap - (1 + scrollProgress);
if (sign === 1) diffToTarget = snap + (1 - scrollProgress);
}
});
}
const translate = diffToTarget * (-1 * PARALLAX_FACTOR) * 100;
const layer = layers[slideIndex];
if (layer) layer.style.transform = `translateX(${translate}%)`;
});
});
};
tween(api);
api.on("scroll", tween);
api.on("slidefocus", tween);
api.on("reinit", tween);
return () => {
api.off("scroll", tween);
api.off("slidefocus", tween);
api.off("reinit", tween);
};
}, [api]);
return (
<div className="w-full max-w-xl">
<Carousel loop align="center" setApi={setApi}>
<CarouselContent>
{SLIDES.map((n) => (
<CarouselSlide key={n} className="basis-2/3">
<div className="aspect-3/2 overflow-hidden rounded-xl">
<div data-parallax-layer className="size-full">
<img
src={`https://appica.dev/carousel/slide-${n}.jpg`}
alt={`Slide ${n}`}
className="size-full scale-[1.6] object-cover"
/>
</div>
</div>
</CarouselSlide>
))}
</CarouselContent>
<div className="mt-5 flex justify-center gap-2">
<CarouselPrev
position="none"
render={
<Button variant="outline" size="icon-md">
<ChevronLeft />
</Button>
}
/>
<CarouselNext
position="none"
render={
<Button variant="outline" size="icon-md">
<ChevronRight />
</Button>
}
/>
</div>
</Carousel>
</div>
);
}