162 lines
4.9 KiB
TypeScript
162 lines
4.9 KiB
TypeScript
/** REACT */
|
|
import { useMemo, useRef, useState } from "react";
|
|
/** SWIPER PACKAGE */
|
|
import { Swiper as SwiperType } from "swiper";
|
|
import "swiper/css";
|
|
import "swiper/css/navigation";
|
|
import "swiper/css/pagination";
|
|
import { Pagination } from "swiper/modules";
|
|
import { Swiper, SwiperClass, SwiperSlide } from "swiper/react";
|
|
import { PaginationOptions } from "swiper/types";
|
|
/** THEME */
|
|
import EggArrowIcon from "$/uwil/Icons/EggArrowIcon";
|
|
import ItemCard from "$/uwil/ItemCard";
|
|
import { bgColors, imageShapes } from "@/front/lib/constants";
|
|
import { cn } from "@/lib/utils";
|
|
import {
|
|
CarouselBreakpointsProps,
|
|
CarouselPropsWithNavigation,
|
|
SwiperButtonProps,
|
|
} from "@/types/front";
|
|
|
|
function NextButton({ swiperRef, disabled, icon }: SwiperButtonProps) {
|
|
return (
|
|
<button
|
|
type="button"
|
|
onClick={() => swiperRef.current?.slideNext()}
|
|
disabled={disabled}
|
|
className="hidden md:block disabled:opacity-0 z-10 hover:scale-105 transition-transform"
|
|
>
|
|
{icon ?? <EggArrowIcon direction="right" />}
|
|
</button>
|
|
);
|
|
}
|
|
|
|
function PrevButton({ swiperRef, disabled, icon }: SwiperButtonProps) {
|
|
return (
|
|
<button
|
|
type="button"
|
|
onClick={() => swiperRef.current?.slidePrev()}
|
|
disabled={disabled}
|
|
className="hidden md:block disabled:opacity-0 z-10 hover:scale-105 transition-transform"
|
|
>
|
|
{icon ?? <EggArrowIcon direction="left" />}
|
|
</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 bulletClass =
|
|
variant === bgColors.PRIMARY
|
|
? "inline-block w-3 h-3 rounded-full bg-background/90 ring-1 ring-primary-foreground/20 mx-1"
|
|
: "inline-block w-3 h-3 rounded-full bg-primary mx-1";
|
|
|
|
return {
|
|
clickable: true,
|
|
bulletClass,
|
|
bulletActiveClass: "!bg-primary-foreground w-6 h-6",
|
|
};
|
|
}, [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) => {
|
|
const slideKey = `${item.image.src}-${item.title?.text ?? item.button?.href ?? "slide"}`;
|
|
|
|
return (
|
|
<SwiperSlide key={slideKey} 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>
|
|
);
|
|
}
|