Files
sanihome/src/components/ui/Carousel.tsx
T

108 lines
3.1 KiB
TypeScript

"use client";
import { Children, useRef, useState } from "react";
import { Swiper, SwiperSlide } from "swiper/react";
import { FreeMode } from "swiper/modules";
import type { Swiper as SwiperClass } from "swiper";
import "swiper/css";
import "swiper/css/free-mode";
type CarouselProps = {
children: React.ReactNode;
ariaLabel: string;
slideClassName?: string;
};
function ChevronLeftIcon() {
return (
<svg viewBox="0 0 24 24" fill="none" className="h-5 w-5" aria-hidden>
<path
d="M14.5 6.5 9 12l5.5 5.5"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}
function ChevronRightIcon() {
return (
<svg viewBox="0 0 24 24" fill="none" className="h-5 w-5" aria-hidden>
<path
d="M9.5 6.5 15 12l-5.5 5.5"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
}
export function Carousel({ children, ariaLabel, slideClassName }: CarouselProps) {
const swiperRef = useRef<SwiperClass | null>(null);
const [isLocked, setIsLocked] = useState(false);
const btnClass =
"absolute top-1/2 z-10 flex h-10 w-10 -translate-y-1/2 items-center justify-center rounded-full border border-white/40 bg-white/40 text-red shadow-sm backdrop-blur-md transition-colors hover:border-red/40 hover:bg-white/60 hover:text-red-dark disabled:pointer-events-none disabled:opacity-0";
return (
<div className="relative">
<button
type="button"
onClick={() => swiperRef.current?.slidePrev()}
aria-label="قبلی"
disabled={isLocked}
className={`${btnClass} right-1`}
>
<ChevronRightIcon />
</button>
<button
type="button"
onClick={() => swiperRef.current?.slideNext()}
aria-label="بعدی"
disabled={isLocked}
className={`${btnClass} left-1`}
>
<ChevronLeftIcon />
</button>
<Swiper
modules={[FreeMode]}
onSwiper={(swiper) => {
swiperRef.current = swiper;
setIsLocked(swiper.isLocked);
}}
onLock={(swiper) => {
swiper.slideTo(0, 0);
setIsLocked(true);
}}
onUnlock={() => setIsLocked(false)}
dir="rtl"
slidesPerView="auto"
spaceBetween={16}
watchOverflow
freeMode={{ enabled: true, momentum: true, sticky: true }}
grabCursor
role="region"
aria-label={ariaLabel}
className="!px-1 !py-2"
>
{Children.map(children, (child) => (
// Swiper's own CSS pins slide height to 100%, which — with no definite
// wrapper height — resolves per-slide instead of stretching to match
// the tallest sibling. height:auto (inline, so it wins) lets normal
// flex stretch equalize card heights across a row.
<SwiperSlide
className={slideClassName}
style={slideClassName ? { height: "auto" } : { width: "auto", height: "auto" }}
>
{child}
</SwiperSlide>
))}
</Swiper>
</div>
);
}