236 lines
8.0 KiB
TypeScript
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>
|
|
);
|
|
}
|