'use client' import * as React from 'react' import { SvgTaillessLineArrowDown1, SvgTaillessLineArrowUp1, } from '@chainlink/blocks-icons' import { cn } from '../../utils' /** Tolerance (px) for scroll boundary detection. Use a small value so arrows show/hide precisely when content uses exact row heights (e.g. dropdownHeights). */ const SCROLL_ARROW_TOLERANCE = 1 function useScrollArrows({ enabled = true, /** When set (e.g. combobox), keyboard/pointer check uses this ref instead of containerRef. Use a ref that wraps the whole focus scope (e.g. header + list) so input focus still counts. */ focusScopeRef, }: { enabled?: boolean focusScopeRef?: React.RefObject } = {}) { const scrollRef = React.useRef(null) const containerRef = React.useRef(null) const scrollIntervalRef = React.useRef | null>( null, ) const [showScrollDown, setShowScrollDown] = React.useState(false) const [showScrollUp, setShowScrollUp] = React.useState(false) const [keyboardMode, setKeyboardMode] = React.useState(false) const checkScroll = React.useCallback(() => { const el = scrollRef.current if (!el) return const { scrollTop, clientHeight, scrollHeight } = el setShowScrollDown( scrollTop + clientHeight < scrollHeight - SCROLL_ARROW_TOLERANCE, ) setShowScrollUp(scrollTop > SCROLL_ARROW_TOLERANCE) }, []) React.useEffect(() => { if (!enabled) return const scopeRef = focusScopeRef ?? containerRef const onKeyDown = (e: KeyboardEvent) => { if (e.key !== 'ArrowUp' && e.key !== 'ArrowDown') return const container = scopeRef.current if (container && container.contains(document.activeElement)) { setKeyboardMode(true) } } const onPointerDown = (e: PointerEvent) => { const container = scopeRef.current if (container && container.contains(e.target as Node)) { setKeyboardMode(false) } } document.addEventListener('keydown', onKeyDown, true) document.addEventListener('pointerdown', onPointerDown) return () => { document.removeEventListener('keydown', onKeyDown, true) document.removeEventListener('pointerdown', onPointerDown) } }, [enabled, focusScopeRef]) const stopAutoScroll = React.useCallback(() => { if (scrollIntervalRef.current) { clearInterval(scrollIntervalRef.current) scrollIntervalRef.current = null } }, []) const startAutoScroll = React.useCallback( (direction: 'up' | 'down') => { stopAutoScroll() scrollIntervalRef.current = setInterval(() => { const el = scrollRef.current if (!el) return const atBoundary = direction === 'down' ? el.scrollTop + el.clientHeight >= el.scrollHeight - SCROLL_ARROW_TOLERANCE : el.scrollTop <= SCROLL_ARROW_TOLERANCE if (atBoundary) { stopAutoScroll() checkScroll() return } el.scrollTop += direction === 'down' ? 4 : -4 checkScroll() }, 16) }, [checkScroll, stopAutoScroll], ) const scrollToStart = React.useCallback(() => { stopAutoScroll() scrollRef.current?.scrollTo({ top: 0, behavior: 'smooth' }) }, [stopAutoScroll]) const scrollToEnd = React.useCallback(() => { stopAutoScroll() scrollRef.current?.scrollTo({ top: scrollRef.current.scrollHeight, behavior: 'smooth', }) }, [stopAutoScroll]) React.useEffect(() => { if (!enabled || !scrollRef.current) return const ro = new ResizeObserver(() => checkScroll()) ro.observe(scrollRef.current) checkScroll() return () => { ro.disconnect() } }, [enabled, checkScroll]) React.useEffect(() => { if (enabled) { requestAnimationFrame(() => checkScroll()) const timer = setTimeout(() => checkScroll(), 150) return () => clearTimeout(timer) } stopAutoScroll() return undefined }, [enabled, checkScroll, stopAutoScroll]) React.useEffect(() => { return () => stopAutoScroll() }, [stopAutoScroll]) return { scrollRef, containerRef, showScrollUp: showScrollUp && !keyboardMode, showScrollDown: showScrollDown && !keyboardMode, handleScroll: checkScroll, startAutoScroll, stopAutoScroll, scrollToStart, scrollToEnd, } } interface ScrollArrowProps { onHoverStart: () => void onHoverEnd: () => void onJump: () => void className?: string } function ScrollArrowUp({ onHoverStart, onHoverEnd, onJump, className, }: ScrollArrowProps) { return (
) } function ScrollArrowDown({ onHoverStart, onHoverEnd, onJump, className, }: ScrollArrowProps) { return (
) } export { useScrollArrows, ScrollArrowUp, ScrollArrowDown }