Files

236 lines
8.0 KiB
TypeScript

"use client";
import { motion } from "framer-motion";
import { ArrowRight } from "lucide-react";
import Image from "next/image";
import Link from "next/link";
import { useEffect, useState } from "react";
import { Button } from "@/components/ui/button";
import {
Carousel,
type CarouselApi,
CarouselContent,
CarouselItem,
} from "@/components/ui/carousel";
import { cn } from "@/lib/utils";
export interface HeroCarouselSlide {
src: string;
alt: string;
name?: string;
title?: string;
description?: string;
href?: string;
}
export interface Hero226Props {
slides: HeroCarouselSlide[];
slideCtaLabel: string;
/** برای دسترسی‌پذیری: تنها h1 معنادار صفحه (معمولاً نام برند) */
pageHeading: string;
className?: string;
}
type Props = Partial<Hero226Props>;
const defaultSlides: HeroCarouselSlide[] = [
{
src: "/assets/main-pic/banner-master-decoor.jpg",
alt: "نمونه ویترین و دکور",
title: "ویترین طلا",
description: "نمایش حرفه‌ای و مدیریت ویترین محصولات طلا.",
href: "/product?category=vitrin-tala",
},
{
src: "/assets/main-pic/banner-master-decoor.jpg",
alt: "نمونه ویترین خردسال",
title: "ویترین خردسال",
description: "راهکار ویترین و فرایندهای مرتبط با خردسال.",
href: "/product?category=vitrin-khorde-sal",
},
{
src: "/assets/main-pic/banner-master-decoor.jpg",
alt: "نمونه ویترین جواهر",
title: "ویترین جواهر",
description: "ویترین تخصصی برای جواهر و اقلام پرارزش.",
href: "/product?category=vitrin-javaher",
},
{
src: "/assets/main-pic/banner-master-decoor.jpg",
alt: "نمونه دکور فروشگاه",
title: "دکور و جعبه",
description: "ترکیبی از تنوع، ظرافت و کیفیت برای فضای فروش شما.",
href: "/about",
},
{
src: "/assets/main-pic/banner-master-decoor.jpg",
alt: "نمونه راهکار سفارشی",
title: "راهکار سفارشی",
description: "طراحی و تولید متناسب با مدل کاری مجموعه شما.",
href: "/consultation",
},
];
const defaultProps: Required<Pick<Hero226Props, "slides" | "slideCtaLabel" | "pageHeading">> = {
slides: defaultSlides,
slideCtaLabel: "مشاهده",
pageHeading: "مستر دکور",
};
function isExternalHref(href: string) {
return href.startsWith("http://") || href.startsWith("https://");
}
function SlideCtaLink({
href,
slideCtaLabel,
}: {
href: string;
slideCtaLabel: string;
}) {
const className =
"group gap-2 rounded-full border border-gold-400/55 bg-background/15 px-4 py-2 text-body font-medium tracking-tight text-gold-300 backdrop-blur-sm hover:border-gold-400/75 hover:bg-gold-950/35 hover:text-gold-200";
const icon = (
<ArrowRight
className="size-4 shrink-0 transition-transform ease-out group-hover:translate-x-0.5 rtl:rotate-180"
aria-hidden
/>
);
if (isExternalHref(href)) {
return (
<Button variant="outline" className={className} asChild>
<a href={href} target="_blank" rel="noopener noreferrer" className="inline-flex items-center">
{slideCtaLabel}
{icon}
</a>
</Button>
);
}
return (
<Button variant="outline" className={className} asChild>
<Link href={href} className="inline-flex items-center">
{slideCtaLabel}
{icon}
</Link>
</Button>
);
}
export function Hero226(props: Props) {
const [api, setApi] = useState<CarouselApi>();
const [current, setCurrent] = useState(0);
const { slides, slideCtaLabel, pageHeading, className } = {
...defaultProps,
...props,
};
useEffect(() => {
if (!api) return;
const onSelect = () => setCurrent(api.selectedScrollSnap());
onSelect();
api.on("select", onSelect);
return () => {
api.off("select", onSelect);
};
}, [api]);
return (
<section
className={cn(
"scroll-mt-20 pt-20 pb-16 md:scroll-mt-24 md:pt-24 md:pb-20 lg:pb-28",
className,
)}
aria-labelledby="home-hero-page-heading">
<h1 id="home-hero-page-heading" className="sr-only">
{pageHeading}
</h1>
<div className="mx-auto w-full max-w-7xl px-4 sm:px-6">
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.5 }}
>
<Carousel
setApi={setApi}
className="w-full"
opts={{
loop: true,
slidesToScroll: 1,
direction: "rtl",
align: "center",
}}
>
<CarouselContent className="flex w-full gap-2 sm:gap-4">
{slides.map((img, index) => (
<CarouselItem
key={`${img.src}-${index}`}
className="basis-full"
>
<div className="p-0.5 sm:p-1">
<div
className={cn(
"relative flex min-h-[min(70vh,28rem)] flex-col justify-end overflow-hidden !rounded-xl sm:min-h-[32rem] md:min-h-[36rem] lg:min-h-[42rem] lg:rounded-2xl",
"border border-border/40 bg-muted animated-gradient-border p-6 sm:p-8",
)}
>
<div className="pointer-events-none absolute inset-0">
<Image
src={img.src}
alt={img.alt}
fill
className="object-cover object-center"
sizes="(max-width: 768px) 90vw, (max-width: 1200px) 80vw, 1200px"
priority={index === 0}
draggable={false}
/>
</div>
<div className="relative z-10 flex w-full flex-col gap-4 text-start">
<div className="max-w-full space-y-3 sm:max-w-2xl lg:max-w-3xl">
{/*
هر اسلاید h2 است؛ h1 فقط sr-only بالای سکشن (استاندارد تایپوگرافی)
*/}
<h2 className="text-balance text-heading-1 font-bold tracking-tight text-foreground">
{img.title ?? ""}
</h2>
<p className="text-balance text-body leading-relaxed text-foreground/90">
{img.description ?? ""}
</p>
</div>
<div className="flex w-full flex-wrap items-center justify-between gap-3 bg-transparent pt-1">
<SlideCtaLink href={img.href ?? "/"} slideCtaLabel={slideCtaLabel} />
</div>
</div>
</div>
</div>
</CarouselItem>
))}
</CarouselContent>
<div className="mt-4 flex flex-wrap justify-center gap-2 sm:mt-6">
{slides.map((_, index) => (
<button
key={index}
type="button"
onClick={() => api?.scrollTo(index)}
className={cn(
"h-2.5 min-h-2.5 min-w-2.5 rounded-full transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background",
current === index ? "w-8 bg-primary" : "bg-muted-foreground/50 hover:bg-muted-foreground/70",
)}
aria-label={`رفتن به اسلاید ${index + 1}`}
aria-current={current === index ? "true" : undefined}
/>
))}
</div>
</Carousel>
</motion.div>
</div>
</section>
);
}