ldc-qualimaine/resources/ts/front/Components/EngagementSection.tsx
Abdel LAMRANI 7baa1c18e4 fix: 🐛 eslint
2026-08-07 13:40:17 +02:00

207 lines
7.8 KiB
TypeScript

import { ChevronLeft, ChevronRight } from "lucide-react";
import { useEffect, useRef, useState } from "react";
import alimentationJpg from "&/images/engagements/alimentation.jpg";
import alimentationWebp from "&/images/engagements/alimentation.webp";
import controlJpg from "&/images/engagements/controles-independants.jpg";
import controlWebp from "&/images/engagements/controles-independants.webp";
import enrichissementsJpg from "&/images/engagements/enrichissements.jpg";
import enrichissementsWebp from "&/images/engagements/enrichissements.webp";
import espaceDeViePng from "&/images/engagements/espace-de-vie.jpg";
import espaceDeVieWebp from "&/images/engagements/espace-de-vie.webp";
import farmersJpg from "&/images/engagements/formation-eleveurs.jpg";
import farmersWebp from "&/images/engagements/formation-eleveurs.webp";
import monitoringJpg from "&/images/engagements/surveillance-renforcee.jpg";
import monitoringWebp from "&/images/engagements/surveillance-renforcee.webp";
import useDeviceType from "@/hooks/use-device-type";
import { GetTranslation } from "@/lib/utils";
const ENGAGEMENTS = [
{
webp: espaceDeVieWebp,
fallback: espaceDeViePng,
title: "front.engagements.livingSpace.title",
text: "front.engagements.livingSpace.text",
imgAlt: "front.engagements.livingSpace.imgAlt",
imgTitle: "front.engagements.livingSpace.imgTitle",
},
{
webp: enrichissementsWebp,
fallback: enrichissementsJpg,
title: "front.engagements.enrichments.title",
text: "front.engagements.enrichments.text",
imgAlt: "front.engagements.enrichments.imgAlt",
imgTitle: "front.engagements.enrichments.imgTitle",
},
{
webp: alimentationWebp,
fallback: alimentationJpg,
title: "front.engagements.feeding.title",
text: "front.engagements.feeding.text",
imgAlt: "front.engagements.feeding.imgAlt",
imgTitle: "front.engagements.feeding.imgTitle",
},
{
webp: monitoringWebp,
fallback: monitoringJpg,
title: "front.engagements.monitoring.title",
text: "front.engagements.monitoring.text",
imgAlt: "front.engagements.monitoring.imgAlt",
imgTitle: "front.engagements.monitoring.imgTitle",
},
{
webp: farmersWebp,
fallback: farmersJpg,
title: "front.engagements.trainingFarmers.title",
text: "front.engagements.trainingFarmers.text",
imgAlt: "front.engagements.trainingFarmers.imgAlt",
imgTitle: "front.engagements.trainingFarmers.imgTitle",
},
{
webp: controlWebp,
fallback: controlJpg,
title: "front.engagements.control.title",
text: "front.engagements.control.text",
imgAlt: "front.engagements.control.imgAlt",
imgTitle: "front.engagements.control.imgTitle",
linkLabel: "front.engagements.control.linkLabel",
textAfter: "front.engagements.control.text_after",
linkUrl: "https://certipaq.com",
},
];
export default function EngagementsSection() {
const scrollerRef = useRef<HTMLDivElement>(null);
const cardRefs = useRef<(HTMLDivElement | null)[]>([]);
const [activeIndex, setActiveIndex] = useState(0);
const { isMobile } = useDeviceType();
const visibleCount = isMobile ? 1 : 3;
const dotsCount = Math.max(ENGAGEMENTS.length - visibleCount + 1, 1);
useEffect(() => {
const scroller = scrollerRef.current;
if (!scroller) return;
const handleScroll = () => {
const maxScroll = scroller.scrollWidth - scroller.clientWidth;
const ratio = maxScroll > 0 ? scroller.scrollLeft / maxScroll : 0;
const index = Math.round(ratio * (dotsCount - 1));
setActiveIndex(index);
};
scroller.addEventListener("scroll", handleScroll, { passive: true });
handleScroll();
return () => {
scroller.removeEventListener("scroll", handleScroll);
};
}, [dotsCount]);
const scrollToIndex = (index: number) => {
const clamped = Math.max(0, Math.min(index, dotsCount - 1));
const card = cardRefs.current[clamped];
const scroller = scrollerRef.current;
if (!card || !scroller) return;
scroller.scrollTo({ left: card.offsetLeft, behavior: "smooth" });
};
return (
<section
id={GetTranslation("front.engagements.slug")}
className="relative w-full overflow-hidden bg-white px-4 py-14 md:px-12 lg:px-[60px]"
>
<div className="mx-auto max-w-[1320px] text-center">
<h2 className="font-sans text-3xl lg:text-[32px] font-extrabold text-neutral-800 uppercase">
{GetTranslation("front.engagements.title")}
</h2>
<p className="mx-auto mt-3 max-w-[820px] font-sans text-[22px] leading-relaxed text-neutral-700">
{GetTranslation("front.engagements.subtitle")}
</p>
<div className="relative mt-10">
<button
onClick={() => {
scrollToIndex(activeIndex - 1);
}}
aria-label={GetTranslation("front.slides.previous")}
className="absolute left-0 top-[110px] cursor-pointer z-10 -translate-x-1/2 p-2.5 text-band transition-colors hover:text-black md:-left-6"
>
<ChevronLeft size={52} />
</button>
<div
ref={scrollerRef}
className="flex snap-x snap-mandatory gap-6 overflow-x-auto scroll-smooth px-4 pb-2 md:gap-6 md:px-0"
style={{ scrollbarWidth: "none" }}
>
{ENGAGEMENTS.map((item, index) => (
<div
key={item.title}
ref={(el) => {
cardRefs.current[index] = el;
}}
className="flex w-[calc(100%-0rem)] flex-none snap-center flex-col items-center gap-3 md:w-[calc((100%-4rem)/3)]"
>
<picture>
<source srcSet={item.webp} type="image/webp" />
<img
src={item.fallback}
alt={GetTranslation(item.imgAlt)}
title={GetTranslation(item.imgTitle)}
className="h-[280px] w-full rounded-2xl object-cover md:h-[220px]"
/>
</picture>
<h3 className="font-sans text-[26px] font-bold text-neutral-800">
{GetTranslation(item.title)}
</h3>
<p className="font-sans text-[18px] leading-relaxed text-neutral-700">
{GetTranslation(item.text)}{" "}
{item.linkLabel && item.textAfter && (
<>
{" "}
<a
href={item.linkUrl}
target="_blank"
rel="noreferrer noopener"
className="font-bold underline"
>
{GetTranslation(item.linkLabel)}
</a>{" "}
{GetTranslation(item.textAfter)}
</>
)}
</p>
</div>
))}
</div>
<button
onClick={() => {
scrollToIndex(activeIndex + 1);
}}
aria-label={GetTranslation("front.slides.next")}
className="absolute right-0 top-[110px] cursor-pointer z-10 translate-x-1/2 p-2.5 text-band transition-colors hover:text-black md:-right-6"
>
<ChevronRight size={52} />
</button>
</div>
<div className="mt-6 flex items-center justify-center gap-2">
{Array.from({ length: dotsCount }).map((_, index) => (
<button
key={index}
onClick={() => {
scrollToIndex(index);
}}
className={`rounded-full transition-all duration-300 ${
index === activeIndex
? "h-5 w-5 bg-neutral-400"
: "h-3.5 w-3.5 bg-band"
}`}
/>
))}
</div>
</div>
</section>
);
}