import { useState, useEffect, useCallback, useContext, RefObject } from 'react'; import { IProduct } from '../types'; import { StoreContext } from '../services/store'; import { productCarouselGap } from '../components/consts'; interface UseProductCarouselProps { products: IProduct[]; containerRef: RefObject; scrollContainerRef: RefObject; } interface CarouselState { scrollPosition: number; canScrollPrev: boolean; canScrollNext: boolean; enableTransition: boolean; } interface CarouselActions { scrollPrev: () => void; scrollNext: () => void; getScrollPosition: () => number; getItemWidth: (index: number) => number; } export const useProductCarousel = ({ products, scrollContainerRef, }: UseProductCarouselProps): CarouselState & CarouselActions => { const context = useContext(StoreContext); const [scrollPosition, setScrollPosition] = useState(0); const [canScrollPrev, setCanScrollPrev] = useState(false); const [canScrollNext, setCanScrollNext] = useState(false); const [enableTransition, setEnableTransition] = useState(true); const getItemWidth = useCallback( (index: number): number => { const imageWidth = context.getProductImageWidth(products[index]?.id) || 0; const containerWidth = scrollContainerRef.current?.clientWidth || 400; if (imageWidth > 0) { return imageWidth; } // Fallback to responsive width when no image width available return Math.min(containerWidth * 0.4, 200); }, [context, products, scrollContainerRef], ); // Calculate total width of all items including gaps const getTotalWidth = useCallback((): number => { let totalWidth = 0; for (let i = 0; i < products.length; i++) { totalWidth += getItemWidth(i); if (i < products.length - 1) { totalWidth += productCarouselGap; } } return totalWidth; }, [products, getItemWidth]); // Get the width of items that fit in the current view const getViewWidth = useCallback((): number => { return scrollContainerRef.current?.clientWidth || 0; }, [scrollContainerRef]); // Find the next scroll position for true view-based scrolling const getNextScrollPosition = useCallback( (direction: 'prev' | 'next'): number => { const viewWidth = getViewWidth(); const totalWidth = getTotalWidth(); if (direction === 'prev') { // For previous: scroll back by exactly one view width const newPosition = Math.max(0, scrollPosition - viewWidth); return newPosition; } else { // For next: find the first item that's partially visible (cut off) // Then make that item the first fully visible item in the next view let accumulatedWidth = 0; const currentViewEnd = scrollPosition + viewWidth; for (let i = 0; i < products.length; i++) { const itemWidth = getItemWidth(i); const itemStart = accumulatedWidth; const itemEnd = accumulatedWidth + itemWidth; if (itemStart < currentViewEnd && itemEnd > currentViewEnd) { const maxScroll = Math.max(0, totalWidth - viewWidth); return Math.min(itemStart, maxScroll); } if (itemStart >= currentViewEnd) { const maxScroll = Math.max(0, totalWidth - viewWidth); return Math.min(itemStart, maxScroll); } accumulatedWidth += itemWidth + productCarouselGap; } // If no partially visible item found, scroll by view width const maxScroll = Math.max(0, totalWidth - viewWidth); return Math.min(scrollPosition + viewWidth, maxScroll); } }, [scrollPosition, getViewWidth, getTotalWidth, products, getItemWidth], ); const scrollPrev = useCallback(() => { const newPosition = getNextScrollPosition('prev'); setScrollPosition(newPosition); }, [getNextScrollPosition]); const scrollNext = useCallback(() => { const newPosition = getNextScrollPosition('next'); setScrollPosition(newPosition); }, [getNextScrollPosition]); const getScrollPosition = useCallback(() => { return scrollPosition; }, [scrollPosition]); useEffect(() => { const viewWidth = getViewWidth(); const totalWidth = getTotalWidth(); setCanScrollPrev(scrollPosition > 0); setCanScrollNext(scrollPosition < totalWidth - viewWidth); }, [scrollPosition, products.length, getViewWidth, getTotalWidth]); // Reset when products change useEffect(() => { setEnableTransition(false); setScrollPosition(0); const timer = setTimeout(() => setEnableTransition(true), 50); return () => clearTimeout(timer); }, [products]); return { scrollPosition, canScrollPrev, canScrollNext, enableTransition, scrollPrev, scrollNext, getScrollPosition, getItemWidth, }; };