// @flow import React, { Fragment, useState, useRef, useContext, useLayoutEffect, createContext } from 'react'; import { Tabs as ReachTabs, Tab as ReachTab, TabList as ReachTabList, TabPanels as ReachTabPanels, TabPanel as ReachTabPanel, } from '@reach/tabs'; import classnames from 'classnames'; import { useOnResize } from 'effects/use-on-resize'; // Tabs are a compound component // The components are used individually, but they will still interact and share state // When using, at a minimum you must arrange the components in this pattern // When the at index 0 is active, the TabPanel at index 0 will be displayed // // // // Tab label 1 // Tab label 2 // ... // // // Content for Tab 1 // Content for Tab 2 // ... // // // // the base @reach/tabs components handle all the focus/accessibility labels // We're just adding some styling type TabsProps = { index?: number, onChange?: (number) => void, children: Array, }; // Use context so child TabPanels can set the active tab, which is kept in Tabs' state const AnimatedContext = createContext(); function Tabs(props: TabsProps) { // Store the position of the selected Tab so we can animate the "active" bar to its position const [selectedRect, setSelectedRect] = useState(null); const [tabsRect, setTabsRect] = React.useState(); // Create a ref of the parent element so we can measure the relative "left" for the bar for the child Tab's const tabsRef = useRef(); // Recalculate "Rect" on window resize useOnResize(() => { if (tabsRef.current) { setTabsRect(tabsRef.current.getBoundingClientRect()); } }); const tabLabels = props.children[0]; const tabContent = props.children[1]; return ( {tabLabels}
{tabContent} ); } // // The wrapper for the list of tab labels that users can click type TabListProps = { className?: string, }; function TabList(props: TabListProps) { const { className, ...rest } = props; return ; } // // The links that users click // Accesses `setSelectedRect` from context to set itself as active if needed // Flow doesn't understand we don't have to pass it in ourselves type TabProps = { isSelected?: Boolean, }; function Tab(props: TabProps) { // @reach/tabs provides an `isSelected` prop // We could also useContext to read it manually const { isSelected } = props; const [rect, setRect] = React.useState(); // Recalculate "Rect" on window resize useOnResize(() => { if (ref.current) { setRect(ref.current.getBoundingClientRect()); } }); // Each tab measures itself const ref = useRef(); // and calls up to the parent when it becomes selected // we useLayoutEffect to avoid flicker const setSelectedRect = useContext(AnimatedContext); useLayoutEffect(() => { if (isSelected) setSelectedRect(rect); }, [isSelected, rect, setSelectedRect]); return ; } // // The wrapper for TabPanel type TabPanelsProps = { header?: React$Node, }; function TabPanels(props: TabPanelsProps) { const { header, ...rest } = props; return ( {header} ); } // // The wrapper for content when it's associated Tab is selected function TabPanel(props: any) { return ; } export { Tabs, TabList, Tab, TabPanels, TabPanel };