Skip to content

Commit

Permalink
feat: add ability to drag to scroll for the Carousel component
Browse files Browse the repository at this point in the history
  • Loading branch information
bacali95 committed Apr 19, 2022
1 parent b81e074 commit eb91078
Show file tree
Hide file tree
Showing 3 changed files with 104 additions and 623 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@
"dependencies": {
"@popperjs/core": "^2.11.5",
"classnames": "^2.3.1",
"react-icons": "^4.3.1"
"react-icons": "^4.3.1",
"react-indiana-drag-scroll": "^2.1.0"
},
"peerDependencies": {
"flowbite": "^1",
Expand Down
48 changes: 31 additions & 17 deletions src/components/Carousel/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ import {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import classNames from 'classnames';
import { HiOutlineChevronLeft, HiOutlineChevronRight } from 'react-icons/hi';
import ScrollContainer from 'react-indiana-drag-scroll';

export type CarouselProps = PropsWithChildren<{
slide?: boolean;
Expand All @@ -31,14 +33,17 @@ export const Carousel: FC<CarouselProps> = ({
rightControl,
}) => {
const [activeItem, setActiveItem] = useState(0);
const [isDragging, setIsDragging] = useState(false);
const carouselContainer = useRef<HTMLDivElement>(null);
const isDeviceMobile = typeof window.orientation !== 'undefined' || navigator.userAgent.indexOf('IEMobile') !== -1;

const items = useMemo(
() =>
Children.map(children as ReactElement<ComponentProps<'img'>>[], (child: ReactElement<ComponentProps<'img'>>) =>
cloneElement(child, {
className: classNames(
child.props.className,
'block absolute top-1/2 left-1/2 w-full -translate-x-1/2 -translate-y-1/2',
'absolute top-1/2 left-1/2 block w-full -translate-x-1/2 -translate-y-1/2',
),
}),
),
Expand All @@ -48,41 +53,50 @@ export const Carousel: FC<CarouselProps> = ({
const navigateTo = useCallback(
(item: number) => () => {
item = (item + items.length) % items.length;
if (carouselContainer.current) {
carouselContainer.current.scrollLeft = carouselContainer.current.clientWidth * item;
}
setActiveItem(item);
},
[items.length],
);

const isAfterActiveItem = (item: number) =>
item !== activeItem && (activeItem === items.length - 1 ? item === 0 : item - 1 === activeItem);
const isBeforeActiveItem = (item: number) =>
item !== activeItem && (activeItem === 0 ? item === items.length - 1 : item + 1 === activeItem);
useEffect(() => {
if (carouselContainer.current && !isDragging) {
setActiveItem(Math.round(carouselContainer.current.scrollLeft / carouselContainer.current.clientWidth));
}
}, [isDragging]);

useEffect(() => {
if (slide) {
const intervalId = setInterval(() => navigateTo(activeItem + 1)(), slideInterval ?? 3000);
const intervalId = setInterval(() => !isDragging && navigateTo(activeItem + 1)(), slideInterval ?? 3000);

return () => clearInterval(intervalId);
}
}, [activeItem, navigateTo, slide, slideInterval]);
}, [activeItem, isDragging, navigateTo, slide, slideInterval]);

const handleDragging = (dragging: boolean) => () => setIsDragging(dragging);

return (
<div className="relative">
{/* Carousel wrapper */}
<div className="relative h-56 overflow-hidden rounded-lg sm:h-64 xl:h-80 2xl:h-96">
<ScrollContainer
className={classNames(
'flex h-56 snap-mandatory overflow-y-hidden overflow-x-scroll scroll-smooth rounded-lg sm:h-64 xl:h-80 2xl:h-96',
{ 'snap-x': isDeviceMobile || !isDragging },
)}
draggingClassName="cursor-grab"
onStartScroll={handleDragging(true)}
onEndScroll={handleDragging(false)}
innerRef={carouselContainer}
vertical={false}
>
{items?.map((item, index) => (
<div
key={index}
className={classNames('absolute inset-0 transform transition-all duration-700 ease-in-out', {
hidden: index !== activeItem && !isBeforeActiveItem(index) && !isAfterActiveItem(index),
'-translate-x-full': isBeforeActiveItem(index),
'translate-x-full': isAfterActiveItem(index),
})}
>
<div key={index} className="w-full flex-shrink-0 transform snap-center">
{item}
</div>
))}
</div>
</ScrollContainer>

{/* Slider indicators */}
{indicators && (
Expand Down
Loading

0 comments on commit eb91078

Please sign in to comment.