209 lines
6.8 KiB
TypeScript
209 lines
6.8 KiB
TypeScript
"use client";
|
|
|
|
import Image from "next/image";
|
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
import HoverFillButton from "./HoverFillButton";
|
|
|
|
export type HeroSlideData = {
|
|
id: string;
|
|
image: string;
|
|
imagePosition?: string;
|
|
title: string;
|
|
subtitle: string;
|
|
cta: { label: string; href: string };
|
|
};
|
|
|
|
const FALLBACK_SLIDES: HeroSlideData[] = [
|
|
{
|
|
id: "autumn",
|
|
image: "/images/home/banners/hero.jpg",
|
|
imagePosition: "object-[center_20%]",
|
|
title: "برای بانویی که خودش انتخاب میکند.",
|
|
subtitle: "made for her",
|
|
cta: { label: "کالکشن پاییزه", href: "/products?collection=autumn" },
|
|
},
|
|
{
|
|
id: "new",
|
|
image: "/images/home/banners/hero.jpg",
|
|
imagePosition: "object-[center_40%]",
|
|
title: "جدیدترینهای فصل همین حالا ببینید.",
|
|
subtitle: "new arrivals",
|
|
cta: { label: "جدیدترینها", href: "/products?sort=newest" },
|
|
},
|
|
{
|
|
id: "sale",
|
|
image: "/images/home/banners/hero.jpg",
|
|
imagePosition: "object-[center_55%]",
|
|
title: "فروش ویژه تا ۷۰٪ تخفیف",
|
|
subtitle: "up to 70% off",
|
|
cta: { label: "مشاهده تخفیفها", href: "/products?sale=1" },
|
|
},
|
|
];
|
|
|
|
const AUTO_MS = 10000;
|
|
|
|
type HeroSliderProps = {
|
|
slides?: HeroSlideData[];
|
|
};
|
|
|
|
export default function HeroSlider({ slides }: HeroSliderProps) {
|
|
const list = useMemo(
|
|
() => (slides?.length ? slides : FALLBACK_SLIDES),
|
|
[slides],
|
|
);
|
|
const [index, setIndex] = useState(0);
|
|
const [textKey, setTextKey] = useState(0);
|
|
const [paused, setPaused] = useState(false);
|
|
const slide = list[index] ?? list[0];
|
|
|
|
useEffect(() => {
|
|
setIndex(0);
|
|
}, [list]);
|
|
|
|
const goTo = useCallback((next: number) => {
|
|
setIndex(next);
|
|
setTextKey((k) => k + 1);
|
|
}, []);
|
|
|
|
const goPrev = () => goTo((index - 1 + list.length) % list.length);
|
|
const goNext = () => goTo((index + 1) % list.length);
|
|
|
|
useEffect(() => {
|
|
if (paused || list.length <= 1) return;
|
|
const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
if (reduce) return;
|
|
const t = window.setInterval(() => {
|
|
setIndex((i) => (i + 1) % list.length);
|
|
setTextKey((k) => k + 1);
|
|
}, AUTO_MS);
|
|
return () => window.clearInterval(t);
|
|
}, [index, paused, list.length]);
|
|
|
|
if (!slide) return null;
|
|
|
|
return (
|
|
<section
|
|
className="relative h-[min(100dvh,1081px)] min-h-[560px] w-full overflow-hidden bg-[#ebe6df]"
|
|
onMouseEnter={() => setPaused(true)}
|
|
onMouseLeave={() => setPaused(false)}
|
|
>
|
|
{list.map((s, i) => {
|
|
const active = i === index;
|
|
return (
|
|
<div
|
|
key={s.id}
|
|
className={`absolute inset-0 transition-opacity duration-1000 ease-out ${
|
|
active ? "opacity-100" : "opacity-0"
|
|
}`}
|
|
aria-hidden={!active}
|
|
>
|
|
<Image
|
|
key={active ? `on-${s.id}-${index}` : `off-${s.id}`}
|
|
src={s.image}
|
|
alt=""
|
|
fill
|
|
priority={i === 0}
|
|
sizes="100vw"
|
|
className={`object-cover ${s.imagePosition ?? "object-center"} ${
|
|
active ? "hero-ken-burns" : ""
|
|
} ${active && paused ? "hero-ken-burns-paused" : ""}`}
|
|
/>
|
|
</div>
|
|
);
|
|
})}
|
|
|
|
<div className="absolute inset-0 bg-gradient-to-l from-white/25 via-transparent to-transparent" />
|
|
|
|
<div className="absolute inset-y-0 right-16 z-10 flex w-[min(360px,calc(100%-6.5rem))] items-center sm:right-24 lg:right-[200px]">
|
|
<div
|
|
key={textKey}
|
|
dir="rtl"
|
|
className="hero-copy-in w-full pt-16 text-right"
|
|
>
|
|
<h1 className="hero-copy-item whitespace-pre-line font-doran text-right text-[clamp(1.25rem,2.2vw,2.25rem)] font-medium leading-[1.4] text-ink">
|
|
{slide.title}
|
|
</h1>
|
|
{slide.subtitle ? (
|
|
<p
|
|
dir="ltr"
|
|
className="hero-copy-item hero-copy-item-2 mt-3 block w-full whitespace-pre-line text-right font-montserrat text-[clamp(0.875rem,1.4vw,1.35rem)] font-light uppercase tracking-[0.28em] text-[#6d6d6d]"
|
|
>
|
|
{slide.subtitle}
|
|
</p>
|
|
) : null}
|
|
{slide.cta.label ? (
|
|
<div className="hero-copy-item hero-copy-item-3">
|
|
<HoverFillButton href={slide.cta.href || "#"} className="mt-7 w-full">
|
|
{slide.cta.label}
|
|
</HoverFillButton>
|
|
</div>
|
|
) : null}
|
|
</div>
|
|
</div>
|
|
|
|
{list.length > 1 ? (
|
|
<>
|
|
<div className="pointer-events-none absolute inset-y-0 left-4 z-20 flex items-center sm:left-8 lg:left-[120px]">
|
|
<button
|
|
type="button"
|
|
aria-label="اسلاید قبلی"
|
|
onClick={goPrev}
|
|
className="pointer-events-auto flex size-10 items-center justify-center rounded-[4px] bg-white/20 backdrop-blur-sm transition hover:bg-white/40 sm:size-12"
|
|
>
|
|
<Image
|
|
src="/images/home/icons/caret-left-dark.svg"
|
|
alt=""
|
|
width={22}
|
|
height={22}
|
|
unoptimized
|
|
/>
|
|
</button>
|
|
</div>
|
|
<div className="pointer-events-none absolute inset-y-0 right-4 z-20 flex items-center sm:right-8 lg:right-[120px]">
|
|
<button
|
|
type="button"
|
|
aria-label="اسلاید بعدی"
|
|
onClick={goNext}
|
|
className="pointer-events-auto flex size-10 items-center justify-center rounded-[4px] bg-white/20 backdrop-blur-sm transition hover:bg-white/40 sm:size-12"
|
|
>
|
|
<Image
|
|
src="/images/home/icons/caret-right.svg"
|
|
alt=""
|
|
width={22}
|
|
height={22}
|
|
unoptimized
|
|
/>
|
|
</button>
|
|
</div>
|
|
|
|
<div
|
|
dir="ltr"
|
|
className="absolute inset-x-0 bottom-8 z-20 flex items-center justify-center gap-2.5 sm:bottom-12"
|
|
role="tablist"
|
|
aria-label="اسلایدهای هیرو"
|
|
>
|
|
{list.map((s, i) => {
|
|
const active = i === index;
|
|
return (
|
|
<button
|
|
key={s.id}
|
|
type="button"
|
|
role="tab"
|
|
aria-selected={active}
|
|
aria-label={`اسلاید ${i + 1}`}
|
|
onClick={() => goTo(i)}
|
|
className={`h-2 rounded-full transition-all duration-300 ${
|
|
active
|
|
? "w-8 bg-ink"
|
|
: "w-2 bg-ink/30 hover:bg-ink/50"
|
|
}`}
|
|
/>
|
|
);
|
|
})}
|
|
</div>
|
|
</>
|
|
) : null}
|
|
</section>
|
|
);
|
|
}
|