import PropTypes from 'prop-types'; import React, { useRef, useState } from 'react'; import styled from '@emotion/styled'; import { IconArrowUp } from '@apollo/space-kit/icons/IconArrowUp'; import { IconCollapseList } from '@apollo/space-kit/icons/IconCollapseList'; import { IconExpandList } from '@apollo/space-kit/icons/IconExpandList'; import { IconOutlink } from '@apollo/space-kit/icons/IconOutlink'; import { Link, withPrefix } from 'gatsby'; import { colors } from '../utils/colors'; import { size } from 'polished'; import { smallCaps } from '../utils/typography'; const ExpandAll = styled.button(smallCaps, { display: 'flex', alignItems: 'center', marginBottom: 12, padding: 0, border: 0, fontSize: 12, fontWeight: 600, lineHeight: 1, background: 'none', outline: 'none', cursor: 'pointer', color: 'inherit', ':hover': { opacity: colors.hoverOpacity }, svg: { ...size(12), marginRight: 8 } }); const StyledList = styled.ul({ marginLeft: 0, marginBottom: 32, listStyle: 'none' }); const StyledListItem = styled.li({ fontSize: '1rem', lineHeight: 1.5, // marginBottom: '0.8125rem', marginBottom: '12px', a: { color: 'inherit', textDecoration: 'none', ':hover': { opacity: colors.hoverOpacity }, '&.active': { color: colors.primary, pointerEvents: 'none' } }, ':first-child': { marginTop: '6px' } }); const Category = styled.div({ position: 'relative', zIndex: 0, [StyledList]: { position: 'relative', zIndex: 2 } }); const categoryTitleStyles = { display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '12px 0', color: colors.text1, fontWeight: 'bold', fontSize: 14, lineHeight: '15px', ...smallCaps, svg: size(10), '&.active': { color: colors.primary } }; const CategoryTitle = styled.div(categoryTitleStyles); const CategoryLink = styled(Link)(categoryTitleStyles, { textDecoration: 'none', ':hover': { opacity: colors.hoverOpacity } }); const StyledCheckbox = styled.input({ ...size('100%'), cursor: 'pointer', position: 'absolute', top: 0, left: 0, opacity: 0, zIndex: 1, [`:hover ~ ${CategoryTitle}`]: { opacity: colors.hoverOpacity }, ':not(:checked) ~': { [`${CategoryTitle} svg`]: { transform: 'scaleY(-1)' }, [StyledList]: { display: 'none' } } }); const StyledOutlinkIcon = styled(IconOutlink)(size(14), { verticalAlign: -1, marginLeft: 8, color: colors.text3 }); function isPageSelected(path, pathname) { const [a, b] = [withPrefix(path), pathname].map(string => string.replace(/\/$/, '') ); return a === b; } function NavItems(props) { return ( <StyledList> {props.pages.map((page, index) => { const pageTitle = page.sidebarTitle || page.title; return ( <StyledListItem key={index}> {page.anchor ? ( <a href={page.path} target="_blank" rel="noopener noreferrer"> {pageTitle} <StyledOutlinkIcon /> </a> ) : ( <Link className={ isPageSelected(page.path, props.pathname) ? 'active' : null } to={page.path} title={page.description} onClick={props.onLinkClick} > {pageTitle} </Link> )} </StyledListItem> ); })} </StyledList> ); } NavItems.propTypes = { pages: PropTypes.array.isRequired, pathname: PropTypes.string.isRequired, onLinkClick: PropTypes.func }; export default function SidebarNav(props) { const categoriesRef = useRef(); const [allExpanded, setAllExpanded] = useState(false); const categories = props.contents.filter(content => content.title); const [root] = props.contents.filter(content => !content.title); function toggleAll() { const checkboxes = Array.from( categoriesRef.current.querySelectorAll('input[type="checkbox"]') ); const expanded = !checkboxes.every(checkbox => checkbox.checked); checkboxes.forEach(checkbox => (checkbox.checked = expanded)); setAllExpanded(expanded); if (props.onToggleAll) { props.onToggleAll(expanded); } } function toggleCategory(event) { const { value, checked, parentElement } = event.target; const checkboxes = parentElement.parentElement.querySelectorAll( 'input[type="checkbox"]' ); const expanded = Array.from(checkboxes).every(checkbox => checkbox.checked); setAllExpanded(expanded); if (props.onToggleCategory) { props.onToggleCategory(value, checked); } } return ( <> {root && ( <NavItems pages={root.pages} pathname={props.pathname} onLinkClick={props.onLinkClick} /> )} {!props.alwaysExpanded && categories.length > 1 && ( <ExpandAll onClick={toggleAll}> {React.createElement(allExpanded ? IconCollapseList : IconExpandList)} {allExpanded ? 'Collapse' : 'Expand'} all </ExpandAll> )} <div ref={categoriesRef}> {categories.map((category, index) => { const isSelected = category.pages.some(page => isPageSelected(page.path, props.pathname) ); const className = isSelected ? 'active' : null; return ( <Category key={index}> {!props.alwaysExpanded && ( <StyledCheckbox type="checkbox" value={category.title} onChange={toggleCategory} defaultChecked={isSelected} /> )} {props.alwaysExpanded && category.path ? ( <CategoryLink className={className} to={category.path}> {category.title} </CategoryLink> ) : ( <CategoryTitle className={className}> {category.title} {!props.alwaysExpanded && <IconArrowUp />} </CategoryTitle> )} <NavItems pages={category.pages} pathname={props.pathname} onLinkClick={props.onLinkClick} /> </Category> ); })} </div> </> ); } SidebarNav.propTypes = { alwaysExpanded: PropTypes.bool, contents: PropTypes.array.isRequired, pathname: PropTypes.string.isRequired, onToggleAll: PropTypes.func, onToggleCategory: PropTypes.func, onLinkClick: PropTypes.func };