@newrelic/gatsby-theme-newrelic
Version:
[](https://opensource.newrelic.com/oss-category/#community-project)
154 lines (138 loc) • 4.12 kB
JavaScript
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;