UNPKG

@newrelic/gatsby-theme-newrelic

Version:

[![Community Project header](https://github.com/newrelic/opensource-website/raw/master/src/images/categories/Community_Project.png)](https://opensource.newrelic.com/oss-category/#community-project)

154 lines (138 loc) 4.12 kB
import React, { useEffect, useRef, useState } from 'react'; import PropTypes from 'prop-types'; import { useStaticQuery, graphql } from 'gatsby'; import { useLocation } from '@reach/router'; import TabsContext from '../Context'; import Bar from './Bar'; import BarItem from './BarItem'; import Pages from './Pages'; import Page from './Page'; const Tabs = ({ children, initialTab = 0 }) => { const [currentTabIndex, setCurrentTabIndex] = useState(initialTab); const [previousTabIndex, setPreviousTabIndex] = useState(initialTab); const tabsContainer = useRef(null); let transitionDirection = 'none'; if (previousTabIndex !== currentTabIndex) { transitionDirection = previousTabIndex > currentTabIndex ? 'right' : 'left'; } const [containerHeight, setContainerHeight] = useState(0); const setTab = (tab) => { setPreviousTabIndex(currentTabIndex); setCurrentTabIndex(tab); }; const updateHeight = (pageHeight) => { if (pageHeight > containerHeight) { const maxHeight = Math.min(pageHeight, 500); setContainerHeight(maxHeight); } }; const location = useLocation(); const findPageIndexById = (pages, hash) => { const findInChildren = (children, hash) => { if (!Array.isArray(children)) { return false; } for (const child of children) { if (child.props?.id === hash) { return true; } if (child.props?.children) { if (findInChildren(child.props.children, hash)) { return true; } } } return false; }; for (let index = 0; index < pages.length; index++) { const page = pages[index]; if (page.props?.id === hash) { return index; } else if ( page.props?.children && findInChildren(page.props.children, hash) ) { return index; } } return -1; }; // this needs to run in a useEffect since the hash // isn't available on the server during SSG. // to put it another way, in order to have this work with SSG // we'd hypothetically need to render a separate HTML file // for every single tab id on the page. // i don't think we can do this with Gatsby. useEffect(() => { const hash = location.hash.replace('#', ''); if (hash !== '') { const [_tabBar, tabPages] = children; // Tabs should always look like this: // ``` // <Tabs> // <Tabs.Bar>...</Tabs.Bar> // <Tabs.Pages>...</Tabs.Pages> // </Tabs> // ``` const bars = _tabBar.props.children; const pages = tabPages.props.children; // const index = pages.findIndex((page) => page.props.id === hash); const index = findPageIndexById(pages, hash); const isTabBar = bars.some((bar) => bar.props.id === hash); if (index !== -1) { // this is so the animation doesn't play on page load // if the first tab is selected. if (index !== 0) { setTab(index); } const y = tabsContainer.current.getBoundingClientRect().top + window.scrollY - // header height 72; isTabBar && window.scrollTo({ top: y, behavior: 'smooth' }); } } // eslint-disable-next-line react-hooks/exhaustive-deps }, [location.hash, children]); const { site: { layout: { mobileBreakpoint }, }, } = useStaticQuery(graphql` query BarQuery { site { layout { mobileBreakpoint } } } `); const context = { containerHeight, currentTabIndex, previousTabIndex, transitionDirection, mobileBreakpoint, setCurrentTabIndex: setTab, setPreviousTabIndex, updateHeight, }; return ( <TabsContext.Provider value={context}> <div ref={tabsContainer}>{children}</div> </TabsContext.Provider> ); }; Tabs.propTypes = { children: PropTypes.node.isRequired, /** * this should be the `id` of the tab. */ initialTab: PropTypes.string, }; Tabs.Bar = Bar; Tabs.BarItem = BarItem; Tabs.Pages = Pages; Tabs.Page = Page; export default Tabs;