yintai-company-home/src/components/layout/TopTabsSection/TopTabs.tsx

150 lines
5.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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<HTMLDivElement>(null);
const scrollRef = useRef<HTMLDivElement>(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 (
<div className={className}>
<div ref={containerRef} className={styles.topTabsTabs}>
{canScrollLeft && (
<button
type="button"
className={`${styles.topTabsNavBtn} ${styles.topTabsNavBtnLeft}`}
onClick={handleScrollLeft}
aria-label="向左滚动"
>
<LeftOutlined />
</button>
)}
<div
ref={scrollRef}
className={`${styles.topTabsTabsScroll} ${shouldCenter ? styles.topTabsTabsScrollCenter : ''}`}
>
{data.tabItems.map((item, index) => (
<div
key={index}
className={`${styles.topTabsTabItem} ${activeIndex === index ? styles.active : ''}`}
onClick={() => setActiveIndex(index)}
>
<span>{item.tabName}</span>
</div>
))}
</div>
{canScrollRight && (
<button
type="button"
className={`${styles.topTabsNavBtn} ${styles.topTabsNavBtnRight}`}
onClick={handleScrollRight}
aria-label="向右滚动"
>
<RightOutlined />
</button>
)}
<div
className={styles.topTabsBottomLine}
style={{
left: indicatorStyle.left,
width: indicatorStyle.width,
}}
/>
</div>
</div>
);
}