286 lines
9.3 KiB
TypeScript
286 lines
9.3 KiB
TypeScript
"use client";
|
|
|
|
import { 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 features = [
|
|
{
|
|
id: "feature-1",
|
|
title: "گزارشساز",
|
|
description:
|
|
"گزارش سفارشی را بدون SQL بسازید و سریع منتشر کنید.",
|
|
image: "/block-media/shadcnblocks/block/placeholder-1.svg",
|
|
subfeatures: [
|
|
{
|
|
id: "subfeature-1",
|
|
title: "Pivot تعاملی",
|
|
description:
|
|
"ابعاد مختلف را بچرخانید و الگوی پنهان را پیدا کنید.",
|
|
},
|
|
{
|
|
id: "subfeature-2",
|
|
title: "داشبورد مدیریتی",
|
|
description:
|
|
"نمای اجرایی برای تصمیمهای سریع طراحی کنید.",
|
|
},
|
|
{
|
|
id: "subfeature-3",
|
|
title: "اتصال منبع",
|
|
description:
|
|
"داده پراکنده را در یک مدل واحد ساختاردهی کنید.",
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: "feature-2",
|
|
title: "سطح دسترسی",
|
|
description: "گزارش سفارشی را بدون SQL بسازید و سریع منتشر کنید.",
|
|
image: "/block-media/shadcnblocks/block/placeholder-2.svg",
|
|
subfeatures: [
|
|
{
|
|
id: "subfeature-1",
|
|
title: "تحلیل پیشبین",
|
|
description:
|
|
"ابعاد مختلف را بچرخانید و الگوی پنهان را پیدا کنید.",
|
|
},
|
|
{
|
|
id: "subfeature-2",
|
|
title: "اشتراک گزارش",
|
|
description:
|
|
"نمای اجرایی برای تصمیمهای سریع طراحی کنید.",
|
|
},
|
|
{
|
|
id: "subfeature-3",
|
|
title: "مدل داده",
|
|
description:
|
|
"داده پراکنده را در یک مدل واحد ساختاردهی کنید.",
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: "feature-3",
|
|
title: "گزارشساز",
|
|
description: "گزارش سفارشی را بدون SQL بسازید و سریع منتشر کنید.",
|
|
image: "/block-media/shadcnblocks/block/placeholder-3.svg",
|
|
subfeatures: [
|
|
{
|
|
id: "subfeature-1",
|
|
title: "Pivot تعاملی",
|
|
description:
|
|
"ابعاد مختلف را بچرخانید و الگوی پنهان را پیدا کنید.",
|
|
},
|
|
{
|
|
id: "subfeature-2",
|
|
title: "داشبورد مدیریتی",
|
|
description:
|
|
"نمای اجرایی برای تصمیمهای سریع طراحی کنید.",
|
|
},
|
|
{
|
|
id: "subfeature-3",
|
|
title: "اتصال منبع",
|
|
description:
|
|
"داده پراکنده را در یک مدل واحد ساختاردهی کنید.",
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: "feature-4",
|
|
title: "سطح دسترسی",
|
|
description: "گزارش سفارشی را بدون SQL بسازید و سریع منتشر کنید.",
|
|
image: "/block-media/shadcnblocks/block/placeholder-4.svg",
|
|
subfeatures: [
|
|
{
|
|
id: "subfeature-1",
|
|
title: "تحلیل پیشبین",
|
|
description:
|
|
"ابعاد مختلف را بچرخانید و الگوی پنهان را پیدا کنید.",
|
|
},
|
|
{
|
|
id: "subfeature-2",
|
|
title: "اشتراک گزارش",
|
|
description:
|
|
"نمای اجرایی برای تصمیمهای سریع طراحی کنید.",
|
|
},
|
|
{
|
|
id: "subfeature-3",
|
|
title: "مدل داده",
|
|
description:
|
|
"داده پراکنده را در یک مدل واحد ساختاردهی کنید.",
|
|
},
|
|
],
|
|
},
|
|
{
|
|
id: "feature-5",
|
|
title: "گزارشساز",
|
|
description: "گزارش سفارشی را بدون SQL بسازید و سریع منتشر کنید.",
|
|
image: "/block-media/shadcnblocks/block/placeholder-5.svg",
|
|
subfeatures: [
|
|
{
|
|
id: "subfeature-1",
|
|
title: "Pivot تعاملی",
|
|
description:
|
|
"ابعاد مختلف را بچرخانید و الگوی پنهان را پیدا کنید.",
|
|
},
|
|
{
|
|
id: "subfeature-2",
|
|
title: "داشبورد مدیریتی",
|
|
description:
|
|
"نمای اجرایی برای تصمیمهای سریع طراحی کنید.",
|
|
},
|
|
{
|
|
id: "subfeature-3",
|
|
title: "اتصال منبع",
|
|
description:
|
|
"داده پراکنده را در یک مدل واحد ساختاردهی کنید.",
|
|
},
|
|
],
|
|
},
|
|
];
|
|
|
|
interface Feature54Props {
|
|
className?: string;
|
|
}
|
|
|
|
const Feature54 = ({ className }: Feature54Props) => {
|
|
const [api, setApi] = useState<CarouselApi>();
|
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
|
|
useEffect(() => {
|
|
if (!api) {
|
|
return;
|
|
}
|
|
|
|
const onSelect = () => {
|
|
setSelectedIndex(api.selectedScrollSnap());
|
|
};
|
|
|
|
onSelect();
|
|
api.on("select", onSelect);
|
|
api.on("reInit", onSelect);
|
|
|
|
return () => {
|
|
api.off("select", onSelect);
|
|
api.off("reInit", onSelect);
|
|
};
|
|
}, [api]);
|
|
|
|
return (
|
|
<section
|
|
dir="rtl"
|
|
lang="fa"
|
|
className={cn(
|
|
"relative w-full max-w-full overflow-hidden py-32",
|
|
className,
|
|
)}
|
|
>
|
|
<div className="mx-auto flex w-full min-w-0 max-w-5xl flex-col items-center px-4 md:gap-y-4 sm:px-6 lg:px-8">
|
|
<div className="flex w-full min-w-0 flex-col items-center">
|
|
<h3 className="mb-3 max-w-full text-center text-3xl font-semibold text-pretty break-words md:mb-4 md:text-xl lg:mb-6 lg:max-w-3xl lg:text-2xll">
|
|
سطح دسترسی
|
|
</h3>
|
|
</div>
|
|
|
|
<div className="mb-6 hidden w-full min-w-0 flex-col items-center md:flex lg:mb-8">
|
|
<div className="inline-flex w-fit max-w-full flex-wrap items-center justify-center gap-1 rounded-lg bg-muted p-[3px] text-muted-foreground">
|
|
{features.map((feature, index) => (
|
|
<button
|
|
key={feature.id}
|
|
type="button"
|
|
onClick={() => api?.scrollTo(index)}
|
|
className={cn(
|
|
"inline-flex h-7 items-center justify-center rounded-md px-2.5 text-sm font-medium whitespace-nowrap transition-all",
|
|
selectedIndex === index
|
|
? "bg-background text-foreground shadow-sm"
|
|
: "text-foreground/60 hover:text-foreground",
|
|
)}
|
|
>
|
|
{feature.title}
|
|
</button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="w-full min-w-0 max-w-full overflow-hidden">
|
|
<Carousel
|
|
className="w-full min-w-0 max-w-full"
|
|
setApi={setApi}
|
|
opts={{
|
|
align: "start",
|
|
direction: "rtl",
|
|
startIndex: 0,
|
|
containScroll: "trimSnaps",
|
|
}}
|
|
>
|
|
<CarouselContent className="ml-0">
|
|
{features.map((feature) => (
|
|
<CarouselItem
|
|
key={feature.id}
|
|
className="min-w-0 basis-full pl-0"
|
|
>
|
|
<div className="w-full min-w-0 max-w-full">
|
|
<div className="aspect-[5/3] w-full max-w-full overflow-hidden rounded-lg border border-border bg-accent">
|
|
<img
|
|
src={feature.image}
|
|
alt={`نمای ${feature.title}`}
|
|
className="size-full max-w-full object-cover object-center"
|
|
draggable={false}
|
|
/>
|
|
</div>
|
|
<div
|
|
dir="rtl"
|
|
className="grid w-full min-w-0 grid-cols-1 gap-6 py-6 text-start md:grid-cols-3"
|
|
>
|
|
{feature.subfeatures.map((subfeature) => (
|
|
<div
|
|
key={`${feature.id}-${subfeature.id}`}
|
|
className="flex min-w-0 flex-col items-start text-right"
|
|
>
|
|
<p className="mb-2 text-xs font-semibold break-words">
|
|
{subfeature.title}
|
|
</p>
|
|
<p className="text-xs text-pretty break-words text-muted-foreground md:mb-8">
|
|
{subfeature.description}
|
|
</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</CarouselItem>
|
|
))}
|
|
</CarouselContent>
|
|
</Carousel>
|
|
</div>
|
|
|
|
<div className="flex w-full min-w-0 justify-center py-3 md:hidden">
|
|
{features.map((feature, index) => (
|
|
<Button
|
|
key={feature.id}
|
|
variant="ghost"
|
|
size="sm"
|
|
aria-label={feature.title}
|
|
onClick={() => api?.scrollTo(index)}
|
|
>
|
|
<div
|
|
className={cn(
|
|
"size-2 rounded-full",
|
|
selectedIndex === index ? "bg-primary" : "bg-input",
|
|
)}
|
|
/>
|
|
</Button>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
);
|
|
};
|
|
|
|
export { Feature54 };
|