Files
reui-blocks/blocks/hero187a.tsx
T

243 lines
7.4 KiB
TypeScript

"use client";
import Autoplay from "embla-carousel-autoplay";
import {
ArrowRight,
Blend,
ChartNoAxesColumn,
CircleDot,
Diamond,
} from "lucide-react";
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 type { HeroFeatureIconsProps, Image } from "@/lib/hero-feature-icons";
import { cn } from "@/lib/utils";
// Extended Image type with optional label for carousel indicators
type ImageWithLabel = Image & { label?: string };
const DEFAULT_FEATURES = [
{
title: "گردش‌کارهای سفارشی",
description:
"پیشرفت وظایف را در گردش‌کارهای سفارشی تیم خود مدیریت کنید.",
icon: CircleDot,
},
{
title: "نقاط عطف",
description: "پروژه‌ها را به فازهای مشخص تقسیم کنید.",
icon: Diamond,
},
{
title: "پروژه‌های بین‌تیمی",
description: "همکاری میان تیم‌ها و واحدهای مختلف را ساده‌تر کنید.",
icon: Blend,
},
{
title: "بینش پیشرفت",
description: "وضعیت پروژه، سرعت اجرا و میزان پیشرفت را به‌صورت مستمر بررسی کنید.",
icon: ChartNoAxesColumn,
},
];
const DEFAULT_IMAGES: [ImageWithLabel, ...ImageWithLabel[]] = [
{
src: "/block-media/shadcnblocks/block/placeholder-1.svg",
alt: "پیش‌نمایش کانبان",
label: "کانبان",
},
{
src: "/block-media/shadcnblocks/block/placeholder-2.svg",
alt: "پیش‌نمایش فهرست مسائل",
label: "مسائل",
},
{
src: "/block-media/shadcnblocks/block/placeholder-3.svg",
alt: "پیش‌نمایش افزودن مسئله",
label: "افزودن مسئله",
},
];
const Hero187a = ({
heading = "بلاک‌های آماده برای پروژه بعدی شما",
description =
"ابزار مناسب برای برنامه‌ریزی و ساخت محصولات نرم‌افزاری مدرن.",
buttonPrimary = { text: "شروع رایگان", href: "#" },
buttonSecondary = { text: "مستندات", href: "#" },
features = DEFAULT_FEATURES,
images = DEFAULT_IMAGES,
className,
}: HeroFeatureIconsProps) => {
const [currentSlide, setCurrentSlide] = useState(0);
const [api, setApi] = useState<CarouselApi>();
useEffect(() => {
if (!api) {
return;
}
api.on("select", () => {
setCurrentSlide(api.selectedScrollSnap());
});
}, [api]);
const activeImages = images ?? DEFAULT_IMAGES;
return (
<section className={cn("relative overflow-hidden py-32", className)}>
<div className="container">
{/* Carousel Section */}
<div className="relative mb-16">
<Carousel
className="mx-auto"
setApi={setApi}
opts={{
loop: true,
direction: "rtl",
align: "start",
}}
plugins={[Autoplay({ delay: 4000, stopOnInteraction: true })]}
>
<CarouselContent>
{activeImages.map((image, index) => (
<CarouselItem key={index}>
<img
src={image.src}
alt={image.alt}
className="aspect-video w-full rounded-2xl object-contain"
/>
</CarouselItem>
))}
</CarouselContent>
</Carousel>
<SlideIndicator
currentSlide={currentSlide}
images={activeImages}
className="mt-8"
api={api}
/>
</div>
{/* Content Section */}
<div className="space-y-12 text-center">
<div className="mx-auto max-w-4xl">
<h1 className="text-3xl font-semibold tracking-tight sm:text-xl md:text-2xll lg:text-2xll">
{heading}
</h1>
<p className="mt-6 text-xl font-medium text-muted-foreground lg:text-xl">
{description}
</p>
</div>
{/* Features */}
<div
className={cn(
"mx-auto grid max-w-6xl gap-8",
features?.length === 1 && "max-w-xs grid-cols-1",
features?.length === 2 && "max-w-2xl grid-cols-1 md:grid-cols-2",
features?.length === 3 && "max-w-4xl grid-cols-1 md:grid-cols-3",
features?.length === 4 &&
"grid-cols-1 md:grid-cols-2 lg:grid-cols-4",
(features?.length ?? 0) > 4 &&
"grid-cols-1 md:grid-cols-2 lg:grid-cols-4",
)}
>
{features?.map((feature) => {
const Icon = feature.icon;
return (
<div key={feature.title} className="space-y-4 text-center">
<div className="flex justify-center">
<Icon className="size-8 text-primary" />
</div>
<div>
<h2 className="text-lg font-semibold">{feature.title}</h2>
<p className="mt-2 text-sm text-muted-foreground">
{feature.description}
</p>
</div>
</div>
);
})}
</div>
<div className="flex flex-wrap items-center justify-center gap-4">
{buttonPrimary && (
<Button asChild size="lg" aria-label={buttonPrimary.text}>
<a href={buttonPrimary.href}>{buttonPrimary.text}</a>
</Button>
)}
{buttonSecondary && (
<Button
asChild
aria-label={buttonSecondary.text}
variant="outline"
size="lg"
>
<a href={buttonSecondary.href}>
<span className="flex items-center gap-2">
{buttonSecondary.text}
<ArrowRight className="size-4 rtl:rotate-180" />
</span>
</a>
</Button>
)}
</div>
</div>
</div>
</section>
);
};
interface SlideIndicatorProps {
currentSlide: number;
images: ImageWithLabel[];
className?: string;
api: CarouselApi | null;
}
const SlideIndicator = ({
currentSlide,
images,
className,
api,
}: SlideIndicatorProps) => {
return (
<div
className={cn("flex flex-col items-center gap-2 font-medium", className)}
>
<div className="">
<span className="text-foreground-700">
{(currentSlide + 1).toLocaleString("fa-IR")} از{" "}
{images.length.toLocaleString("fa-IR")} {" "}
</span>
<span className="text-primary">{images[currentSlide].label}</span>
</div>
<div className="flex gap-2">
{images.map((_, index) => (
<button
key={index}
onClick={() => api?.scrollTo(index)}
aria-label={`رفتن به اسلاید ${(index + 1).toLocaleString("fa-IR")}`}
className={cn(
"h-0.5 w-6 rounded-full transition-colors",
index === currentSlide
? "bg-primary"
: "bg-primary/20 hover:bg-primary/40",
)}
/>
))}
</div>
</div>
);
};
export { Hero187a };