1 import React from 'react'
2 import {Heading, Box, Details, useDetails, Button, NavList} from '@primer/react'
3 import {ChevronDownIcon, ChevronRightIcon} from '@primer/octicons-react'
4 import {SCROLL_MARGIN_TOP} from '../constants'
5 import usePage from '../hooks/use-page'
6
7 const TableOfContentsItems = ({items, depth}) => (
8 <>
9 {items.map(item => (
10 <React.Fragment key={item.title}>
11 <NavList.Item href={item.url} sx={{pl: depth > 1 ? 4 : 2}}>
12 {item.title}
13 </NavList.Item>
14 {item.items ? <TableOfContentsItems items={item.items} depth={depth + 1} /> : null}
15 </React.Fragment>
16 ))}
17 </>
18 )
19
20 const TableOfContents = ({'aria-labelledby': ariaLabelledBy, items, depth = 1, ...props}) => (
21 <NavList aria-labelledby={ariaLabelledBy} {...props}>
22 <TableOfContentsItems items={items} depth={depth} />
23 </NavList>
24 )
25
26 const withTableOfContents = Component => {
27 const WithTableOfContents = props => {
28 const {tableOfContents} = usePage().pageContext
29 return tableOfContents ? <Component {...props} items={tableOfContents} /> : null
30 }
31 return WithTableOfContents
32 }
33
34 export const Mobile = withTableOfContents(({items}) => {
35 const {getDetailsProps, open} = useDetails({defaultOpen: true})
36 return (
37 <Box sx={{display: ['block', null, 'none'], mb: 3, mt: 4}}>
38 <Details
39 {...getDetailsProps()}
40 sx={{
41 borderStyle: 'solid',
42 borderWidth: 1,
43 borderColor: 'border.muted',
44 borderRadius: 2,
45 }}
46 >
47 <Button
48 as="summary"
49 sx={{
50 borderTopWidth: 0,
51 borderLeftWidth: 0,
52 borderRightWidth: 0,
53 borderBottomWidth: open ? 1 : 0,
54 borderBottomLeftRadius: open ? 0 : 2,
55 borderBottomRightRadius: open ? 0 : 2,
56 }}
57 leadingIcon={open ? ChevronDownIcon : ChevronRightIcon}
58 >
59 Table of contents
60 </Button>
61 <TableOfContents items={items} />
62 </Details>
63 </Box>
64 )
65 })
66
67 export const Desktop = withTableOfContents(({items}) => (
68 <Box
69 sx={{
70 width: 220,
71 flex: '0 0 auto',
72 marginLeft: [null, 7, 8, 9],
73 display: ['none', null, 'block'],
74 position: 'sticky',
75 top: SCROLL_MARGIN_TOP,
76 maxHeight: `calc(100vh - ${SCROLL_MARGIN_TOP}px)`,
77 }}
78 >
79 <Heading as="h3" sx={{fontSize: 1, display: 'inline-block', fontWeight: 'bold'}} id="toc-heading">
80 Table of contents
81 </Heading>
82 <Box
83 sx={{
84 // extra pixels to account for table of contents title height
85 maxHeight: `calc(100% - 24px)`,
86 overflowY: 'auto',
87 }}
88 >
89 <TableOfContents aria-labelledby="toc-heading" items={items} sx={{ml: -2}} />
90 </Box>
91 </Box>
92 ))