import { useRect } from '@reach/rect'; import { Tabs as ReachTabs, TabList as ReachTabList, Tab as ReachTab, useTabsContext, } from '@reach/tabs'; import clsx from 'clsx'; import React from 'react'; import { useHistory } from 'react-router-dom'; import Counter from '../counter/counter'; import './tabs.css'; const HORIZONTAL_PADDING = 8; const AnimatedContext = React.createContext(null); interface IAnimatedInterface { /** Callback when a tab is chosen. */ onChange(index: number): void /** Default tab index. */ defaultIndex: number children: React.ReactNode } /** Tabs with a sliding active state. */ const AnimatedTabs: React.FC = ({ children, ...rest }) => { const [activeRect, setActiveRect] = React.useState(null); const ref = React.useRef(); const rect = useRect(ref); // @ts-ignore const top: number = (activeRect && activeRect.bottom) - (rect && rect.top); // @ts-ignore const width: number = activeRect && activeRect.width - HORIZONTAL_PADDING * 2; // @ts-ignore const left: number = (activeRect && activeRect.left) - (rect && rect.left) + HORIZONTAL_PADDING; return ( // @ts-ignore
{children} ); }; interface IAnimatedTab { /** ARIA role. */ role: 'button' /** Element to represent the tab. */ as: 'a' | 'button' /** Route to visit when the tab is chosen. */ href?: string /** Tab title text. */ title: string /** Index value of the tab. */ index: number } /** A single animated tab. */ const AnimatedTab: React.FC = ({ index, ...props }) => { // get the currently selected index from useTabsContext const { selectedIndex } = useTabsContext(); const isSelected: boolean = selectedIndex === index; // measure the size of our element, only listen to rect if active const ref = React.useRef(); const rect = useRect(ref, { observe: isSelected }); // get the style changing function from context const setActiveRect = React.useContext(AnimatedContext); // callup to set styles whenever we're active React.useLayoutEffect(() => { if (isSelected) { // @ts-ignore setActiveRect(rect); } }, [isSelected, rect, setActiveRect]); return ( // @ts-ignore ); }; /** Structure to represent a tab. */ export type Item = { /** Tab text. */ text: React.ReactNode /** Tab tooltip text. */ title?: string /** URL to visit when the tab is selected. */ href?: string /** Route to visit when the tab is selected. */ to?: string /** Callback when the tab is selected. */ action?: () => void /** Display a counter over the tab. */ count?: number /** Unique name for this tab. */ name: string } interface ITabs { /** Array of structured tab items. */ items: Item[] /** Name of the active tab item. */ activeItem: string } /** Animated tabs component. */ const Tabs = ({ items, activeItem }: ITabs) => { const defaultIndex = items.findIndex(({ name }) => name === activeItem); const history = useHistory(); const onChange = (selectedIndex: number) => { const item = items[selectedIndex]; if (typeof item.action === 'function') { item.action(); } else if (item.to) { history.push(item.to); } }; const renderItem = (item: Item, idx: number) => { const { name, text, title, count } = item; return (
{count ? ( ) : null} {text}
); }; return ( {items.map((item, i) => renderItem(item, i))} ); }; export default Tabs;