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, index) => (
10 <React.Fragment key={item.title}>
11 <NavList.Item
12 href={item.url}
13 aria-label={`${item.title}, ${index + 1} of ${items.length}`}
14 aria-labelledby={null}
15 >
16 {item.title}
17 {item.items ? (
18 <NavList.SubNav>
19 <TableOfContentsItems items={item.items} depth={depth + 1} />
20 </NavList.SubNav>
21 ) : null}
22 </NavList.Item>
23 </React.Fragment>
24 ))}
25 </>
26 )
27
28 const TableOfContents = ({'aria-labelledby': ariaLabelledBy, items, depth = 1, ...props}) => (
29 <NavList
30 aria-labelledby={ariaLabelledBy}
31 {...props}
32 sx={{
33 textDecoration: 'underline',
34 ...props.sx,
35 }}
36 >
37 <TableOfContentsItems items={items} depth={depth} />
38 </NavList>
39 )
40
41 const withTableOfContents = Component => {
42 const WithTableOfContents = props => {
43 const {tableOfContents} = usePage().pageContext
44 return tableOfContents ? <Component {...props} items={tableOfContents} /> : null
45 }
46 return WithTableOfContents
47 }
48
49 export const Mobile = withTableOfContents(({items}) => {
50 const {getDetailsProps, open} = useDetails({defaultOpen: true})
51 return (
52 <Box sx={{display: ['block', null, 'none'], mb: 3, mt: 4}}>
53 <Details
54 {...getDetailsProps()}
55 sx={{
56 borderStyle: 'solid',
57 borderWidth: 1,
58 borderColor: 'border.muted',
59 borderRadius: 2,
60 }}
61 >
62 <Button
63 as="summary"
64 sx={{
65 borderTopWidth: 0,
66 borderLeftWidth: 0,
67 borderRightWidth: 0,
68 borderBottomWidth: open ? 1 : 0,
69 borderBottomLeftRadius: open ? 0 : 2,
70 borderBottomRightRadius: open ? 0 : 2,
71 '&:focus-visible': {
72 outline: '2px solid',
73 outlineColor: '-webkit-focus-ring-color',
74 outlineOffset: '1px',
75 },
76 }}
77 leadingIcon={open ? ChevronDownIcon : ChevronRightIcon}
78 >
79 Table of contents
80 </Button>
81 <TableOfContents items={items} />
82 </Details>
83 </Box>
84 )
85 })
86
87 export const Desktop = withTableOfContents(({items}) => (
88 <Box
89 sx={{
90 width: 220,
91 flex: '0 0 auto',
92 marginLeft: [null, 7, 8, 9],
93 display: ['none', null, 'block'],
94 position: 'sticky',
95 top: SCROLL_MARGIN_TOP,
96 maxHeight: `calc(100vh - ${SCROLL_MARGIN_TOP}px)`,
97 }}
98 >
99 <Heading as="h2" sx={{fontSize: 1, display: 'inline-block', fontWeight: 'bold'}} id="toc-heading">
100 Table of contents
101 </Heading>
102 <Box
103 sx={{
104 // extra pixels to account for table of contents title height
105 maxHeight: `calc(100% - 24px)`,
106 overflowY: 'auto',
107 }}
108 >
109 <TableOfContents aria-labelledby="toc-heading" items={items} sx={{ml: -2}} />
110 </Box>
111 </Box>
112 ))