1import { css } from '@emotion/react'; 2import { theme, iconSize, spacing, ChevronDownIcon, borderRadius, shadows } from '@expo/styleguide'; 3import { useRouter } from 'next/router'; 4import React, { useState } from 'react'; 5 6import { CALLOUT } from '../Text'; 7 8import stripVersionFromPath from '~/common/stripVersionFromPath'; 9import { NavigationRoute } from '~/types/common'; 10 11if (typeof window !== 'undefined' && !window.hasOwnProperty('sidebarState')) { 12 window.sidebarState = {}; 13} 14 15type Props = React.PropsWithChildren<{ 16 info: NavigationRoute; 17}>; 18 19export function SidebarCollapsible(props: Props) { 20 const { info, children } = props; 21 const router = useRouter(); 22 23 const isChildRouteActive = () => { 24 let result = false; 25 26 const sections = info.children; 27 28 const isSectionActive = (section: NavigationRoute) => { 29 const linkUrl = stripVersionFromPath(section.as || section.href); 30 const pathname = stripVersionFromPath(router.pathname); 31 const asPath = stripVersionFromPath(router.asPath); 32 33 if (linkUrl === pathname || linkUrl === asPath) { 34 result = true; 35 } 36 }; 37 38 let posts: NavigationRoute[] = []; 39 sections?.forEach(section => { 40 posts = [...posts, ...(section?.children ?? [])]; 41 }); 42 43 posts.forEach(isSectionActive); 44 return result; 45 }; 46 47 const hasCachedState = 48 typeof window !== 'undefined' && window.sidebarState[info.name] !== undefined; 49 50 const initState = hasCachedState 51 ? window.sidebarState[info.name] 52 : isChildRouteActive() || info.expanded; 53 54 const [isOpen, setOpen] = useState(initState); 55 56 const toggleIsOpen = () => { 57 setOpen(prevState => !prevState); 58 window.sidebarState[info.name] = !isOpen; 59 }; 60 61 return ( 62 <> 63 <a css={titleStyle} onClick={toggleIsOpen}> 64 <div css={chevronContainerStyle}> 65 <ChevronDownIcon 66 size={iconSize.tiny} 67 css={[chevronStyle, !isOpen && chevronClosedStyle]} 68 /> 69 </div> 70 <CALLOUT weight="medium">{info.name}</CALLOUT> 71 </a> 72 {isOpen && <div>{children}</div>} 73 </> 74 ); 75} 76 77const titleStyle = css({ 78 display: 'flex', 79 alignItems: 'center', 80 gap: spacing[1.5], 81 position: 'relative', 82 marginBottom: spacing[2], 83 userSelect: 'none', 84 transition: '100ms', 85 padding: `${spacing[1.5]}px ${spacing[3]}px`, 86 87 ':hover': { 88 cursor: 'pointer', 89 backgroundColor: theme.background.tertiary, 90 borderRadius: borderRadius.medium, 91 transition: '100ms', 92 }, 93}); 94 95const chevronContainerStyle = css({ 96 backgroundColor: theme.background.default, 97 border: `1px solid ${theme.border.default}`, 98 borderRadius: borderRadius.small, 99 display: 'flex', 100 alignItems: 'center', 101 justifyContent: 'center', 102 boxShadow: shadows.micro, 103 height: 20, 104 width: 20, 105 marginRight: spacing[1], 106}); 107 108const chevronStyle = css({ 109 transition: '100ms ease transform', 110 transform: 'translateX(-0.5px)', 111}); 112 113const chevronClosedStyle = css({ 114 transform: 'rotate(-90deg)', 115}); 116