import React, { useRef, useEffect, useState } from "react"; import classNames from "classnames"; import { TabItem } from "./TabItem"; import { Tab } from "./TabProps"; import { Button } from "../button"; import { useConfig } from "../_util/config-context"; import { useResizeObserver } from "../_util/use-resize-observer"; export interface TabBarProps { activeId: string; tabs: Tab[]; onActive: (tab: Tab, evt: React.SyntheticEvent) => void; addon: React.ReactNode; vertical: boolean; maxHeight: number; tabBarRender: (children: JSX.Element, tab: Tab) => JSX.Element; style: React.CSSProperties; activeTabAutoScrollIntoView: boolean; disableTabScrolling: boolean; } export function TabBar({ activeId, tabs, onActive = () => null, addon, vertical, maxHeight, tabBarRender, style, disableTabScrolling, activeTabAutoScrollIntoView, }: TabBarProps) { const { classPrefix } = useConfig(); // Scroll 相关 const scrollAreaRef = useRef(null); const tabListRef = useRef(null); const buttonRef = useRef(null); const activeItemRef = useRef(null); const [offset, setOffset] = useState(0); const [scrolling, setScrolling] = useState(false); // resize 或 tabs/addon 变化时重置滚动 const { width } = useResizeObserver(scrollAreaRef); useEffect(handleScroll, [tabs, addon, width]); useEffect(handleActiveItemIntoView, [activeId]); // 设置滚动状态 function handleScroll() { const scrolling = getMaxOffset() > 0; setScrolling(scrolling); // 无需滚动时重置位置 if (!scrolling) { setOffset(0); } else { handleActiveItemIntoView(); } } // 处理滚动至当前选中 TabItem function handleActiveItemIntoView() { if (!activeTabAutoScrollIntoView) { return; } setTimeout(() => { if (!scrollAreaRef.current || !activeItemRef.current) { return; } try { const scrollAreaRect = scrollAreaRef.current.getBoundingClientRect(); const activeItemRect = activeItemRef.current.getBoundingClientRect(); const [startPropertyName, endPropertyName] = vertical ? ["top", "bottom"] : ["left", "right"]; const buttonProperty = vertical ? buttonRef.current.clientHeight : buttonRef.current.clientWidth; const startDelta = scrollAreaRect[startPropertyName] - activeItemRect[startPropertyName] + buttonProperty; const endDelta = activeItemRect[endPropertyName] - scrollAreaRect[endPropertyName] + buttonProperty; if (startDelta > 0) { setOffset(Math.min(0, offset + startDelta)); } else if (endDelta > 0) { setOffset(Math.max(0 - getMaxOffset(), offset - endDelta)); } } catch (_) { // ignore } }, 0); } // 获取最大滚动偏移 function getMaxOffset() { // 垂直时不指定 maxHeight 不触发滚动 if (!scrollAreaRef.current || (vertical && !maxHeight)) { return 0; } const propertyName = vertical ? "clientHeight" : "clientWidth"; const scrollAreaProperty = vertical ? maxHeight : scrollAreaRef.current[propertyName]; const tabListProperty = tabListRef.current[propertyName]; const buttonProperty = buttonRef.current[propertyName]; if (scrollAreaProperty >= tabListProperty) { return 0; } return tabListProperty - (scrollAreaProperty - buttonProperty * 2); } // 获取最大单次滚动步长 function getStep() { const propertyName = vertical ? "clientHeight" : "clientWidth"; return ( scrollAreaRef.current[propertyName] - buttonRef.current[propertyName] * 2 ); } function handleBackward() { setOffset(offset => Math.min(0, offset + getStep())); } function handleForward() { setOffset(offset => Math.max(0 - getMaxOffset(), offset - getStep())); } return (
    {tabs.map(tab => ( tab.onClose(tab, evt) : null} onClick={evt => onActive(tab, evt)} render={children => tabBarRender(children, tab)} className={tab.className} style={tab.style} /> ))}
{addon}
); } TabBar.displayName = "TabBar";