272 lines
9.1 KiB
TypeScript
272 lines
9.1 KiB
TypeScript
"use client";
|
|
|
|
import { ArrowLeft, ArrowRight } from "lucide-react";
|
|
import { useEffect, useRef, useState } from "react";
|
|
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
Carousel,
|
|
CarouselApi,
|
|
CarouselContent,
|
|
CarouselItem,
|
|
} from "@/components/ui/carousel";
|
|
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
import { cn } from "@/lib/utils";
|
|
|
|
const items = [
|
|
{
|
|
title: (
|
|
<>
|
|
<span className="bg-gradient-to-b from-foreground/20 to-muted-foreground bg-clip-text text-transparent">
|
|
کشف
|
|
</span>
|
|
<br />
|
|
ویژگیهای اصلی ما
|
|
</>
|
|
),
|
|
description: (
|
|
<>
|
|
به عمق قابلیتهای قدرتمندی بروید که برای سادهسازی گردش کار شما طراحی
|
|
شدهاند. از طراحی شهودی، یکپارچهسازی روان و گزینههای سفارشیسازی
|
|
قدرتمند بهره ببرید.
|
|
<br />
|
|
<br />
|
|
ببینید پلتفرم ما چگونه با نیازهای در حال تغییر شما سازگار میشود و
|
|
ارزش و کارایی بلندمدت را تضمین میکند.
|
|
</>
|
|
),
|
|
note: "مستندات جامع و کانالهای پشتیبانی اختصاصی برای همراهی با شما در دسترس است.",
|
|
image:
|
|
"/block-media/shadcnblocks/block/placeholder-1.svg",
|
|
category: "ویژگیها",
|
|
},
|
|
{
|
|
title: (
|
|
<>
|
|
<span className="bg-gradient-to-b from-foreground/20 to-muted-foreground bg-clip-text text-transparent">
|
|
راهکار برای
|
|
</span>
|
|
<br />
|
|
هر سناریو
|
|
</>
|
|
),
|
|
description: (
|
|
<>
|
|
<p>
|
|
ببینید پلتفرم ما چگونه چالشهای متنوع در حوزههای مختلف را پاسخ
|
|
میدهد:
|
|
</p>
|
|
<ul className="my-4 ms-6 list-disc">
|
|
<li>افزایش کارایی همکاری تیمی.</li>
|
|
<li>بهینهسازی تخصیص منابع حیاتی.</li>
|
|
<li>سادهسازی تحلیل دادههای پیچیده.</li>
|
|
</ul>
|
|
<p>ابزارهای سازگار برای بستر منحصربهفرد شما ارائه میدهیم.</p>
|
|
</>
|
|
),
|
|
note: "از تخصص ما در یکپارچهسازی و توسعه سفارشی برای نیازهای خاص بهره ببرید.",
|
|
image:
|
|
"/block-media/shadcnblocks/block/placeholder-2.svg",
|
|
category: "راهکارها",
|
|
},
|
|
{
|
|
title: (
|
|
<>
|
|
<span className="bg-gradient-to-b from-foreground/20 to-muted-foreground bg-clip-text text-transparent">
|
|
ساخت
|
|
</span>
|
|
<br />
|
|
آینده با هم
|
|
</>
|
|
),
|
|
description: (
|
|
<>
|
|
<p>
|
|
نگاهی به تعهد مداوم ما به نوآوری و بهبود بیندازید:
|
|
</p>
|
|
<ul className="my-4 ms-6 list-disc">
|
|
<li>طراحی رابط کاربری نسل بعد.</li>
|
|
<li>عرضه قابلیتهای تحلیل پیشرفته.</li>
|
|
<li>گسترش شبکه یکپارچهسازی با سرویسهای شخص ثالث.</li>
|
|
</ul>
|
|
<p>
|
|
ما بر اساس بازخورد کاربران و روندهای صنعت دائماً در حال تکامل
|
|
هستیم.
|
|
</p>
|
|
</>
|
|
),
|
|
note: "تیم تحقیق و توسعه اختصاصی ما بر ارائه راهکارهای پیشرفته متمرکز است.",
|
|
image:
|
|
"/block-media/shadcnblocks/block/placeholder-3.svg",
|
|
category: "نقشه راه",
|
|
},
|
|
];
|
|
|
|
interface Gallery16Props {
|
|
className?: string;
|
|
}
|
|
|
|
const Gallery16 = ({ className }: Gallery16Props) => {
|
|
const [api, setApi] = useState<CarouselApi>();
|
|
const [current, setCurrent] = useState(items[0].category);
|
|
const [canScrollPrev, setCanScrollPrev] = useState(false);
|
|
const [canScrollNext, setCanScrollNext] = useState(false);
|
|
const tabRefs = useRef<(HTMLButtonElement | null)[]>([]);
|
|
const [indicatorStyle, setIndicatorStyle] = useState({
|
|
width: 0,
|
|
insetInlineStart: 0,
|
|
});
|
|
|
|
useEffect(() => {
|
|
const currentIndex = items.findIndex((item) => item.category === current);
|
|
const activeTab = tabRefs.current[currentIndex];
|
|
|
|
if (activeTab) {
|
|
const { offsetWidth, offsetLeft } = activeTab;
|
|
setIndicatorStyle({
|
|
width: offsetWidth,
|
|
insetInlineStart: offsetLeft,
|
|
});
|
|
}
|
|
}, [current]);
|
|
|
|
useEffect(() => {
|
|
if (!api) {
|
|
return;
|
|
}
|
|
|
|
const currentIndex = items.findIndex((item) => item.category === current);
|
|
api.scrollTo(currentIndex);
|
|
|
|
const onSelect = () => {
|
|
const idx = api.selectedScrollSnap();
|
|
setCurrent(items[idx].category);
|
|
setCanScrollPrev(api.canScrollPrev());
|
|
setCanScrollNext(api.canScrollNext());
|
|
};
|
|
|
|
setCanScrollPrev(api.canScrollPrev());
|
|
setCanScrollNext(api.canScrollNext());
|
|
api.on("select", onSelect);
|
|
|
|
return () => {
|
|
api.off("select", onSelect);
|
|
};
|
|
}, [api, current]);
|
|
|
|
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",
|
|
}}
|
|
className="[&>div[data-slot=carousel-content]]:overflow-visible"
|
|
>
|
|
<div className="flex items-center justify-between">
|
|
<Tabs
|
|
value={current}
|
|
onValueChange={setCurrent}
|
|
className="mb-8 flex justify-center"
|
|
dir="rtl"
|
|
>
|
|
<TabsList className="relative h-auto gap-6 bg-background">
|
|
{items.map((item, idx) => (
|
|
<TabsTrigger
|
|
key={idx}
|
|
ref={(el) => {
|
|
tabRefs.current[idx] = el;
|
|
}}
|
|
value={item.category}
|
|
className="border-none !bg-background text-base transition-all duration-700 ease-out [&[data-state=active]]:shadow-none"
|
|
>
|
|
{item.category}
|
|
</TabsTrigger>
|
|
))}
|
|
<div
|
|
className="absolute bottom-0 h-0.5 bg-primary transition-all duration-700 ease-out"
|
|
style={{
|
|
width: `${indicatorStyle.width}px`,
|
|
insetInlineStart: `${indicatorStyle.insetInlineStart}px`,
|
|
}}
|
|
/>
|
|
</TabsList>
|
|
</Tabs>
|
|
<div className="hidden items-center gap-4 sm:flex">
|
|
<Button
|
|
type="button"
|
|
size="icon"
|
|
variant="outline"
|
|
className="static size-10 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-10 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-4xl">
|
|
{items.map((item, idx) => (
|
|
<CarouselItem
|
|
key={idx}
|
|
className="w-fit max-w-4xl ps-4 pe-0"
|
|
>
|
|
<div className="grid h-full max-w-4xl gap-10 rounded-xl border border-border p-6 shadow-sm select-none sm:p-10 md:max-h-[450px] md:grid-cols-2 lg:gap-20">
|
|
<div className="flex flex-col justify-between gap-4 text-start">
|
|
<div>
|
|
<h2 className="text-xl font-medium sm:text-xl">
|
|
{item.title}
|
|
</h2>
|
|
<div className="mt-4 text-sm text-muted-foreground sm:mt-6">
|
|
{item.description}
|
|
</div>
|
|
</div>
|
|
<p className="mt-4 text-xs text-muted-foreground sm:mt-6">
|
|
{item.note}
|
|
</p>
|
|
</div>
|
|
<div className="rounded-xl border border-border p-2">
|
|
<img
|
|
src={item.image}
|
|
alt={item.category}
|
|
className="h-full w-full rounded-xl object-cover"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</CarouselItem>
|
|
))}
|
|
</CarouselContent>
|
|
</Carousel>
|
|
</div>
|
|
</section>
|
|
);
|
|
};
|
|
|
|
export { Gallery16 };
|