ldc-ovoteam/resources/ts/front/Components/Base/Carousel/CarouselContent.tsx
2026-08-04 15:57:20 +02:00

157 lines
4.5 KiB
TypeScript

/** REACT */
import { useMemo, useRef, useState } from "react";
/** SWIPER PACKAGE */
import { Swiper as SwiperType } from "swiper";
import { PaginationOptions } from "swiper/types";
import { Swiper, SwiperClass, SwiperSlide } from "swiper/react";
import { Pagination } from "swiper/modules";
import "swiper/css";
import "swiper/css/navigation";
import "swiper/css/pagination";
/** THEME */
import ItemCard from "$/uwil/ItemCard";
import {
CarouselBreakpointsProps,
CarouselPropsWithNavigation,
SwiperButtonProps,
} from "@/types/front";
import { bgColors, imageShapes } from "@/front/lib/constants";
import { cn } from "@/lib/utils";
function NextButton({ swiperRef, disabled, icon }: SwiperButtonProps) {
return (
<button
type="button"
onClick={() => swiperRef.current?.slideNext()}
disabled={disabled}
className="hidden md:block disabled:opacity-0"
>
{icon}
</button>
);
}
function PrevButton({ swiperRef, disabled, icon }: SwiperButtonProps) {
return (
<button
type="button"
onClick={() => swiperRef.current?.slidePrev()}
disabled={disabled}
className="hidden md:block disabled:opacity-0"
>
{icon}
</button>
);
}
const defaultSlidesPerView: Record<number, number> = {
768: 2,
1024: 3,
1248: 4,
};
export default function CarouselContent(props: CarouselPropsWithNavigation) {
const {
items,
carousel,
variant = bgColors.PRIMARY,
prevBtn,
nextBtn,
shape = imageShapes.ROUNDED
} = props;
const [isBeginning, setIsBeginning] = useState(true);
const [isEnd, setIsEnd] = useState(false);
const swiperRef = useRef<SwiperType | null>(null);
const spaceBetween = carousel?.spaceBetween ?? 50;
const slidesPerView = carousel?.slidesPerView ?? 1;
const defaultPagination = useMemo((): PaginationOptions => {
const variantClass =
variant === bgColors.PRIMARY ? "bg-background" : "bg-primary";
return {
clickable: true,
bulletClass: `inline-block w-3 h-3 rounded-full ${variantClass} mx-1`,
bulletActiveClass: "bg-primary-foreground w-5 h-5",
};
}, [variant]);
const breakpoints = useMemo(() => {
return Object.keys({
...defaultSlidesPerView,
...carousel?.breakpoints,
}).reduce((acc, key) => {
const numKey = Number(key);
acc[numKey] = {
...carousel?.breakpoints?.[numKey],
slidesPerView:
carousel?.breakpoints?.[numKey]?.slidesPerView ??
defaultSlidesPerView[numKey],
};
return acc;
}, {} as CarouselBreakpointsProps);
}, [carousel?.breakpoints]);
const pagination = useMemo(() => {
if (carousel?.pagination === false) return false;
return {
...defaultPagination,
...(typeof carousel?.pagination === "object" ? carousel.pagination : {}),
};
}, [carousel?.pagination, defaultPagination]);
const handleSwiper = (swiper: SwiperClass) => {
swiperRef.current = swiper;
carousel?.onSwiper?.(swiper);
};
return (
<div className="relative flex justify-center items-center w-full ">
<PrevButton icon={prevBtn} swiperRef={swiperRef} disabled={isBeginning} />
<Swiper
modules={[Pagination]}
centeredSlides={carousel?.centeredSlides}
spaceBetween={spaceBetween}
slidesPerView={slidesPerView}
onSlideChange={(swiper) => {
carousel?.onSlideChange?.(swiper);
setIsBeginning(swiper.isBeginning);
setIsEnd(swiper.isEnd || swiper.isLocked);
}}
onAfterInit={(swiper) => {
setIsBeginning(swiper.isBeginning);
setIsEnd(swiper.isEnd || swiper.isLocked);
}}
watchOverflow={carousel?.watchOverflow}
onSwiper={handleSwiper}
autoplay={carousel?.autoplay}
pagination={pagination}
navigation={carousel?.navigation}
breakpoints={breakpoints}
wrapperClass={cn("pb-20", carousel?.wrapperClass)}
className="w-full"
>
{items.map((item) => {
return (
<SwiperSlide key={Math.random()} className="justify-center !flex">
<ItemCard
image={{ lazyload: true, ...item.image }}
shape={shape}
title={item.title}
subtitle={item.subtitle}
description={item.description}
button={item.button}
variant={variant}
/>
</SwiperSlide>
);
})}
</Swiper>
<NextButton icon={nextBtn} swiperRef={swiperRef} disabled={isEnd} />
</div>
);
}