Files
homaroma/apps/spa/src/components/DynamicEditorialCarousel.tsx
T
2026-06-21 01:03:29 -04:00

120 lines
4.7 KiB
TypeScript

import { Swiper, SwiperSlide } from 'swiper/react';
import { Autoplay, Pagination } from 'swiper/modules';
import { Link } from 'react-router-dom';
import type { EditorialCarouselSectionPayload } from '@homaroma/types';
import { useTranslation } from 'react-i18next';
import { getLocalizedField } from '../utils/localization';
// Import Swiper styles
import 'swiper/css';
import 'swiper/css/pagination';
interface DynamicEditorialCarouselProps {
payload: EditorialCarouselSectionPayload;
}
export default function DynamicEditorialCarousel({ payload }: DynamicEditorialCarouselProps) {
const { i18n } = useTranslation();
const lang = i18n.language;
const title = getLocalizedField(payload.title, lang);
const slides = (payload.slides || []).map((slide) => ({
...slide,
title: getLocalizedField(slide.title, lang),
description: getLocalizedField(slide.description, lang),
buttonText: getLocalizedField(slide.buttonText, lang),
category: getLocalizedField(slide.category, lang)
}));
if (slides.length === 0) return null;
const renderTitle = (text: string) => {
if (!text) return null;
return text.split('[').map((part, i) => {
if (part.includes(']')) {
const [accent, rest] = part.split(']');
return (
<span key={i}>
<span className="text-spa-gold font-medium italic">
{accent}
</span>
{rest}
</span>
);
}
return <span key={i}>{part}</span>;
});
};
return (
<section className="ui-dark-shell overflow-hidden">
<div className="ui-section-inner space-y-12">
<div className="space-y-3 text-center">
<h2 className="font-serif text-3xl font-light tracking-wide text-spa-gold md:text-5xl">
{renderTitle(title) || title}
</h2>
<div className="w-16 h-[1px] bg-spa-gold mx-auto"></div>
</div>
<div className="relative select-none pb-12">
<Swiper
modules={[Autoplay, Pagination]}
spaceBetween={30}
slidesPerView={1}
breakpoints={{
640: { slidesPerView: 2 },
1024: { slidesPerView: 3 }
}}
pagination={{ clickable: true, bulletActiveClass: 'swiper-pagination-bullet-active !bg-spa-gold' }}
autoplay={{ delay: 5000, disableOnInteraction: false }}
className="w-full"
>
{slides.map((slide, idx) => (
<SwiperSlide key={idx} className="h-auto flex flex-col">
<div className="ui-dark-card group flex h-full flex-col transition-all duration-300 hover:border-spa-gold/30">
<div className="aspect-[4/3] overflow-hidden relative">
<img
src={slide.imageUrl}
alt={slide.title}
className="w-full h-full object-cover transition-transform duration-700 ease-out group-hover:scale-105"
onError={(e) => {
(e.target as HTMLImageElement).style.display = 'none';
}}
/>
<div className="absolute inset-0 bg-gradient-to-t from-spa-forest/60 via-transparent to-transparent"></div>
</div>
<div className="p-6 md:p-8 flex flex-col flex-1 justify-between space-y-6">
<div className="space-y-3">
<span className="block text-[10px] font-bold uppercase tracking-[0.2em] text-spa-gold">
{slide.category}
</span>
<h3 className="font-serif text-xl font-light tracking-wide text-spa-light">
{renderTitle(slide.title) || slide.title}
</h3>
<p className="line-clamp-3 text-xs font-light leading-relaxed text-spa-light/70 md:text-sm">
{slide.description}
</p>
</div>
{slide.buttonText && slide.buttonLink && (
<div className="pt-2">
<Link
to={slide.buttonLink}
className="inline-flex items-center gap-2 text-xs font-bold uppercase tracking-widest text-spa-gold hover:text-white transition-colors group/link"
>
{slide.buttonText}
<span className="group-hover/link:translate-x-1 transition-transform">&rarr;</span>
</Link>
</div>
)}
</div>
</div>
</SwiperSlide>
))}
</Swiper>
</div>
</div>
</section>
);
}