Files
reui-blocks/blocks/hero228.tsx
T
2026-07-23 06:55:21 +03:30

201 lines
6.2 KiB
TypeScript

"use client";
import Autoplay from "embla-carousel-autoplay";
import { AnimatePresence, motion } from "framer-motion";
import React, { useCallback, useMemo } from "react";
import {
Carousel,
type CarouselApi,
CarouselContent,
CarouselItem,
} from "@/components/ui/carousel";
import { useIsMobile } from "@/hooks/use-mobile";
import { cn } from "@/lib/utils";
interface Hero228Props {
className?: string;
}
const TESTIMONIALS = [
{
id: 1,
image: "/block-media/shadcnblocks/block/lummi/person1.jpeg",
name: "علی رضایی",
},
{
id: 2,
image: "/block-media/shadcnblocks/block/lummi/person2.jpeg",
name: "سارا محمدی",
},
{
id: 3,
image: "/block-media/shadcnblocks/block/lummi/person3.jpeg",
name: "رضا کریمی",
},
{
id: 4,
image: "/block-media/shadcnblocks/block/lummi/person4.jpeg",
name: "نرگس حسینی",
},
{
id: 5,
image: "/block-media/shadcnblocks/block/lummi/person5.jpeg",
name: "امیر نوری",
},
{
id: 6,
image: "/block-media/shadcnblocks/block/lummi/bw3.jpeg",
name: "الهام کاظمی",
},
{
id: 7,
image: "/block-media/shadcnblocks/block/lummi/bw11.jpeg",
name: "کیان موسوی",
},
] as const;
const Hero228 = ({ className }: Hero228Props) => {
const [api, setApi] = React.useState<CarouselApi>();
const [current, setCurrent] = React.useState(0);
React.useEffect(() => {
if (!api) return;
api.on("select", () => {
setCurrent(api.selectedScrollSnap());
});
}, [api]);
const isMobile = useIsMobile();
const slideCount = isMobile
? TESTIMONIALS.length
: TESTIMONIALS.length + 2;
const activeName =
TESTIMONIALS[current % TESTIMONIALS.length]?.name ?? TESTIMONIALS[0].name;
const getImageSrc = useCallback((index: number) => {
if (index === TESTIMONIALS.length) return TESTIMONIALS[0].image;
if (index === TESTIMONIALS.length + 1) return TESTIMONIALS[1].image;
if (index === TESTIMONIALS.length + 2) return TESTIMONIALS[2].image;
return TESTIMONIALS[index]?.image ?? TESTIMONIALS[0].image;
}, []);
// RTL track: current is on the physical right, then center, then left
const getRotation = useCallback(
(index: number) => {
if (index === current) {
return "md:relative md:z-0 md:translate-x-40 md:scale-75 md:rotate-45";
}
if (index === current + 1) {
return "md:relative md:z-10 md:rotate-0";
}
if (index === current + 2) {
return "md:relative md:z-0 md:-translate-x-40 md:scale-75 md:-rotate-45";
}
return "";
},
[current],
);
const scrollbarBars = useMemo(
() =>
[...Array(40)].map((_, item) => (
<motion.div
key={item}
initial={{
opacity: 0.2,
filter: "blur(1px)",
}}
animate={{
opacity: item % 5 === 0 ? 1 : 0.2,
filter: "blur(0px)",
}}
transition={{
duration: 0.2,
delay: item % 5 === 0 ? (item / 5) * 0.05 : 0,
ease: "easeOut",
}}
className={cn(
"w-px bg-foreground",
item % 5 === 0 ? "h-[15px]" : "h-[10px]",
)}
/>
)),
[],
);
return (
<section className={cn("relative w-full overflow-hidden bg-background py-32", className)}>
<div className="container flex min-w-0 flex-col items-center justify-center gap-4 text-center">
<h1 className="max-w-3xl text-balance text-2xll font-medium tracking-tighter text-foreground md:px-9 md:text-2xll">
<span className="font-playfair italic">همه</span> ابزارهای لازم برای{" "}
<span className="font-playfair italic">خلاق و</span> توسعه سریع
</h1>
<p className="mt-5 max-w-xl text-balance text-muted-foreground/80">
صدها بلاک و کامپوننت آماده مبتنی بر رابط کاربری، استایل‌دهی مدرن و ری یو آی که مستقیماً در پروژه‌های شما قابل استفاده هستند.
</p>
<Carousel
className="relative w-full min-w-0 max-w-5xl overflow-hidden"
opts={{
loop: true,
align: "start",
slidesToScroll: 1,
direction: "rtl",
}}
plugins={[
Autoplay({
delay: 1000,
stopOnInteraction: true,
}),
]}
setApi={setApi}
>
{/* Override -ml-4 / pl-4 — they break RTL track sizing */}
<CarouselContent className="-ml-0!">
{Array.from({ length: slideCount }).map((_, index) => (
<CarouselItem
key={index}
className="my-10 basis-full pl-0! md:basis-1/3"
>
<div
className={cn(
"h-105 w-full min-w-0 overflow-hidden transition-transform duration-500 ease-in-out",
getRotation(index),
)}
>
<img
src={getImageSrc(index)}
className="size-full object-cover"
alt={activeName}
/>
</div>
</CarouselItem>
))}
</CarouselContent>
<div className="absolute inset-x-0 bottom-0 flex w-full translate-y-full flex-col items-center justify-center gap-2">
<div className="flex gap-2">{scrollbarBars}</div>
<AnimatePresence mode="popLayout" initial={false}>
<motion.p
key={activeName}
className="w-full text-lg font-medium"
initial={{ opacity: 0, y: 20, scale: 0.9, filter: "blur(5px)" }}
animate={{ opacity: 1, y: 0, scale: 1, filter: "blur(0px)" }}
exit={{ opacity: 0, y: -20, scale: 0.9, filter: "blur(5px)" }}
transition={{ duration: 0.5 }}
>
{activeName}
</motion.p>
</AnimatePresence>
<div className="flex gap-2">{scrollbarBars}</div>
</div>
</Carousel>
</div>
</section>
);
};
export { Hero228 };