import { forwardRef, useId, useRef, useState, useCallback, useEffect } from 'react' import type { ReactNode, CSSProperties } from 'react' import { cn } from '@/lib/utils' export type ScrollbarVisibility = 'auto' | 'always' | 'hidden' export interface ScrollbarProps { children: ReactNode className?: string style?: CSSProperties maxHeight?: number | string /** auto: 默认透明,滚动时与 hover 时可见;always: 始终可见;hidden: 隐藏 */ scrollbarVisibility?: ScrollbarVisibility scrollbarWidth?: number } export const Scrollbar = forwardRef( function Scrollbar( { children, className, style, maxHeight, scrollbarVisibility = 'auto', scrollbarWidth = 6, }, ref ) { const scrollId = useId().replace(/:/g, '') const scrollClassName = `Scrollbar-${scrollId}` const scrollEndTimerRef = useRef | null>(null) const [isScrolling, setIsScrolling] = useState(false) const handleScroll = useCallback(() => { if (scrollbarVisibility !== 'auto') return setIsScrolling(true) if (scrollEndTimerRef.current) clearTimeout(scrollEndTimerRef.current) scrollEndTimerRef.current = setTimeout(() => { scrollEndTimerRef.current = null setIsScrolling(false) }, 800) }, [scrollbarVisibility]) useEffect(() => { return () => { if (scrollEndTimerRef.current) clearTimeout(scrollEndTimerRef.current) } }, []) const getScrollbarStyles = () => { if (scrollbarVisibility === 'hidden') { return ` .${scrollClassName}::-webkit-scrollbar { display: none; } .${scrollClassName} { scrollbar-width: none; } ` } const isAuto = scrollbarVisibility === 'auto' const thumbColor = scrollbarVisibility === 'always' ? 'var(--color-fill-secondary)' : 'transparent' const thumbHoverColor = 'var(--color-fill-secondary)' const autoVisible = isAuto ? ` .${scrollClassName}:hover::-webkit-scrollbar-thumb, .${scrollClassName}.Scrollbar-isScrolling::-webkit-scrollbar-thumb { background-color: ${thumbHoverColor}; } ` : ` .${scrollClassName}:hover::-webkit-scrollbar-thumb { background-color: ${thumbHoverColor}; } ` return ` .${scrollClassName}::-webkit-scrollbar { width: ${scrollbarWidth}px !important; height: ${scrollbarWidth}px !important; } .${scrollClassName}::-webkit-scrollbar-track { background: transparent; } .${scrollClassName}::-webkit-scrollbar-thumb { background-color: ${thumbColor}; border-radius: ${Math.max(scrollbarWidth / 2, 2)}px; min-height: var(--spacing-8); transition: background-color 0.2s ease; } ${autoVisible} .${scrollClassName}::-webkit-scrollbar-corner { background: transparent; } ` } const computedStyle: CSSProperties = { ...style, maxHeight } return ( <>
{children}
) } ) Scrollbar.displayName = 'Scrollbar'