188 lines
5.7 KiB
JavaScript
188 lines
5.7 KiB
JavaScript
import React, { useEffect, useRef, useState } from "react";
|
|
import { FaChevronLeft, FaChevronRight } from "react-icons/fa";
|
|
|
|
/**
|
|
* A responsive carousel component that displays a list of children in a sliding manner.
|
|
* It supports various features such as auto-play, pause on hover, navigation buttons, and responsive design.
|
|
*
|
|
* @param {ReactNode[]} children - The list of children to be displayed in the carousel.
|
|
* @param {number} limit - The maximum number of children to be displayed in a single slide.
|
|
* @param {number} [mdLimit] - The maximum number of children to be displayed in a single slide on medium-sized screens.
|
|
* @param {number} [smLimit] - The maximum number of children to be displayed in a single slide on small-sized screens.
|
|
* @param {number} [lgLimit] - The maximum number of children to be displayed in a single slide on large-sized screens.
|
|
* @param {string} [title] - The title of the carousel.
|
|
* @param {string} [bgImg] - The background image of the carousel.
|
|
* @param {number} [autoAdvanceInterval] - The interval at which the carousel auto-advances.
|
|
* @param {boolean} [pauseOnHover] - Whether the carousel should pause on hover.
|
|
* @param {boolean} [autoPlay] - Whether the carousel should auto-play.
|
|
* @param {string} [arrowPosition] - The position of the navigation arrows.
|
|
* @param {boolean} [showArrows] - Whether to show the navigation arrows.
|
|
* @param {string} [border] - The border style of the carousel.
|
|
* @param {boolean} [showNavigation] - Whether to show the navigation buttons.
|
|
* @param {string} [gap] - The gap between the children.
|
|
* @param {string} [bg] - The background color of the carousel.
|
|
* @param {string} [px] - The padding x of the carousel.
|
|
* @param {string} [py] - The padding y of the carousel.
|
|
* @return {JSX.Element} The carousel component.
|
|
*/
|
|
|
|
const useInterval = (callback, delay) => {
|
|
const savedCallback = useRef();
|
|
|
|
useEffect(() => {
|
|
savedCallback.current = callback;
|
|
}, [callback]);
|
|
|
|
useEffect(() => {
|
|
const tick = () => {
|
|
savedCallback.current();
|
|
};
|
|
|
|
if (delay !== null) {
|
|
const id = setInterval(tick, delay);
|
|
return () => clearInterval(id);
|
|
}
|
|
}, [delay]);
|
|
};
|
|
|
|
const ResponsiveCarousel = ({
|
|
children,
|
|
limit,
|
|
mdLimit,
|
|
smLimit,
|
|
lgLimit,
|
|
title,
|
|
bgImg,
|
|
autoAdvanceInterval,
|
|
pauseOnHover,
|
|
autoPlay,
|
|
arrowPosition,
|
|
showArrows,
|
|
border,
|
|
showNavigation,
|
|
gap,
|
|
bg,
|
|
px,
|
|
py,
|
|
}) => {
|
|
const [currentSlide, setCurrentSlide] = useState(0);
|
|
const slideCount = Math.ceil(children.length / limit);
|
|
const [isPlaying, setIsPlaying] = useState(autoPlay);
|
|
const [updatedLimit, setUpdatedLimit] = useState(limit);
|
|
|
|
useEffect(() => {
|
|
const updateLimit = () => {
|
|
if (window.innerWidth >= 1280) {
|
|
setUpdatedLimit(lgLimit || limit);
|
|
} else if (window.innerWidth >= 768) {
|
|
setUpdatedLimit(mdLimit || limit);
|
|
} else {
|
|
setUpdatedLimit(smLimit || limit);
|
|
}
|
|
};
|
|
updateLimit();
|
|
window.addEventListener("resize", updateLimit);
|
|
return () => window.removeEventListener("resize", updateLimit);
|
|
}, [limit, mdLimit, smLimit, lgLimit]);
|
|
|
|
const chunkedChildren = Array.from({ length: slideCount }, (_, i) =>
|
|
children.slice(i * updatedLimit, (i + 1) * updatedLimit)
|
|
);
|
|
|
|
// console.log("border: ", border != "none" ? border + "px" : border);
|
|
const prevSlide = () => {
|
|
setCurrentSlide((prevSlide) =>
|
|
prevSlide === 0 ? slideCount - 1 : prevSlide - 1
|
|
);
|
|
};
|
|
|
|
const nextSlide = () => {
|
|
setCurrentSlide((prevSlide) =>
|
|
prevSlide === slideCount - 1 ? 0 : prevSlide + 1
|
|
);
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (autoPlay) {
|
|
const id = setInterval(() => {
|
|
if (isPlaying) {
|
|
nextSlide();
|
|
}
|
|
}, autoAdvanceInterval);
|
|
|
|
return () => clearInterval(id);
|
|
}
|
|
}, [autoPlay, autoAdvanceInterval, isPlaying]);
|
|
|
|
const handleMouseEnter = () => {
|
|
if (pauseOnHover) {
|
|
setIsPlaying(false);
|
|
}
|
|
};
|
|
const RenderButtons = () => {
|
|
return (
|
|
<div className="flex justify-center">
|
|
<button
|
|
onClick={prevSlide}
|
|
className="bg-white/40 hover:bg-gray-100 text-gray-dark font-bold py-2 px-2 "
|
|
>
|
|
<FaChevronLeft />
|
|
</button>
|
|
<button
|
|
onClick={nextSlide}
|
|
className="bg-white/40 hover:bg-gray-100 text-gray-dark font-bold py-2 px-2 "
|
|
>
|
|
<FaChevronRight />
|
|
</button>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
const handleMouseLeave = () => {
|
|
if (pauseOnHover) {
|
|
setIsPlaying(autoPlay);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<>
|
|
{title && (
|
|
<div className={"w-full text-base flex justify-between "}>
|
|
<div id="title" className="bg-white text-sm uppercase">
|
|
{title}
|
|
</div>
|
|
|
|
{showArrows && <RenderButtons position="topRight" />}
|
|
</div>
|
|
)}
|
|
<div
|
|
className={`bg-${bg} px-${px} py-${py} flex border border-${
|
|
border != "none" ? border + "px" : border
|
|
} border-gray-200 justify-evenly items-center overflow-hidden w-full relative mx-auto`}
|
|
onMouseEnter={handleMouseEnter}
|
|
onMouseLeave={handleMouseLeave}
|
|
>
|
|
<div className={`w-full flex justify-evenly gap-${gap}`}>
|
|
{chunkedChildren.map((chunk, index) => (
|
|
<div
|
|
key={index}
|
|
className={`flex w-full justify-evenly gap-${gap} transition-transform duration-500 ${
|
|
index !== currentSlide ? "absolute" : ""
|
|
} ${
|
|
index === currentSlide
|
|
? "translate-x-0"
|
|
: index < currentSlide
|
|
? "-translate-x-full"
|
|
: "translate-x-full"
|
|
}`}
|
|
>
|
|
{chunk}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</>
|
|
);
|
|
};
|
|
export default ResponsiveCarousel;
|