Files
sitemente/components/site-mente/HeroSlider.tsx
T
Haitham Khalifa 11252e6520 Initial commit
2026-02-16 12:02:45 +01:00

175 lines
5.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import Image from "next/image";
import { useEffect, useMemo, useRef, useState } from "react";
type ImageSlide = {
type: "image";
id: string;
src: string;
alt: string;
};
type StatItem = {
headline: string;
description: string;
};
type StatsSlide = {
type: "stats";
id: string;
title: string;
items: StatItem[];
};
export type HeroSlide = ImageSlide | StatsSlide;
type HeroSliderProps = {
slides: HeroSlide[];
autoAdvanceMs?: number;
};
export default function HeroSlider({
slides,
autoAdvanceMs = 7000,
}: HeroSliderProps) {
const [activeIndex, setActiveIndex] = useState(0);
const [isPaused, setIsPaused] = useState(false);
const touchStartX = useRef<number | null>(null);
const touchDelta = useRef(0);
const maxIndex = slides.length - 1;
const goTo = (index: number) => {
if (index < 0) return setActiveIndex(maxIndex);
if (index > maxIndex) return setActiveIndex(0);
return setActiveIndex(index);
};
const next = () => goTo(activeIndex + 1);
const prev = () => goTo(activeIndex - 1);
const currentSlide = useMemo(() => slides[activeIndex], [slides, activeIndex]);
useEffect(() => {
if (isPaused || slides.length <= 1) return undefined;
const timer = window.setInterval(() => {
setActiveIndex((prevIndex) => (prevIndex + 1) % slides.length);
}, autoAdvanceMs);
return () => window.clearInterval(timer);
}, [autoAdvanceMs, isPaused, slides.length]);
const handleTouchStart = (event: React.TouchEvent<HTMLDivElement>) => {
touchStartX.current = event.touches[0]?.clientX ?? null;
touchDelta.current = 0;
};
const handleTouchMove = (event: React.TouchEvent<HTMLDivElement>) => {
if (touchStartX.current === null) return;
touchDelta.current =
(event.touches[0]?.clientX ?? touchStartX.current) - touchStartX.current;
};
const handleTouchEnd = () => {
const delta = touchDelta.current;
touchStartX.current = null;
touchDelta.current = 0;
if (Math.abs(delta) < 40) return;
if (delta > 0) prev();
if (delta < 0) next();
};
return (
<section
className="w-full"
onMouseEnter={() => setIsPaused(true)}
onMouseLeave={() => setIsPaused(false)}
onTouchStart={handleTouchStart}
onTouchMove={handleTouchMove}
onTouchEnd={handleTouchEnd}
>
<div className="relative h-[320px] w-full overflow-hidden sm:h-[420px] lg:h-[560px]">
{currentSlide.type === "image" ? (
<Image
src={currentSlide.src}
alt={currentSlide.alt}
fill
sizes="100vw"
priority
className="object-cover"
/>
) : (
<div className="flex h-full w-full items-center justify-center bg-brand-purple-dark px-6">
<div className="mx-auto w-full max-w-5xl">
<p className="text-center text-sm font-semibold uppercase tracking-[0.2em] text-white/70">
{currentSlide.title}
</p>
<div className="mt-8 grid gap-6 text-center md:grid-cols-3">
{currentSlide.items.map((item) => (
<div
key={item.headline}
className="rounded-2xl border border-white/15 bg-white/10 p-6 backdrop-blur"
>
<p className="text-3xl font-bold text-brand-pink sm:text-4xl">
{item.headline}
</p>
<p className="mt-3 text-sm text-white/85">
{item.description}
</p>
</div>
))}
</div>
</div>
</div>
)}
<div className="pointer-events-none absolute inset-x-0 bottom-4 flex items-center justify-center gap-2">
{slides.map((slide, index) => (
<button
key={slide.id}
className={`pointer-events-auto h-2.5 w-2.5 rounded-full transition ${
index === activeIndex
? "bg-brand-pink"
: "bg-white/50 hover:bg-white"
}`}
onClick={() => goTo(index)}
aria-label={`Go to slide ${index + 1}`}
/>
))}
</div>
{slides.length > 1 && (
<>
<button
className="absolute left-4 top-1/2 hidden -translate-y-1/2 rounded-full border border-white/30 bg-white/10 px-3 py-2 text-sm font-semibold text-white transition hover:bg-white/20 md:block"
onClick={prev}
aria-label="Previous slide"
>
</button>
<button
className="absolute right-4 top-1/2 hidden -translate-y-1/2 rounded-full border border-white/30 bg-white/10 px-3 py-2 text-sm font-semibold text-white transition hover:bg-white/20 md:block"
onClick={next}
aria-label="Next slide"
>
</button>
<button
className="absolute left-3 top-1/2 -translate-y-1/2 rounded-full border border-white/40 bg-white/10 px-3 py-2 text-sm font-semibold text-white transition hover:bg-white/20 md:hidden"
onClick={prev}
aria-label="Previous slide"
>
</button>
<button
className="absolute right-3 top-1/2 -translate-y-1/2 rounded-full border border-white/40 bg-white/10 px-3 py-2 text-sm font-semibold text-white transition hover:bg-white/20 md:hidden"
onClick={next}
aria-label="Next slide"
>
</button>
</>
)}
</div>
</section>
);
}