190 lines
6.8 KiB
TypeScript
190 lines
6.8 KiB
TypeScript
"use client";
|
|
|
|
import { ArrowLeft, ArrowRight } from "lucide-react";
|
|
import { startTransition, useEffect, useState } from "react";
|
|
|
|
import { Button } from "@/components/ui/button";
|
|
import type { CarouselApi } from "@/components/ui/carousel";
|
|
import {
|
|
Carousel,
|
|
CarouselContent,
|
|
CarouselItem,
|
|
} from "@/components/ui/carousel";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
const carouselItems = [
|
|
{
|
|
image:
|
|
"/block-media/external/unsplash/photo-1589100787575-fad1dcaa9d17.jpg",
|
|
title: "واکنشگرا",
|
|
description:
|
|
"قالبهای ما با رویکرد mobile-first ساخته شدهاند تا وبسایت شما در همه دستگاهها و اندازههای صفحه زیبا به نظر برسد.",
|
|
},
|
|
{
|
|
image:
|
|
"/block-media/external/unsplash/photo-1698516923130-8845104b6224.jpg",
|
|
title: "کد تمیز",
|
|
description:
|
|
"هر قالب با کد تمیز و ساختارمند و مطابق بهترین شیوهها طراحی شده تا سفارشیسازی و نگهداری ساده باشد.",
|
|
},
|
|
{
|
|
image:
|
|
"/block-media/external/unsplash/photo-1647517649508-855580038bfd.jpg",
|
|
title: "کپی و پیست",
|
|
description:
|
|
"قالبها برای کپی و پیست آسان طراحی شدهاند تا بتوانید سریعاً کامپوننتها و قابلیتها را در پروژههای خود پیاده کنید.",
|
|
},
|
|
{
|
|
image:
|
|
"/block-media/external/unsplash/photo-1586869871566-d8e41dd50318.jpg",
|
|
title: "بهروزرسانی آسان",
|
|
description:
|
|
"بهروزرسانی و نگهداری منظم تضمین میکند قالب شما همیشه با استانداردها و امنیت وب بهروز بماند.",
|
|
},
|
|
|
|
];
|
|
|
|
interface Gallery14Props {
|
|
className?: string;
|
|
}
|
|
|
|
const Gallery14 = ({ className }: Gallery14Props) => {
|
|
const [api, setApi] = useState<CarouselApi>();
|
|
const [current, setCurrent] = useState(1);
|
|
const [canScrollPrev, setCanScrollPrev] = useState(false);
|
|
const [canScrollNext, setCanScrollNext] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (!api) {
|
|
return;
|
|
}
|
|
|
|
const updateSelection = () => {
|
|
startTransition(() => {
|
|
setCurrent(api.selectedScrollSnap() + 1);
|
|
setCanScrollPrev(api.canScrollPrev());
|
|
setCanScrollNext(api.canScrollNext());
|
|
});
|
|
};
|
|
|
|
updateSelection();
|
|
api.on("select", updateSelection);
|
|
|
|
return () => {
|
|
api.off("select", updateSelection);
|
|
};
|
|
}, [api]);
|
|
|
|
useEffect(() => {
|
|
if (!api) {
|
|
return;
|
|
}
|
|
api.reInit();
|
|
}, [api]);
|
|
|
|
return (
|
|
<section
|
|
dir="rtl"
|
|
lang="fa"
|
|
className={cn("overflow-hidden py-32", className)}
|
|
>
|
|
<div className="container">
|
|
<Carousel
|
|
dir="rtl"
|
|
setApi={setApi}
|
|
opts={{
|
|
align: "start",
|
|
direction: "rtl",
|
|
}}
|
|
>
|
|
<div className="grid gap-8 md:gap-4 lg:grid-cols-2 [&>div[data-slot=carousel-content]]:overflow-visible [&>div[data-slot=carousel-content]]:[clip-path:inset(-100vw_0_-100vw_-100vw)]">
|
|
<div className="text-start">
|
|
<h2 className="text-xl font-semibold md:text-2xll">
|
|
کمتر کد بزن. <br />
|
|
<span className="text-primary/40">سریعتر بساز.</span>
|
|
</h2>
|
|
<p className="mt-8 text-xl text-primary">
|
|
با قالبهای ما شروع کنید و مطابق نیاز خود سفارشی کنید.
|
|
</p>
|
|
<div className="mt-8 hidden items-center gap-4 md:flex">
|
|
<Button
|
|
type="button"
|
|
size="icon"
|
|
variant="outline"
|
|
className="static size-12 translate-none touch-manipulation"
|
|
disabled={!canScrollPrev}
|
|
onClick={() => api?.scrollPrev()}
|
|
>
|
|
<ArrowRight className="size-4" />
|
|
<span className="sr-only">اسلاید قبلی</span>
|
|
</Button>
|
|
<Button
|
|
type="button"
|
|
size="icon"
|
|
variant="outline"
|
|
className="static size-12 translate-none touch-manipulation"
|
|
disabled={!canScrollNext}
|
|
onClick={() => api?.scrollNext()}
|
|
>
|
|
<ArrowLeft className="size-4" />
|
|
<span className="sr-only">اسلاید بعدی</span>
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<CarouselContent className="me-0 ms-0 max-w-[400px] select-none">
|
|
{carouselItems.map((item, idx) => (
|
|
<CarouselItem className="w-fit ps-4 pe-0" key={idx}>
|
|
<div className="relative aspect-4/5 max-h-[500px] rounded-2xl">
|
|
<div className="absolute inset-0 rounded-2xl bg-linear-to-b from-primary to-transparent to-40% dark:from-background" />
|
|
<img
|
|
src={item.image}
|
|
alt={item.title}
|
|
className="size-full rounded-2xl bg-cover"
|
|
/>
|
|
<div className="absolute inset-0 p-8 text-start">
|
|
<p className="text-sm font-semibold text-background/50 dark:text-foreground/50">
|
|
<span className="me-1 text-background dark:text-foreground">
|
|
{item.title}.
|
|
</span>
|
|
{item.description}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</CarouselItem>
|
|
))}
|
|
</CarouselContent>
|
|
</div>
|
|
</Carousel>
|
|
<div className="mt-8 flex items-center lg:ms-[50%]">
|
|
{Array.from({ length: carouselItems.length }).map((_, index) => (
|
|
<button
|
|
type="button"
|
|
key={index}
|
|
className={cn(
|
|
"flex h-8 cursor-pointer items-center justify-center overflow-hidden rounded-full bg-muted-foreground/15 text-xs font-semibold whitespace-nowrap transition-all duration-300",
|
|
index + 1 === current ? "w-32" : "m-4 size-4",
|
|
)}
|
|
onClick={() => api?.scrollTo(index)}
|
|
aria-label={`رفتن به ${carouselItems[index].title}`}
|
|
>
|
|
<span
|
|
className={cn(
|
|
"inline-block transition-all duration-300",
|
|
index + 1 === current
|
|
? "translate-x-0 opacity-100"
|
|
: "translate-x-6 opacity-0 rtl:-translate-x-6",
|
|
)}
|
|
>
|
|
{carouselItems[index].title}
|
|
</span>
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
};
|
|
|
|
export { Gallery14 };
|