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