gatsby-theme-guitar-book
Version:
A Gatsby theme for building guitar book websites
260 lines (238 loc) • 6.85 kB
JavaScript
import '../prism.less';
import 'prismjs/plugins/line-numbers/prism-line-numbers.css';
import DocsetSwitcher from './docset-switcher';
import Header from './header';
import PropTypes from 'prop-types';
import React, {createContext, useMemo, useRef, useState} from 'react';
import Search from './search';
import styled from '@emotion/styled';
import useLocalStorage from 'react-use/lib/useLocalStorage';
import {Button} from '@apollo/space-kit/Button';
import {colors} from '../utils/colors';
import breakpoints from '../utils/breakpoints';
import {Helmet} from 'react-helmet';
import {IconLayoutModule} from '@apollo/space-kit/icons/IconLayoutModule';
import {graphql, navigate, useStaticQuery} from 'gatsby';
import {Select} from './select';
import {SelectedLanguageContext} from './multi-code-block';
import {getVersionBasePath} from '../utils';
import {size} from 'polished';
import {trackCustomEvent} from 'gatsby-plugin-google-analytics';
import {useResponsiveSidebar} from "./responsive-sidebar";
import Layout from "./layout";
import FlexWrapper from "./flex-wrapper";
import Sidebar from "./sidebar";
import SidebarNav from "./sidebar-nav";
import MenuButton from "./menu-button";
import Toolbox from "./toolbox";
const Main = styled.main({
flexGrow: 1
});
const ButtonWrapper = styled.div({
flexGrow: 1
});
const StyledButton = styled(Button)({
width: '100%',
':not(:hover)': {
backgroundColor: colors.background
}
});
const StyledIcon = styled(IconLayoutModule)(size(16), {
marginLeft: 'auto'
});
const MobileNav = styled.div({
display: 'none',
[breakpoints.md]: {
display: 'flex',
alignItems: 'center',
color: colors.text1
}
});
const HeaderInner = styled.span({
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
marginBottom: 32
});
function getVersionLabel(version) {
return `v${version}`;
}
const GA_EVENT_CATEGORY_SIDEBAR = 'Sidebar';
function handleToggleAll(expanded) {
trackCustomEvent({
category: GA_EVENT_CATEGORY_SIDEBAR,
action: 'Toggle all',
label: expanded ? 'expand' : 'collapse'
});
}
function handleToggleCategory(label, expanded) {
trackCustomEvent({
category: GA_EVENT_CATEGORY_SIDEBAR,
action: 'Toggle category',
label,
value: Number(expanded)
});
}
export const NavItemsContext = createContext();
export default function PageLayout(props) {
const data = useStaticQuery(
graphql`
{
site {
siteMetadata {
title
siteName
}
}
}
`
);
const {
sidebarRef,
openSidebar,
sidebarOpen,
handleWrapperClick,
handleSidebarNavLinkClick
} = useResponsiveSidebar();
const buttonRef = useRef(null);
const [menuOpen, setMenuOpen] = useState(false);
const selectedLanguageState = useLocalStorage('docs-lang');
function openMenu() {
setMenuOpen(true);
}
function closeMenu() {
setMenuOpen(false);
}
const {pathname} = props.location;
const {siteName, title} = data.site.siteMetadata;
const {
subtitle,
sidebarContents,
versions,
versionDifference,
versionBasePath,
defaultVersion
} = props.pageContext;
const {
twitterHandle,
youtubeUrl,
navConfig = {},
footerNavConfig,
logoLink,
menuTitle
} = props.pluginOptions;
const navItems = useMemo(
() =>
Object.entries(navConfig).map(([title, navItem]) => ({
...navItem,
title
})),
[navConfig]
);
const hasNavItems = navItems.length > 0;
const sidebarTitle = (
<span className="title-sidebar">{subtitle || siteName}</span>
);
return (
<Layout>
<Helmet
titleTemplate={['%s', subtitle, title].filter(Boolean).join(' - ')}
>
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1"
/>
</Helmet>
<FlexWrapper onClick={handleWrapperClick}>
<Sidebar
responsive
className="sidebar"
open={sidebarOpen}
ref={sidebarRef}
title={siteName}
logoLink={logoLink}
>
<HeaderInner>
{hasNavItems ? (
<ButtonWrapper ref={buttonRef}>
<StyledButton
feel="flat"
color={colors.primary}
size="small"
onClick={openMenu}
style={{display: 'flex'}}
>
{sidebarTitle}
<StyledIcon />
</StyledButton>
</ButtonWrapper>
) : (
sidebarTitle
)}
{versions && versions.length > 0 && (
<Select
feel="flat"
size="small"
value={versionDifference ? versionBasePath : '/'}
onChange={navigate}
style={{marginLeft: 8}}
options={versions.reduce(
(acc, version) => ({
...acc,
[getVersionBasePath(version)]: getVersionLabel(version)
}),
{
'/': defaultVersion
? getVersionLabel(defaultVersion)
: 'Latest'
}
)}
/>
)}
</HeaderInner>
{sidebarContents && (
<SidebarNav
contents={sidebarContents}
pathname={pathname}
onToggleAll={handleToggleAll}
onToggleCategory={handleToggleCategory}
onLinkClick={handleSidebarNavLinkClick}
/>
)}
</Sidebar>
<Main>
<Header>
<MobileNav>
<MenuButton onClick={openSidebar} />
</MobileNav>
<Search siteName={siteName} />
<Toolbox pathname={pathname} />
</Header>
<SelectedLanguageContext.Provider value={selectedLanguageState}>
<NavItemsContext.Provider value={navItems}>
{props.children}
</NavItemsContext.Provider>
</SelectedLanguageContext.Provider>
</Main>
</FlexWrapper>
{hasNavItems && (
<DocsetSwitcher
siteName={menuTitle || siteName}
twitterUrl={twitterHandle && `https://twitter.com/${twitterHandle}`}
youtubeUrl={youtubeUrl}
navItems={navItems}
footerNavConfig={footerNavConfig}
open={menuOpen}
buttonRef={buttonRef}
onClose={closeMenu}
/>
)}
</Layout>
);
}
PageLayout.propTypes = {
children: PropTypes.node.isRequired,
location: PropTypes.object.isRequired,
pageContext: PropTypes.object.isRequired,
pluginOptions: PropTypes.object.isRequired
};