fix: dedupe shared page components into wrap-page-element
Luke Karrys committed
Oct 15, 2023 at 16:33 UTC
3560a454fdd61c69a827b3028eaf707ffe10c575
26 files changed
+365
-368
content/index.mdx
+1
-1
@@ -3,7 +3,7 @@ title: npm Documentation
3
edit_on_github: false
4
---
5
6
-import HeroLayout from 'theme/layout/hero.js'
6
+import HeroLayout from '~/layout/hero.js'
7
export default HeroLayout
8
9
<Index depth='1' />
gatsby-config.js
+2
-2
@@ -3,7 +3,7 @@ const fs = require('fs')
3
4
const {NODE_ENV, GATSBY_PARTIAL_CONTENT, GATSBY_CONTENT_DIR = 'content'} = process.env
5
const DEV = NODE_ENV === 'development'
6
-const CONTENT_DIR = path.resolve(__dirname, '..', GATSBY_CONTENT_DIR)
6
+const CONTENT_DIR = path.resolve(GATSBY_CONTENT_DIR)
7
8
const walkDirs = dir => {
9
const dirs = fs
@@ -77,7 +77,7 @@ module.exports = {
77
{
78
resolve: 'gatsby-plugin-manifest',
79
options: {
80
- icon: path.resolve('./src/images/favicon.png'),
80
+ icon: path.resolve('./src/favicon.png'),
81
},
82
},
83
'gatsby-plugin-meta-redirect',
src/components/clipboard-copy.js
+2
-2
@@ -1,5 +1,5 @@
1
import React from 'react'
2
-import {Button, StyledOcticon, themeGet} from '@primer/react'
2
+import {Button, Octicon, themeGet} from '@primer/react'
3
import {CheckIcon, CopyIcon} from '@primer/octicons-react'
4
import styled from 'styled-components'
5
import copy from 'copy-to-clipboard'
@@ -33,7 +33,7 @@ function ClipboardCopy({value}) {
33
announce(`Copied to clipboard`)
34
}}
35
>
36
- {copied ? <StyledOcticon icon={CheckIcon} color="green.5" /> : <StyledOcticon icon={CopyIcon} color="gray.7" />}
36
+ {copied ? <Octicon icon={CheckIcon} color="green.5" /> : <Octicon icon={CopyIcon} color="gray.7" />}
37
</CopyToClipboard>
38
)
39
}
src/components/container.js
+1
-1
@@ -3,7 +3,7 @@ import React from 'react'
3
4
function Container({children}) {
5
return (
6
- <Box id="skip-nav" width="100%" maxWidth={960} p={5} mx="auto">
6
+ <Box width="100%" maxWidth={960} p={5} mx="auto">
7
{children}
8
</Box>
9
)
src/components/contributors.js
+3
-2
@@ -1,8 +1,6 @@
1
import {Box, Avatar, Link, Text, Tooltip} from '@primer/react'
2
import React from 'react'
3
4
-const pluralize = (word, count) => `${word}${count === 1 ? '' : 's'}`
5
-
4
const months = [
5
'January',
6
'February',
@@ -17,8 +15,11 @@ const months = [
15
'November',
16
'December',
17
]
18
+
19
const format = d => `${months[d.getMonth()]} ${d.getDate()}, ${d.getFullYear()}`
20
21
+const pluralize = (word, count) => `${word}${count === 1 ? '' : 's'}`
22
+
23
function Contributors({logins, latestCommit}) {
24
return (
25
<div>
src/components/drawer.js
+52
-54
@@ -3,59 +3,57 @@ import {Box} from '@primer/react'
3
import {AnimatePresence, motion} from 'framer-motion'
4
import {FocusOn} from 'react-focus-on'
5
6
-function Drawer({isOpen, onDismiss, children}) {
7
- return (
8
- <AnimatePresence>
9
- {isOpen ? (
10
- // These event handlers fix a bug that caused links below the fold
11
- // to be unclickable in macOS Safari.
12
- // Reference: https://github.com/theKashey/react-focus-lock/issues/79
13
- <div
14
- style={{textAlign: 'start', fontSize: '1rem', lineHeight: '1.5rem'}}
15
- onMouseDown={event => event.preventDefault()}
16
- onKeyDown={event => event.target.focus()}
17
- onClick={event => event.target.focus()}
18
- role="button"
19
- tabIndex="0"
20
- >
21
- <FocusOn returnFocus={true} onEscapeKey={() => onDismiss()}>
22
- <Box
23
- position="fixed"
24
- key="overlay"
25
- as={motion.div}
26
- initial={{opacity: 0}}
27
- animate={{opacity: 1}}
28
- exit={{opacity: 0}}
29
- transition={{type: 'tween'}}
30
- top={0}
31
- right={0}
32
- bottom={0}
33
- left={0}
34
- bg="rgba(0, 0, 0, 0.5)"
35
- onClick={() => onDismiss()}
36
- />
37
- <Box
38
- position="fixed"
39
- key="drawer"
40
- as={motion.div}
41
- initial={{x: '100%'}}
42
- animate={{x: 0}}
43
- exit={{x: '100%'}}
44
- transition={{type: 'tween', duration: 0.2}}
45
- width={300}
46
- top={0}
47
- right={0}
48
- bottom={0}
49
- bg="gray.0"
50
- style={{zIndex: 1}}
51
- >
52
- {children}
53
- </Box>
54
- </FocusOn>
55
- </div>
56
- ) : null}
57
- </AnimatePresence>
58
- )
59
-}
6
+const Drawer = ({isOpen, onDismiss, children}) => (
7
+ <AnimatePresence>
8
+ {isOpen ? (
9
+ // These event handlers fix a bug that caused links below the fold
10
+ // to be unclickable in macOS Safari.
11
+ // Reference: https://github.com/theKashey/react-focus-lock/issues/79
12
+ <div
13
+ style={{textAlign: 'start', fontSize: '1rem', lineHeight: '1.5rem'}}
14
+ onMouseDown={event => event.preventDefault()}
15
+ onKeyDown={event => event.target.focus()}
16
+ onClick={event => event.target.focus()}
17
+ role="button"
18
+ tabIndex="0"
19
+ >
20
+ <FocusOn returnFocus={true} onEscapeKey={() => onDismiss()}>
21
+ <Box
22
+ position="fixed"
23
+ key="overlay"
24
+ as={motion.div}
25
+ initial={{opacity: 0}}
26
+ animate={{opacity: 1}}
27
+ exit={{opacity: 0}}
28
+ transition={{type: 'tween'}}
29
+ top={0}
30
+ right={0}
31
+ bottom={0}
32
+ left={0}
33
+ bg="rgba(0, 0, 0, 0.5)"
34
+ onClick={() => onDismiss()}
35
+ />
36
+ <Box
37
+ position="fixed"
38
+ key="drawer"
39
+ as={motion.div}
40
+ initial={{x: '100%'}}
41
+ animate={{x: 0}}
42
+ exit={{x: '100%'}}
43
+ transition={{type: 'tween', duration: 0.2}}
44
+ width={300}
45
+ top={0}
46
+ right={0}
47
+ bottom={0}
48
+ bg="gray.0"
49
+ style={{zIndex: 1}}
50
+ >
51
+ {children}
52
+ </Box>
53
+ </FocusOn>
54
+ </div>
55
+ ) : null}
56
+ </AnimatePresence>
57
+)
58
59
export default Drawer
src/components/head.js
+7
-4
@@ -1,12 +1,15 @@
1
import React from 'react'
2
import {Helmet} from 'react-helmet'
3
import useSiteMetdata from '../hooks/use-site-metadata'
4
+import {useFrontmatter} from '../layout'
5
5
-function Head(props) {
6
+function Head() {
7
+ const fm = useFrontmatter()
8
const siteMetadata = useSiteMetdata()
7
- const title = [props.title, siteMetadata.title].filter(Boolean).join(' | ')
8
- const description = props.description || siteMetadata.description
9
- const lang = props.lang || siteMetadata.lang
9
+
10
+ const title = [fm.title, siteMetadata.title].filter(Boolean).join(' | ')
11
+ const description = fm.description || siteMetadata.description
12
+ const lang = fm.lang || siteMetadata.lang
13
14
return (
15
<Helmet>
src/components/header.js
+22
-27
@@ -9,26 +9,17 @@ import NpmLogo from './npm-logo'
9
import useSearch from '../hooks/use-search'
10
import useSiteMetadata from '../hooks/use-site-metadata'
11
import headerNavItems from '../header-nav.yml'
12
-
13
-export const HEADER_HEIGHT = 66
12
+import {HEADER_HEIGHT} from '../constants'
13
14
const NpmHeaderBar = styled(Box)`
15
height: 10px;
16
background-image: linear-gradient(139deg, #fb8817, #ff4b01, #c12127, #e02aff);
17
`
18
20
-function Header({location, repositoryUrl}) {
19
+function Header() {
20
const siteMetadata = useSiteMetadata()
21
const search = useSearch()
22
24
- const logoStyle = {color: '#cb0000', marginRight: '16px'}
25
- const titleStyle = {
26
- color: '#dddddd',
27
- fontWeight: '600',
28
- display: 'flex',
29
- alignItems: 'center',
30
- }
31
-
23
return (
24
<Box position="sticky" top={0} sx={{zIndex: 1}} role="banner">
25
<NpmHeaderBar />
@@ -41,8 +32,18 @@ function Header({location, repositoryUrl}) {
32
bg="#333333"
33
>
34
<Box display="flex" alignItems="center">
44
- <Link as={GatsbyLink} to="/" style={titleStyle} sx={{mr: 4}}>
45
- <NpmLogo size="32" style={logoStyle} />
35
+ <Link
36
+ as={GatsbyLink}
37
+ to="/"
38
+ style={{
39
+ color: '#dddddd',
40
+ fontWeight: '600',
41
+ display: 'flex',
42
+ alignItems: 'center',
43
+ }}
44
+ sx={{mr: 4}}
45
+ >
46
+ <NpmLogo size="32" style={{color: '#cb0000', marginRight: '16px'}} />
47
{siteMetadata.title}
48
</Link>
49
<Box display={['none', null, null, 'block']} ml={4}>
@@ -51,11 +52,17 @@ function Header({location, repositoryUrl}) {
52
</Box>
53
<Box display="flex">
54
<Box display={['none', null, null, 'block']}>
54
- <HeaderNavItems items={headerNavItems} />
55
+ <Box display="flex" alignItems="center" color="gray.2">
56
+ {headerNavItems.map((item, index) => (
57
+ <Link key={index} href={item.url} sx={{display: 'block', color: 'inherit', ml: 4}}>
58
+ {item.title}
59
+ </Link>
60
+ ))}
61
+ </Box>
62
</Box>
63
<Box display={['flex', null, null, 'none']}>
64
<MobileSearch {...search} />
58
- <NavDrawer location={location} repositoryUrl={repositoryUrl} />
65
+ <NavDrawer />
66
</Box>
67
</Box>
68
</Box>
@@ -63,16 +70,4 @@ function Header({location, repositoryUrl}) {
70
)
71
}
72
66
-function HeaderNavItems({items}) {
67
- return (
68
- <Box display="flex" alignItems="center" color="gray.2">
69
- {items.map((item, index) => (
70
- <Link key={index} href={item.url} sx={{display: 'block', color: 'inherit', ml: 4}}>
71
- {item.title}
72
- </Link>
73
- ))}
74
- </Box>
75
- )
76
-}
77
-
73
export default Header
src/components/hero.js
+1
-1
@@ -10,7 +10,7 @@ function Hero() {
10
<ThemeProvider colorMode="night" nightScheme="dark_dimmed">
11
<Box sx={{bg: 'canvas.default', py: 6}}>
12
<Container>
13
- <Heading as="h1" sx={{color: 'accent.fg', fontSize: 7, m: 0}}>
13
+ <Heading as="h1" sx={{color: 'fg.onEmphasis', fontSize: 7, m: 0}}>
14
{title}
15
</Heading>
16
<Text as="p" sx={{m: 0, color: 'fg.onEmphasis', fontSize: 4}}>
src/components/nav-drawer.js
+20
-28
@@ -25,7 +25,7 @@ const useDrawerIsOpen = () => {
25
return [isOpen, {setOpen, setClose}]
26
}
27
28
-function NavDrawer({location, repositoryUrl}) {
28
+function NavDrawer() {
29
const siteMetadata = useSiteMetadata()
30
const [isOpen, {setOpen, setClose}] = useDrawerIsOpen()
31
@@ -62,39 +62,31 @@ function NavDrawer({location, repositoryUrl}) {
62
</DarkButton>
63
</Box>
64
</Box>
65
- {navItems.length > 0 ? (
66
- <Box display="flex" flexDirection="column">
67
- <NavItems location={location} items={navItems} repositoryUrl={repositoryUrl} />
65
+ <Box display="flex" flexDirection="column">
66
+ <NavItems items={navItems} />
67
+ </Box>
68
+ </Box>
69
+ <Box display="flex" flexDirection="column" flex="1 0 auto" color="gray.1" bg="gray.9">
70
+ {headerNavItems.map((item, index) => (
71
+ <Box
72
+ borderStyle="solid"
73
+ key={item.title}
74
+ borderWidth={0}
75
+ borderRadius={0}
76
+ borderTopWidth={index !== 0 ? 1 : 0}
77
+ borderColor="gray.7"
78
+ p={4}
79
+ >
80
+ <Link key={index} href={item.url} sx={{color: 'inherit', display: 'block'}}>
81
+ {item.title}
82
+ </Link>
83
</Box>
69
- ) : null}
84
+ ))}
85
</Box>
71
- {headerNavItems.length > 0 ? (
72
- <Box display="flex" flexDirection="column" flex="1 0 auto" color="gray.1" bg="gray.9">
73
- <HeaderNavItems items={headerNavItems} />
74
- </Box>
75
- ) : null}
86
</Box>
87
</Drawer>
88
</>
89
)
90
}
91
82
-function HeaderNavItems({items}) {
83
- return items.map((item, index) => (
84
- <Box
85
- borderStyle="solid"
86
- key={item.title}
87
- borderWidth={0}
88
- borderRadius={0}
89
- borderTopWidth={index !== 0 ? 1 : 0}
90
- borderColor="gray.7"
91
- p={4}
92
- >
93
- <Link key={index} href={item.url} sx={{color: 'inherit', display: 'block'}}>
94
- {item.title}
95
- </Link>
96
- </Box>
97
- ))
98
-}
99
-
92
export default NavDrawer
src/components/nav-items.js
+5
-10
@@ -4,6 +4,7 @@ import {Box, StyledOcticon, Link, themeGet} from '@primer/react'
4
import {LinkExternalIcon} from '@primer/octicons-react'
5
import styled from 'styled-components'
6
import NavHierarchy from '../util/nav-hierarchy'
7
+import {useLocation, usePageContext} from '../layout'
8
9
const getActiveClass = props => {
10
const location = NavHierarchy.getLocation(props.location.pathname)
@@ -149,22 +150,16 @@ function thirdLevelItems(items) {
150
)
151
}
152
152
-function NavItems({location, repositoryUrl}) {
153
+function NavItems() {
154
+ const location = useLocation()
155
+ const {repositoryUrl} = usePageContext()
156
const path = NavHierarchy.getLocation(location.pathname)
157
const items = NavHierarchy.getHierarchy(null, {path, hideVariants: true})
158
159
return (
160
<>
161
{topLevelItems(items, path)}
159
- <Box
160
- borderStyle="solid"
161
- borderColor="border.default"
162
- borderWidth={0}
163
- borderTopWidth={1}
164
- borderRadius={0}
165
- py={5}
166
- px={4}
167
- >
162
+ <Box borderStyle="solid" borderColor="border.default" borderWidth={0} borderTopWidth={1} py={3} px={4}>
163
<Link href={repositoryUrl} sx={{color: 'inherit'}}>
164
<Box display="flex" justifyContent="space-between" alignItems="center" color="gray.5">
165
GitHub
src/components/page-footer.js
+2
-2
@@ -1,5 +1,5 @@
1
import React from 'react'
2
-import {Box, StyledOcticon, Link} from '@primer/react'
2
+import {Box, Link, Octicon} from '@primer/react'
3
import {PencilIcon} from '@primer/octicons-react'
4
import Contributors from './contributors'
5
@@ -18,7 +18,7 @@ function PageFooter({editUrl, contributors = {}}) {
18
<Box display="grid" gridGap={4}>
19
{editUrl != null ? (
20
<Link href={editUrl}>
21
- <StyledOcticon icon={PencilIcon} mr={2} />
21
+ <Octicon icon={PencilIcon} mr={2} />
22
Edit this page on GitHub
23
</Link>
24
) : null}
src/components/sidebar.js
+22
-24
@@ -1,35 +1,33 @@
1
import {Box} from '@primer/react'
2
import React from 'react'
3
-import {HEADER_HEIGHT} from './header'
3
import NavItems from './nav-items'
4
import navItems from '../nav.yml'
5
+import {HEADER_HEIGHT} from '../constants'
6
7
-function Sidebar({location, repositoryUrl}) {
8
- return (
7
+const Sidebar = () => (
8
+ <Box
9
+ position="sticky"
10
+ top={HEADER_HEIGHT}
11
+ height={`calc(100vh - ${HEADER_HEIGHT}px)`}
12
+ minWidth={260}
13
+ color="gray.8"
14
+ bg="gray.0"
15
+ role="navigation"
16
+ >
17
<Box
10
- position="sticky"
11
- top={HEADER_HEIGHT}
12
- height={`calc(100vh - ${HEADER_HEIGHT}px)`}
13
- minWidth={260}
14
- color="gray.8"
15
- bg="gray.0"
16
- role="navigation"
18
+ borderStyle="solid"
19
+ borderColor="border.default"
20
+ borderWidth={0}
21
+ borderRightWidth={1}
22
+ borderRadius={0}
23
+ height="100%"
24
+ style={{overflow: 'auto'}}
25
>
18
- <Box
19
- borderStyle="solid"
20
- borderColor="border.default"
21
- borderWidth={0}
22
- borderRightWidth={1}
23
- borderRadius={0}
24
- height="100%"
25
- style={{overflow: 'auto'}}
26
- >
27
- <Box display="flex" flexDirection="column" role="list">
28
- <NavItems location={location} items={navItems} repositoryUrl={repositoryUrl} />
29
- </Box>
26
+ <Box display="flex" flexDirection="column" role="list">
27
+ <NavItems items={navItems} />
28
</Box>
29
</Box>
32
- )
33
-}
30
+ </Box>
31
+)
32
33
export default Sidebar
src/components/table-of-contents.js
+70
-23
@@ -1,27 +1,74 @@
1
import React from 'react'
2
-import {Box, Link} from '@primer/react'
2
+import {Box, Link, Text, Octicon, Details} from '@primer/react'
3
+import {ChevronDownIcon, ChevronRightIcon} from '@primer/octicons-react'
4
+import {usePageContext} from '../layout'
5
+import {HEADER_HEIGHT} from '../constants'
6
4
-function TableOfContents({items, depth = 0, labelId}) {
5
- return (
6
- <Box
7
- key={items}
8
- as="ul"
9
- role="list"
10
- m={0}
11
- p={0}
12
- css={{listStyle: 'none', lineHeight: '1.4em'}}
13
- aria-labelledby={labelId}
14
- >
15
- {items.map(item => (
16
- <Box as="li" role="listitem" key={item.url} pl={depth > 0 ? 3 : 0}>
17
- <Link key={item.title} href={item.url} sx={{display: 'inline-block', py: 1, color: 'gray.6'}}>
18
- {item.title}
19
- </Link>
20
- {item.items ? <TableOfContents items={item.items} depth={depth + 1} /> : null}
21
- </Box>
22
- ))}
23
- </Box>
24
- )
7
+const TableOfContents = ({items, depth = 0, labelId}) => (
8
+ <Box
9
+ key={items}
10
+ as="ul"
11
+ role="list"
12
+ m={0}
13
+ p={0}
14
+ css={{listStyle: 'none', lineHeight: '1.4em'}}
15
+ aria-labelledby={labelId}
16
+ >
17
+ {items.map(item => (
18
+ <Box as="li" role="listitem" key={item.url} pl={depth > 0 ? 3 : 0}>
19
+ <Link key={item.title} href={item.url} sx={{display: 'inline-block', py: 1, color: 'gray.6'}}>
20
+ {item.title}
21
+ </Link>
22
+ {item.items ? <TableOfContents items={item.items} depth={depth + 1} /> : null}
23
+ </Box>
24
+ ))}
25
+ </Box>
26
+)
27
+
28
+const withTableOfContent = Component => {
29
+ const TOC = props => {
30
+ const {tableOfContents} = usePageContext()
31
+ if (!tableOfContents) {
32
+ return null
33
+ }
34
+ return <Component {...props} items={tableOfContents} />
35
+ }
36
+ return TOC
37
}
38
27
-export default TableOfContents
39
+export const Mobile = withTableOfContent(({items}) => (
40
+ <Box display={['block', null, 'none']} mb={3}>
41
+ <Details>
42
+ {({open}) => (
43
+ <>
44
+ <Text as="summary" fontWeight="bold">
45
+ <Octicon icon={open ? ChevronDownIcon : ChevronRightIcon} mr={2} />
46
+ Table of contents
47
+ </Text>
48
+ <Box pt={1}>
49
+ <TableOfContents items={items} />
50
+ </Box>
51
+ </>
52
+ )}
53
+ </Details>
54
+ </Box>
55
+))
56
+
57
+export const Desktop = withTableOfContent(({items}) => (
58
+ <Box
59
+ display={['none', null, 'block']}
60
+ css={{gridArea: 'table-of-contents', overflow: 'auto'}}
61
+ position="sticky"
62
+ top={HEADER_HEIGHT + 24}
63
+ mt="6px"
64
+ maxHeight={`calc(100vh - ${HEADER_HEIGHT}px - 24px)`}
65
+ pr={1}
66
+ pl={1}
67
+ pb={1}
68
+ >
69
+ <Text display="inline-block" fontWeight="bold" mb={1} id="table-of-content-label">
70
+ Table of contents
71
+ </Text>
72
+ <TableOfContents items={items} labelId="table-of-content-label" />
73
+ </Box>
74
+))
src/components/variant-select.js
+55
-43
@@ -1,45 +1,49 @@
1
import React from 'react'
2
-import {ActionList, ActionMenu} from '@primer/react'
2
+import {ActionList, ActionMenu, Box} from '@primer/react'
3
import NavHierarchy from '../util/nav-hierarchy'
4
+import {useLocation} from '../layout'
5
5
-// VariantSelect: allows a variant to be set up within a document hierarchy
6
-//
7
-// For example, given two paths `/docs/v1.0/foo` and `/docs/v2.0/foo`, the
8
-// second folder acts as a variant. If you use <VariantSelect root="/docs">
9
-// then you'll get a selection for the different variants (v1.0, v2.0).
6
+const VariantItem = ({match, active}) => {
7
+ const {variant, page} = match
8
11
-const VariantSelect = ({variantPages, path}) => {
12
- const [open, setOpen] = React.useState(false)
13
- const anchorClickHandler = React.useCallback((event, url) => {
14
- event.preventDefault()
15
- window.location.href = `${url}?v=true`
16
- }, [])
9
+ const handleClick = React.useCallback(
10
+ event => {
11
+ event.preventDefault()
12
+ window.location.href = `${page.url}?v=true`
13
+ },
14
+ [page.url],
15
+ )
16
+
17
+ const handleKey = React.useCallback(
18
+ event => {
19
+ if (event.key === 'Enter') {
20
+ window.location.href = `${page.url}?v=true`
21
+ }
22
+ },
23
+ [page.url],
24
+ )
25
+
26
+ return (
27
+ <ActionList.Item onKeyDown={handleKey} onClick={handleClick} id={variant.shortName} active={active}>
28
+ {variant.title}
29
+ </ActionList.Item>
30
+ )
31
+}
32
18
- const onItemEnterKey = React.useCallback((event, url) => {
19
- if (event.key === 'Enter') {
20
- window.location.href = `${url}?v=true`
21
- }
22
- }, [])
33
+const VariantMenu = ({variants, path}) => {
34
+ const [open, setOpen] = React.useState(false)
35
24
- let selectedItem = variantPages[0]
25
- const items = variantPages.map((match, index) => {
26
- let active = false
27
- if (match.page.url === path) {
28
- selectedItem = match
29
- active = true
30
- }
31
- return (
32
- <ActionList.Item
33
- onKeyDown={e => onItemEnterKey(e, match.page.url)}
34
- onClick={e => anchorClickHandler(e, match.page.url)}
35
- id={match.variant.shortName}
36
- key={index}
37
- active={active}
38
- >
39
- {match.variant.title}
40
- </ActionList.Item>
41
- )
42
- })
36
+ const {selected, items} = variants.reduce(
37
+ (acc, match, key) => {
38
+ const active = match.page.url === path
39
+ if (active) {
40
+ acc.selected = match
41
+ }
42
+ acc.items.push({match, key, active})
43
+ return acc
44
+ },
45
+ {selected: variants[0], items: []},
46
+ )
47
48
return (
49
<>
@@ -49,11 +53,13 @@ const VariantSelect = ({variantPages, path}) => {
53
in a previous accessibility audit which did not trigger the lint warning. */
54
/* eslint-disable-next-line jsx-a11y/no-autofocus */}
55
<ActionMenu.Button autoFocus aria-describedby="label-versions-list-item">
52
- {selectedItem.variant.title}
56
+ {selected.variant.title}
57
</ActionMenu.Button>
58
<ActionMenu.Overlay width="medium" onEscape={() => setOpen(false)}>
59
<ActionList id="versions-list-item" aria-labelledby="label-versions-list-item">
56
- {items}
60
+ {items.map(item => (
61
+ <VariantItem key={item.key} {...item} />
62
+ ))}
63
</ActionList>
64
</ActionMenu.Overlay>
65
</ActionMenu>
@@ -61,16 +67,22 @@ const VariantSelect = ({variantPages, path}) => {
67
)
68
}
69
64
-const VariantSelectLocation = ({root, location}) => {
70
+const VariantSelect = () => {
71
+ const location = useLocation()
72
+ const root = NavHierarchy.getVariantRoot(location.pathname)
73
const path = NavHierarchy.getPath(location.pathname)
74
const vp = NavHierarchy.getVariantAndPage(root, path)
67
- const variantPages = vp ? NavHierarchy.getVariantsForPage(root, vp.page) : []
75
+ const variants = vp ? NavHierarchy.getVariantsForPage(root, vp.page) : []
76
69
- if (!variantPages.length) {
77
+ if (!variants.length) {
78
return null
79
}
80
73
- return <VariantSelect variantPages={variantPages} path={path} />
81
+ return (
82
+ <Box css={{'margin-top': '25px'}}>
83
+ <VariantMenu variantPages={variants} path={path} />
84
+ </Box>
85
+ )
86
}
87
76
-export default VariantSelectLocation
88
+export default VariantSelect
src/constants.js
new
+3
@@ -0,0 +1,3 @@
1
+export const HEADER_HEIGHT = 66
2
+
3
+export const SKIP_NAV = 'skip-nav'
src/favicon.png
renamed
src/hooks/use-search.js
+2
-3
@@ -4,9 +4,8 @@ import {navigate, graphql, useStaticQuery} from 'gatsby'
4
import {useIsMobile} from './use-breakpoint'
5
6
const useSearchData = () => {
7
- // TODO: this static query runs for all pages changes in dev mode.
8
- // When not testing search explicitly it should be replaced with
9
- // some static data.
7
+ // TODO: this static query runs for all pages changes in dev mode. When not
8
+ // testing search explicitly it should be replaced with some static data.
9
const rawData = useStaticQuery(graphql`
10
{
11
allMdx {
src/hooks/use-slugger.js
deleted
-7
@@ -1,7 +0,0 @@
1
-import React from 'react'
2
-import Slugger from 'github-slugger'
3
-
4
-const SluggerContext = React.createContext(null)
5
-
6
-export const Provider = props => <SluggerContext.Provider value={new Slugger()} {...props} />
7
-export const useSlugger = () => React.useContext(SluggerContext)
src/layout.js
new
+45
@@ -0,0 +1,45 @@
1
+import React from 'react'
2
+import {Box} from '@primer/react'
3
+import Slugger from 'github-slugger'
4
+import Head from './components/head'
5
+import Header from './components/header'
6
+import Sidebar from './components/sidebar'
7
+import {SKIP_NAV} from './constants'
8
+
9
+const SluggerContext = React.createContext(null)
10
+const PageContext = React.createContext(null)
11
+const LocationContext = React.createContext(null)
12
+
13
+export const useSlugger = () => React.useContext(SluggerContext)
14
+export const usePageContext = () => React.useContext(PageContext)
15
+export const useLocation = () => React.useContext(LocationContext)
16
+export const useFrontmatter = () => {
17
+ const pageContext = usePageContext()
18
+ return pageContext.frontmatter || {}
19
+}
20
+
21
+const withLayout = Component => {
22
+ const LayoutProvider = props => (
23
+ <SluggerContext.Provider value={new Slugger()}>
24
+ <PageContext.Provider value={props.pageContext}>
25
+ <LocationContext.Provider value={props.location}>
26
+ <Box display="flex" flexDirection="column" minHeight="100vh">
27
+ <Head />
28
+ <Header />
29
+ <Box display="flex" flex="1 1 auto" flexDirection="row" css={{zIndex: 0}} role="main">
30
+ <Box display={['none', null, null, 'block']}>
31
+ <Sidebar />
32
+ </Box>
33
+ <Box id={SKIP_NAV}>
34
+ <Component {...props} />
35
+ </Box>
36
+ </Box>
37
+ </Box>
38
+ </LocationContext.Provider>
39
+ </PageContext.Provider>
40
+ </SluggerContext.Provider>
41
+ )
42
+ return LayoutProvider
43
+}
44
+
45
+export default withLayout
src/layout/default.js
+36
-107
@@ -1,119 +1,48 @@
1
import React from 'react'
2
-import {Box, Heading, StyledOcticon, Text, Details} from '@primer/react'
3
-import {ChevronDownIcon, ChevronRightIcon} from '@primer/octicons-react'
4
-import Head from '../components/head'
5
-import Header, {HEADER_HEIGHT} from '../components/header'
2
+import {Box, Heading} from '@primer/react'
3
import PageFooter from '../components/page-footer'
7
-import Sidebar from '../components/sidebar'
8
-import TableOfContents from '../components/table-of-contents'
4
+import * as TableOfContents from '../components/table-of-contents'
5
import VariantSelect from '../components/variant-select'
10
-import * as Slugger from '../hooks/use-slugger'
11
-import NavHierarchy from '../util/nav-hierarchy'
6
+import withLayout from '../layout'
7
13
-function Layout({children, pageContext, location}) {
14
- const {repositoryUrl, editUrl, contributors, frontmatter, tableOfContents} = pageContext
8
+const Layout = ({children, pageContext: {frontmatter}}) => {
9
const {title, description} = frontmatter
10
17
- const variantRoot = NavHierarchy.getVariantRoot(location.pathname)
18
-
11
return (
20
- <Slugger.Provider>
21
- <Box display="flex" flexDirection="column" minHeight="100vh">
22
- <Head title={title} description={description} />
23
- <Header repositoryUrl={repositoryUrl} location={location} />
24
- <Box display="flex" flex="1 1 auto" flexDirection="row" css={{zIndex: 0}} role="main">
25
- <Box display={['none', null, null, 'block']}>
26
- <Sidebar repositoryUrl={repositoryUrl} location={location} />
27
- </Box>
28
- <Box
29
- display="grid"
30
- id="skip-nav"
31
- maxWidth="100%"
32
- gridTemplateColumns={['100%', null, 'minmax(0, 65ch) 220px']}
33
- gridTemplateAreas={['"heading" "content"', null, '"heading table-of-contents" "content table-of-contents"']}
34
- gridColumnGap={[null, null, 6, 7]}
35
- gridRowGap={3}
36
- mx="auto"
37
- p={[5, 6, null, 7]}
38
- css={{alignItems: 'start', alignSelf: 'start'}}
39
- role="region"
40
- >
41
- <Box css={{gridArea: 'heading'}}>
42
- <Box
43
- borderStyle="solid"
44
- borderColor="border.default"
45
- borderWidth={0}
46
- borderBottomWidth={1}
47
- borderRadius={0}
48
- pb={2}
49
- >
50
- <Box>
51
- <Box>
52
- <Heading as="h1">{title}</Heading>
53
- {description}
54
- </Box>
55
- </Box>
56
- </Box>
57
- {variantRoot != null ? (
58
- <Box css={{'margin-top': '25px'}}>
59
- <VariantSelect
60
- overlay={true}
61
- direction="se"
62
- menuWidth="min(30ch, 500px)"
63
- root={variantRoot}
64
- location={location}
65
- />
66
- </Box>
67
- ) : null}
68
- </Box>
69
- {tableOfContents ? (
70
- <Box
71
- display={['none', null, 'block']}
72
- css={{gridArea: 'table-of-contents', overflow: 'auto'}}
73
- position="sticky"
74
- top={HEADER_HEIGHT + 24}
75
- mt="6px"
76
- maxHeight={`calc(100vh - ${HEADER_HEIGHT}px - 24px)`}
77
- pr={1}
78
- pl={1}
79
- pb={1}
80
- >
81
- <Text display="inline-block" fontWeight="bold" mb={1} id="table-of-content-label">
82
- Table of contents
83
- </Text>
84
- <TableOfContents items={tableOfContents} labelId="table-of-content-label" />
85
- </Box>
86
- ) : null}
87
- <Box css={{gridArea: 'content'}}>
88
- {tableOfContents ? (
89
- <Box display={['block', null, 'none']} mb={3}>
90
- <Details>
91
- {({open}) => (
92
- <>
93
- <Text as="summary" fontWeight="bold">
94
- {open ? (
95
- <StyledOcticon icon={ChevronDownIcon} mr={2} />
96
- ) : (
97
- <StyledOcticon icon={ChevronRightIcon} mr={2} />
98
- )}
99
- Table of contents
100
- </Text>
101
- <Box pt={1}>
102
- <TableOfContents items={tableOfContents} />
103
- </Box>
104
- </>
105
- )}
106
- </Details>
107
- </Box>
108
- ) : null}
109
- {children}
110
- <PageFooter editUrl={editUrl} contributors={contributors} />
111
- </Box>
112
- </Box>
12
+ <Box
13
+ display="grid"
14
+ maxWidth="100%"
15
+ gridTemplateColumns={['100%', null, 'minmax(0, 65ch) 220px']}
16
+ gridTemplateAreas={['"heading" "content"', null, '"heading table-of-contents" "content table-of-contents"']}
17
+ gridColumnGap={[null, null, 6, 7]}
18
+ gridRowGap={3}
19
+ mx="auto"
20
+ p={[5, 6, null, 7]}
21
+ css={{alignItems: 'start', alignSelf: 'start'}}
22
+ role="region"
23
+ >
24
+ <Box css={{gridArea: 'heading'}}>
25
+ <Box
26
+ borderStyle="solid"
27
+ borderColor="border.default"
28
+ borderWidth={0}
29
+ borderBottomWidth={1}
30
+ borderRadius={0}
31
+ pb={2}
32
+ >
33
+ <Heading as="h1">{title}</Heading>
34
+ {description}
35
</Box>
36
+ <VariantSelect />
37
+ </Box>
38
+ <TableOfContents.Desktop />
39
+ <Box css={{gridArea: 'content'}}>
40
+ <TableOfContents.Mobile />
41
+ {children}
42
+ <PageFooter />
43
</Box>
115
- </Slugger.Provider>
44
+ </Box>
45
)
46
}
47
119
-export default Layout
48
+export default withLayout(Layout)
src/layout/hero.js
+7
-21
@@ -1,28 +1,14 @@
1
import React from 'react'
2
import {Box} from '@primer/react'
3
import Container from '../components/container'
4
-import Head from '../components/head'
5
-import Header from '../components/header'
4
import Hero from '../components/hero'
7
-import Sidebar from '../components/sidebar'
8
-import * as Slugger from '../hooks/use-slugger'
5
+import withLayout from '../layout'
6
10
-const HeroLayout = ({children, location, pageContext: {repositoryUrl}}) => (
11
- <Slugger.Provider>
12
- <Box display="flex" flexDirection="column" minHeight="100vh">
13
- <Head />
14
- <Header location={location} repositoryUrl={repositoryUrl} />
15
- <Box display="flex" flex="1 1 auto" flexDirection="row" role="main">
16
- <Box display={['none', null, null, 'block']}>
17
- <Sidebar repositoryUrl={repositoryUrl} location={location} />
18
- </Box>
19
- <Box width="100%">
20
- <Hero />
21
- <Container>{children}</Container>
22
- </Box>
23
- </Box>
24
- </Box>
25
- </Slugger.Provider>
7
+const HeroLayout = ({children}) => (
8
+ <Box width="100%">
9
+ <Hero />
10
+ <Container>{children}</Container>
11
+ </Box>
12
)
13
28
-export default HeroLayout
14
+export default withLayout(HeroLayout)
src/mdx/index.js
+3
-3
@@ -6,8 +6,8 @@ import {LinkIcon} from '@primer/octicons-react'
6
import textContent from 'react-addons-text-content'
7
import Code from './code'
8
import NavHierarchy from './nav-hierarchy'
9
-import {HEADER_HEIGHT} from '../components/header'
10
-import {useSlugger} from '../hooks/use-slugger'
9
+import {useSlugger} from '../layout'
10
+import {HEADER_HEIGHT, SKIP_NAV} from '../constants'
11
12
const required = (prop, name) => {
13
if (!prop) {
@@ -25,7 +25,7 @@ export {Code, NavHierarchy as Index}
25
export const Pre = ({children}) => children
26
27
const SkipLinkBase = props => (
28
- <PrimerLink {...props} href="#skip-nav" sx={{backgroundColor: 'blue.6', color: 'white', p: 3, fontSize: 1}}>
28
+ <PrimerLink {...props} href={`#${SKIP_NAV}`} sx={{backgroundColor: 'blue.6', color: 'white', p: 3, fontSize: 1}}>
29
Skip to content
30
</PrimerLink>
31
)
src/mdx/nav-hierarchy.js
+1
-1
@@ -1,8 +1,8 @@
1
import React from 'react'
2
-import {useLocation} from '@reach/router' // eslint-disable-line import/no-unresolved
2
import {Box, Link} from '@primer/react'
3
import {Link as GatsbyLink} from 'gatsby'
4
import NavHierarchy from '../util/nav-hierarchy'
5
+import {useLocation} from '../layout'
6
7
function showHierarchy(items, props, depth = 1) {
8
let hierarchy
src/pages/404.js
+2
-1
@@ -1,7 +1,8 @@
1
import React from 'react'
2
+import withLayout from '../layout'
3
4
const Page404 = () => {
5
return <h1>404 page</h1>
6
}
7
7
-export default Page404
8
+export default withLayout(Page404)
src/util/nav-hierarchy.js
+1
-1
@@ -191,7 +191,7 @@ const NavHierarchy = {
191
},
192
193
getVariantAndPage(root, path) {
194
- if (!path.startsWith(`${root}/`)) {
194
+ if (!root || !path.startsWith(`${root}/`)) {
195
return null
196
}
197