Files
website/src/components/home/HeroSlider.tsx
T

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>
);
}