import { useRef, useEffect, useState, useCallback } from 'react'; import { LeftOutlined, RightOutlined } from '@ant-design/icons'; import styles from './index.module.css'; type Data = { tabItems: { icon?: string; tabName?: string; contentTitle?: string; contentSubtitle?: string; contentText?: string; content?: string; /** 以 mockData 为准,tabItems 可能使用 sideImage 或 backgroundImage */ sideImage?: string; path?: string; }[], backgroundImage?: string; titleDirection?: 'row' | 'column'; className?: string; } export default function TopTabs({ data, activeIndex, setActiveIndex, className }: { data: Data, activeIndex: number, setActiveIndex: (index: number) => void, className?: string }) { const containerRef = useRef(null); const scrollRef = useRef(null); const [indicatorStyle, setIndicatorStyle] = useState<{ left: number; width: number }>({ left: 0, width: 0 }); const [canScrollLeft, setCanScrollLeft] = useState(false); const [canScrollRight, setCanScrollRight] = useState(false); const [shouldCenter, setShouldCenter] = useState(true); const updateIndicatorPosition = useCallback(() => { const scrollEl = scrollRef.current; const activeTab = activeIndex < data.tabItems.length ? (scrollEl?.children[activeIndex] as HTMLElement) : null; if (!scrollEl || !activeTab) return; const container = containerRef.current; if (!container) return; const containerRect = container.getBoundingClientRect(); const scrollRect = scrollEl.getBoundingClientRect(); const tabRect = activeTab.getBoundingClientRect(); const scrollLeft = scrollEl.scrollLeft; const tabOffsetLeft = activeTab.offsetLeft; const left = scrollRect.left - containerRect.left + tabOffsetLeft - scrollLeft; const width = tabRect.width; setIndicatorStyle({ left, width }); }, [activeIndex, data.tabItems.length]); const updateScrollState = useCallback(() => { const scrollEl = scrollRef.current; if (!scrollEl) return; const { scrollLeft, clientWidth, scrollWidth } = scrollEl; setCanScrollLeft(scrollLeft > 0); setCanScrollRight(scrollLeft + clientWidth < scrollWidth - 1); setShouldCenter(scrollWidth <= clientWidth); }, []); const handleScrollLeft = () => { scrollRef.current?.scrollBy({ left: -200, behavior: 'smooth' }); }; const handleScrollRight = () => { scrollRef.current?.scrollBy({ left: 200, behavior: 'smooth' }); }; useEffect(() => { const scrollEl = scrollRef.current; const handleScroll = () => { updateIndicatorPosition(); updateScrollState(); }; const rafId = requestAnimationFrame(() => { requestAnimationFrame(() => { updateIndicatorPosition(); updateScrollState(); }); }); if (!scrollEl) return () => cancelAnimationFrame(rafId); scrollEl.addEventListener('scroll', handleScroll); const resizeObserver = new ResizeObserver(() => { updateIndicatorPosition(); updateScrollState(); }); resizeObserver.observe(scrollEl); return () => { cancelAnimationFrame(rafId); scrollEl.removeEventListener('scroll', handleScroll); resizeObserver.disconnect(); }; }, [activeIndex, data.tabItems.length, updateIndicatorPosition, updateScrollState]); return (
{canScrollLeft && ( )}
{data.tabItems.map((item, index) => (
setActiveIndex(index)} > {item.tabName}
))}
{canScrollRight && ( )}
); }