From f6cfea3afe75067719c0ea7736eda2d02b2db2af Mon Sep 17 00:00:00 2001 From: Maanvi Chetwani Date: Tue, 22 Sep 2026 05:44:37 +0100 Subject: [PATCH 1/3] [Carousel] Hide prev/next arrows when there is nothing left to scroll Signed-off-by: Maanvi Chetwani --- src/custom/Carousel/Carousel.tsx | 35 ++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/src/custom/Carousel/Carousel.tsx b/src/custom/Carousel/Carousel.tsx index 50c2f05b3..ddad61bfb 100644 --- a/src/custom/Carousel/Carousel.tsx +++ b/src/custom/Carousel/Carousel.tsx @@ -1,5 +1,5 @@ import { ChevronLeft, ChevronRight } from '@mui/icons-material'; -import React, { ReactNode, useRef } from 'react'; +import React, { ReactNode, useCallback, useEffect, useRef, useState } from 'react'; import { CarouselButton, CarouselContainer, CarouselWrapper } from './style'; interface CarouselProps { @@ -10,6 +10,8 @@ interface CarouselProps { itemClassName?: string; } +const SCROLL_TOLERANCE = 1; // scrollLeft can be fractional on some displays/zoom levels + const Carousel: React.FC = ({ items, scrollAmount = 300, @@ -17,6 +19,23 @@ const Carousel: React.FC = ({ itemClassName = 'carousel-item' }) => { const carouselRef = useRef(null); + const [canScrollLeft, setCanScrollLeft] = useState(false); + const [canScrollRight, setCanScrollRight] = useState(false); + + const updateScrollState = useCallback(() => { + const el = carouselRef.current; + if (!el) return; + const maxScrollLeft = el.scrollWidth - el.clientWidth; + setCanScrollLeft(el.scrollLeft > SCROLL_TOLERANCE); + setCanScrollRight(el.scrollLeft < maxScrollLeft - SCROLL_TOLERANCE); + }, []); + + useEffect(() => { + updateScrollState(); + window.addEventListener('resize', updateScrollState); + return () => window.removeEventListener('resize', updateScrollState); + }, [updateScrollState, items.length]); + if (!items.length) return null; const scroll = (direction: 'left' | 'right') => { @@ -31,11 +50,15 @@ const Carousel: React.FC = ({ return ( {showNavButtons && ( - scroll('left')}> + scroll('left')} + style={{ visibility: canScrollLeft ? 'visible' : 'hidden' }} + > )} - + {items.map((item, index) => (
{item} @@ -43,7 +66,11 @@ const Carousel: React.FC = ({ ))} {showNavButtons && ( - scroll('right')}> + scroll('right')} + style={{ visibility: canScrollRight ? 'visible' : 'hidden' }} + > )} From b63f82e7d29225cdb7b824fe7b11b707070672cc Mon Sep 17 00:00:00 2001 From: Maanvi Chetwani Date: Tue, 22 Sep 2026 07:50:18 +0100 Subject: [PATCH 2/3] fix: recalc carousel scroll state on items reference change, not just length Signed-off-by: Maanvi Chetwani --- src/custom/Carousel/Carousel.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/custom/Carousel/Carousel.tsx b/src/custom/Carousel/Carousel.tsx index ddad61bfb..759007b58 100644 --- a/src/custom/Carousel/Carousel.tsx +++ b/src/custom/Carousel/Carousel.tsx @@ -34,7 +34,7 @@ const Carousel: React.FC = ({ updateScrollState(); window.addEventListener('resize', updateScrollState); return () => window.removeEventListener('resize', updateScrollState); - }, [updateScrollState, items.length]); + }, [updateScrollState, items]); if (!items.length) return null; From 409b0bbe4401248aefa5448a0aa464e554e9647d Mon Sep 17 00:00:00 2001 From: Maanvi Chetwani Date: Sat, 3 Oct 2026 14:26:42 +0100 Subject: [PATCH 3/3] Use ResizeObserver and clamp max scroll Signed-off-by: Maanvi Chetwani --- src/custom/Carousel/Carousel.tsx | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/custom/Carousel/Carousel.tsx b/src/custom/Carousel/Carousel.tsx index 759007b58..124c21dcd 100644 --- a/src/custom/Carousel/Carousel.tsx +++ b/src/custom/Carousel/Carousel.tsx @@ -25,16 +25,23 @@ const Carousel: React.FC = ({ const updateScrollState = useCallback(() => { const el = carouselRef.current; if (!el) return; - const maxScrollLeft = el.scrollWidth - el.clientWidth; + const maxScrollLeft = Math.max(0, el.scrollWidth - el.clientWidth); setCanScrollLeft(el.scrollLeft > SCROLL_TOLERANCE); setCanScrollRight(el.scrollLeft < maxScrollLeft - SCROLL_TOLERANCE); }, []); useEffect(() => { + const el = carouselRef.current; + if (!el) return; + updateScrollState(); - window.addEventListener('resize', updateScrollState); - return () => window.removeEventListener('resize', updateScrollState); - }, [updateScrollState, items]); + + const observer = new ResizeObserver(updateScrollState); + observer.observe(el); + Array.from(el.children).forEach((child) => observer.observe(child)); + + return () => observer.disconnect(); + }, [updateScrollState, items.length]); if (!items.length) return null;