'use client'; import * as React from 'react'; import { cn } from '@djangocfg/ui-core/lib'; import { ChevronLeft, ChevronRight, ChevronUp, ChevronDown } from 'lucide-react'; import { ScrollerProvider, useScrollerContext } from '../context'; import type { ScrollerProps, ScrollerItemProps, ScrollerIndicatorProps, } from '../types'; export function Scroller({ children, orientation = 'horizontal', snap = false, snapAlign = 'start', showIndicators = false, hideScrollbar = true, className, ...props }: ScrollerProps) { const containerRef = React.useRef(null); const [canScrollPrev, setCanScrollPrev] = React.useState(false); const [canScrollNext, setCanScrollNext] = React.useState(false); const checkScroll = React.useCallback(() => { const el = containerRef.current; if (!el) return; if (orientation === 'horizontal') { setCanScrollPrev(el.scrollLeft > 0); setCanScrollNext(el.scrollLeft + el.clientWidth < el.scrollWidth - 1); } else { setCanScrollPrev(el.scrollTop > 0); setCanScrollNext(el.scrollTop + el.clientHeight < el.scrollHeight - 1); } }, [orientation]); React.useEffect(() => { const el = containerRef.current; if (!el) return; checkScroll(); el.addEventListener('scroll', checkScroll, { passive: true }); window.addEventListener('resize', checkScroll); return () => { el.removeEventListener('scroll', checkScroll); window.removeEventListener('resize', checkScroll); }; }, [checkScroll]); const scrollBy = React.useCallback( (direction: 'prev' | 'next') => { const el = containerRef.current; if (!el) return; const amount = orientation === 'horizontal' ? el.clientWidth * 0.8 : el.clientHeight * 0.8; const delta = direction === 'next' ? amount : -amount; if (orientation === 'horizontal') { el.scrollBy({ left: delta, behavior: 'smooth' }); } else { el.scrollBy({ top: delta, behavior: 'smooth' }); } }, [orientation] ); const snapStyle = snap ? { scrollSnapType: `${orientation === 'horizontal' ? 'x' : 'y'} mandatory`, } as React.CSSProperties : undefined; const value = React.useMemo( () => ({ containerRef, orientation }), [orientation] ); return (
{children}
{showIndicators && canScrollPrev && ( scrollBy('prev')} disabled={!canScrollPrev} className={cn( 'absolute z-10', orientation === 'horizontal' ? 'left-2 top-1/2 -translate-y-1/2' : 'top-2 left-1/2 -translate-x-1/2' )} /> )} {showIndicators && canScrollNext && ( scrollBy('next')} disabled={!canScrollNext} className={cn( 'absolute z-10', orientation === 'horizontal' ? 'right-2 top-1/2 -translate-y-1/2' : 'bottom-2 left-1/2 -translate-x-1/2' )} /> )}
); } Scroller.displayName = 'Scroller'; export function ScrollerItem({ children, snapAlign = 'start', className, ...props }: ScrollerItemProps) { const { orientation } = useScrollerContext(); return (
{children}
); } ScrollerItem.displayName = 'ScrollerItem'; function ScrollerIndicator({ direction, onClick, disabled, className, }: ScrollerIndicatorProps) { const { orientation } = useScrollerContext(); const Icon = orientation === 'horizontal' ? direction === 'prev' ? ChevronLeft : ChevronRight : direction === 'prev' ? ChevronUp : ChevronDown; return ( ); } ScrollerIndicator.displayName = 'ScrollerIndicator';