Refactor to use new primer navlist
Luke Karrys committed
Oct 16, 2023 at 14:01 UTC
3adbd1d5febc0aba966172d9376763f54f351c84
16 files changed
+334
-375
CONTRIBUTING.md
+13
-1
@@ -27,7 +27,19 @@ First, `npm install` the dependencies. This will install Gatsby, et al.
27
28
Next, `npm run develop` to start the test server to view your changes. The Gatsby server will be started on port 8000. You can navigate to `http://localhost:8000` to view the site live.
29
30
-In order to cut down on the time it takes `npm run develop` to complete, you can use the environment variable `GATSBY_PARTIAL_CONTENT` to only build some pages. For example, if the only pages you need to test locally are in `/cli/v10/commands`, then you can run the site locally with `GATSBY_PARTIAL_CONTENT=cli/v10/commands npm run develop`.
30
+In order to cut down on the time it takes `npm run develop` to complete, you can use the environment variables `GATSBY_CONTENT_ALLOW` and `GATSBY_CONTENT_IGNORE` to only build some pages.
31
+
32
+For example, if the only pages you need to test locally are in `/cli/v10/commands`, then you can run the the following to build the `cli/v10/commands` pages:
33
+
34
+```sh
35
+GATSBY_CONTENT_ALLOW=cli/v10/commands npm run develop
36
+```
37
+
38
+The `content/cli` directory has the most pages so it tends to be most helpful to ignore older CLI versions unless you need to explicitly test those pages:
39
+
40
+```sh
41
+GATSBY_CONTENT_IGNORE=cli/v6,cli/v7,cli/v8,cli/v9 npm run develop
42
+```
43
44
**For best results use Node 18 and npm 9+**
45
gatsby-config.js
+17
-7
@@ -1,7 +1,7 @@
1
const path = require('path')
2
const fs = require('fs')
3
4
-const {NODE_ENV, GATSBY_PARTIAL_CONTENT, GATSBY_CONTENT_IGNORE, GATSBY_CONTENT_DIR = 'content'} = process.env
4
+const {NODE_ENV, GATSBY_CONTENT_ALLOW, GATSBY_CONTENT_IGNORE, GATSBY_CONTENT_DIR = 'content'} = process.env
5
const DEV = NODE_ENV === 'development'
6
const CONTENT_DIR = path.resolve(GATSBY_CONTENT_DIR)
7
@@ -15,28 +15,38 @@ const walkDirs = dir => {
15
}
16
17
const getContentOptions = () => {
18
- if (!DEV || (!GATSBY_PARTIAL_CONTENT && !GATSBY_CONTENT_IGNORE)) {
18
+ if (!DEV || (!GATSBY_CONTENT_ALLOW && !GATSBY_CONTENT_IGNORE)) {
19
return
20
}
21
22
- const partialContent = (GATSBY_PARTIAL_CONTENT ?? '').split(',')
22
+ const allowContent = (GATSBY_CONTENT_ALLOW ?? '').split(',').filter(Boolean)
23
+ const ignoreContent = (GATSBY_CONTENT_IGNORE ?? '').split(',').filter(Boolean)
24
25
const paths = walkDirs(CONTENT_DIR)
26
.map(p => path.relative(CONTENT_DIR, p))
27
.sort()
28
.reduce(
29
(acc, p) => {
29
- const include = partialContent.some(partial => partial.startsWith(p))
30
- acc[include ? 'include' : 'ignore'].push(p)
30
+ const allow = allowContent.length ? allowContent.includes(p) : null
31
+ const ignore = ignoreContent.length ? ignoreContent.includes(p) : null
32
+ if (ignore === true || allow === false) {
33
+ acc.ignore.push(p)
34
+ } else {
35
+ acc.include.push(p)
36
+ }
37
return acc
38
},
39
{include: [], ignore: []},
40
)
41
36
- console.log(`Only including the following partial content in dev mode:\n - ${paths.include.join('\n - ')}`)
42
+ const ignoreGlobs = paths.ignore.map(p => path.join('**', p, '**'))
43
+
44
+ console.log(`Only including the following partial content in dev mode`)
45
+ console.log(`Allow:\n - ${paths.include.join('\n - ')}`)
46
+ console.log(`Ignore:\n - ${ignoreGlobs.join('\n - ')}`)
47
48
return {
39
- ignore: paths.ignore,
49
+ ignore: ignoreGlobs,
50
}
51
}
52
src/components/breadcrumbs.js
new
+30
@@ -0,0 +1,30 @@
1
+import React from 'react'
2
+import {Breadcrumbs as PrimerBreadcrumbs} from '@primer/react'
3
+import {withPrefix} from 'gatsby'
4
+import * as getNav from '../util/get-nav'
5
+import {useLocation} from '../layout'
6
+
7
+const Breadcrumbs = () => {
8
+ const {pathname} = useLocation()
9
+ const items = getNav.getItemBreadcrumbs(pathname)
10
+
11
+ if (items.length <= 1) {
12
+ return null
13
+ }
14
+
15
+ return (
16
+ <PrimerBreadcrumbs sx={{mb: 4}}>
17
+ {items.map(item => (
18
+ <PrimerBreadcrumbs.Item
19
+ key={item.url}
20
+ href={withPrefix(item.url)}
21
+ selected={getNav.isActiveUrl(pathname, item.url)}
22
+ >
23
+ {item.title}
24
+ </PrimerBreadcrumbs.Item>
25
+ ))}
26
+ </PrimerBreadcrumbs>
27
+ )
28
+}
29
+
30
+export default Breadcrumbs
src/components/nav-items.js
+38
-138
@@ -1,159 +1,59 @@
1
import React from 'react'
2
import {Link as GatsbyLink} from 'gatsby'
3
-import {Box, Octicon, Link, themeGet} from '@primer/react'
3
+import {NavList} from '@primer/react/drafts'
4
import {LinkExternalIcon} from '@primer/octicons-react'
5
-import styled from 'styled-components'
6
-import getNav from '../util/get-nav'
5
+import * as getNav from '../util/get-nav'
6
import {useLocation, usePageContext} from '../layout'
7
+import VisuallyHidden from './visually-hidden'
8
9
-const getActiveClass = props => {
10
- const location = getNav.getLocation(props.location.pathname)
11
- const href = getNav.getLocation(props.href)
12
- return getNav.isActiveUrl(location, href) ? 'active' : ''
13
-}
14
-
15
-const ActiveLink = ({className, children, ...props}) => (
16
- <Link as={GatsbyLink} getProps={p => ({className: `${className} ${getActiveClass(p)}`})} {...props}>
17
- {children}
18
- </Link>
19
-)
9
+const NavItem = ({item, path}) => {
10
+ const href = getNav.getLocation(item.url)
11
+ const isCurrent = getNav.isActiveUrl(path, href)
12
+ const items = getNav.getHierarchy(item, {path: item.url, hideVariants: true})
13
21
-const withItems = Component => {
22
- const WithItems = ({parent, path}) => {
23
- if (!parent || getNav.isActiveUrl(path, parent.url)) {
24
- const items = getNav.getHierarchy(parent, {path, hideVariants: true})
25
- if (items) {
26
- return <Component items={items} path={path} />
27
- }
28
- }
29
- return null
30
- }
31
- return WithItems
14
+ return (
15
+ <NavList.Item as={GatsbyLink} to={href} defaultOpen={items && isCurrent} aria-current={isCurrent ? 'page' : null}>
16
+ {item.title}
17
+ {items ? (
18
+ <NavList.SubNav>
19
+ <NavItems items={items} path={path} />
20
+ </NavList.SubNav>
21
+ ) : null}
22
+ </NavList.Item>
23
+ )
24
}
25
34
-const NavLink = styled(ActiveLink)`
35
- color: inherit;
36
- text-decoration: none;
37
- :hover {
38
- text-decoration: underline;
39
- }
40
-`
41
-
42
-const TopLevelLink = styled(NavLink)`
43
- &.active {
44
- font-weight: ${themeGet('fontWeights.bold')};
45
- color: ${themeGet('colors.gray.8')};
46
- }
47
- &.activePage {
48
- color: ${themeGet('colors.gray.8')};
49
- }
50
-`
51
-
52
-const TopLevelItems = withItems(({items, path}) => (
26
+const NavItems = ({items, path}) => (
27
<>
28
{items.map(item => (
55
- <Box
56
- key={item.title}
57
- role="listitem"
58
- sx={{
59
- borderStyle: 'solid',
60
- borderColor: 'border.default',
61
- borderWidth: 0,
62
- borderRadius: 0,
63
- borderTopWidth: 1,
64
- py: 3,
65
- px: 4,
66
- }}
67
- >
68
- <Box sx={{display: 'flex', flexDirection: 'column'}}>
69
- <TopLevelLink to={item.url} key={item.title}>
70
- {item.title}
71
- </TopLevelLink>
72
- <SecondLevelItems parent={item} path={path} />
73
- </Box>
74
- </Box>
29
+ <NavItem key={item.title} item={item} path={path} />
30
))}
31
</>
77
-))
78
-
79
-const SecondLevelLink = styled(NavLink)`
80
- display: block;
81
- font-size: ${themeGet('fontSizes.1')};
82
- padding-top: ${themeGet('space.1')};
83
- padding-bottom: ${themeGet('space.1')};
84
- margin-top: ${themeGet('space.2')};
85
- &.active {
86
- font-weight: ${themeGet('fontWeights.bold')};
87
- color: ${themeGet('colors.gray.8')};
88
- }
89
-`
90
-
91
-const Description = styled(Box)`
92
- & {
93
- color: ${themeGet('colors.gray.6')};
94
- font-size: 0.8em;
95
- font-weight: normal;
96
- }
97
-`
98
-
99
-const SecondLevelItems = withItems(({items, path}) => (
100
- <Box sx={{display: 'flex', flexDirection: 'column', mt: 2}} role="list">
101
- {items.map(item => (
102
- <Box key={item.title} role="listitem">
103
- <SecondLevelLink key={item.url} to={item.url}>
104
- {item.title}
105
- {item.description ? <Description>{item.description}</Description> : null}
106
- </SecondLevelLink>
107
- <ThirdLevelItems parent={item} path={path} />
108
- </Box>
109
- ))}
110
- </Box>
111
-))
112
-
113
-const ThirdLevelLink = styled(NavLink)`
114
- display: block;
115
- font-size: ${themeGet('fontSizes.1')};
116
- padding-top: ${themeGet('space.1')};
117
- padding-bottom: ${themeGet('space.1')};
118
- border-left: solid 1px ${themeGet('colors.gray.3')};
119
- padding-left: calc(${themeGet('space.2')} + (${themeGet('space.1')} - 1px));
120
- color: ${themeGet('colors.blue.5')};
121
- &.active {
122
- border-left: solid ${themeGet('space.1')} ${themeGet('colors.gray.3')};
123
- padding-left: ${themeGet('space.2')};
124
- color: ${themeGet('colors.gray.8')};
125
- }
126
-`
127
-
128
-const ThirdLevelItems = withItems(({items}) => (
129
- <Box sx={{display: 'flex', flexDirection: 'column', mt: 2}} role="list">
130
- {items.map(item => (
131
- <Box key={item.title} role="listitem">
132
- <ThirdLevelLink key={item.url} to={item.url}>
133
- {item.title}
134
- </ThirdLevelLink>
135
- </Box>
136
- ))}
137
- </Box>
138
-))
32
+)
33
140
-function NavItems() {
141
- const {repositoryUrl} = usePageContext()
34
+const Navigation = () => {
35
const location = useLocation()
36
+ const {repositoryUrl} = usePageContext()
37
+ const path = getNav.getLocation(location.pathname)
38
+ const items = getNav.getHierarchy(null, {path, hideVariants: true})
39
40
return (
41
<>
146
- <TopLevelItems path={getNav.getLocation(location.pathname)} />
147
- <Box sx={{borderStyle: 'solid', borderColor: 'border.default', borderWidth: 0, borderTopWidth: 1, py: 3, px: 4}}>
148
- <Link href={repositoryUrl} sx={{color: 'inherit'}}>
149
- <Box sx={{display: 'flex', justifyContent: 'space-between', alignItems: 'center', color: 'gray.5'}}>
150
- GitHub
151
- <Octicon icon={LinkExternalIcon} sx={{color: 'gray.5'}} />
152
- </Box>
153
- </Link>
154
- </Box>
42
+ <VisuallyHidden>
43
+ <h3>Site navigation</h3>
44
+ </VisuallyHidden>
45
+ <NavList aria-label="Site">
46
+ <NavItems items={items} path={path} />
47
+ <NavList.Divider />
48
+ <NavList.Item href={repositoryUrl}>
49
+ GitHub
50
+ <NavList.TrailingVisual>
51
+ <LinkExternalIcon />
52
+ </NavList.TrailingVisual>
53
+ </NavList.Item>
54
+ </NavList>
55
</>
56
)
57
}
58
159
-export default NavItems
59
+export default Navigation
src/components/search-results.js
+3
-8
@@ -1,18 +1,13 @@
1
import React from 'react'
2
import {Box, Text} from '@primer/react'
3
import useSiteMetadata from '../hooks/use-site-metadata'
4
-import getNav from '../util/get-nav'
4
+import * as getNav from '../util/get-nav'
5
6
const Breadcrumbs = ({item, highlighted}) => {
7
const siteMetadata = useSiteMetadata()
8
- const hierarchy = getNav.getItemHierarchy(item.path)
8
+ const hierarchy = getNav.getItemBreadcrumbs(item.path)
9
10
- const text = hierarchy
11
- ? hierarchy
12
- .slice(0, -1)
13
- .map(item => item.shortName || item.title)
14
- .join(' / ')
15
- : siteMetadata.shortName
10
+ const text = hierarchy ? hierarchy.slice(0, -1).join(' / ') : siteMetadata.shortName
11
12
return <Text sx={{fontSize: 0, color: highlighted ? 'blue.2' : 'gray.7'}}>{text}</Text>
13
}
src/components/sidebar.js
-1
@@ -47,7 +47,6 @@ const Sidebar = () => (
47
height: '100%',
48
borderStyle: 'solid',
49
borderColor: 'border.subtle',
50
- px: 2,
50
}}
51
>
52
<Box sx={{display: 'flex', flexDirection: 'column'}} role="list">
src/components/table-of-contents.js
-1
@@ -62,7 +62,6 @@ export const Desktop = withTableOfContents(({items}) => (
62
pr: 1,
63
pl: 1,
64
pb: 1,
65
- gridArea: 'table-of-contents',
65
overflow: 'auto',
66
}}
67
>
src/components/variant-select.js
+1
-1
@@ -1,7 +1,7 @@
1
import React from 'react'
2
import {ActionList, ActionMenu, Box} from '@primer/react'
3
import {navigate} from 'gatsby'
4
-import getNav from '../util/get-nav'
4
+import * as getNav from '../util/get-nav'
5
import {useLocation} from '../layout'
6
7
const VariantItem = ({match, active}) => {
src/components/visually-hidden.js
new
+16
@@ -0,0 +1,16 @@
1
+import styled from 'styled-components'
2
+
3
+/** Visually hide an element, but keep it accessible to screen readers. */
4
+const VisuallyHidden = styled.div`
5
+ position: absolute;
6
+ width: 1px;
7
+ height: 1px;
8
+ padding: 0;
9
+ margin: -1px;
10
+ overflow: hidden;
11
+ clip: rect(0, 0, 0, 0);
12
+ white-space: nowrap;
13
+ border-width: 0;
14
+`
15
+
16
+export default VisuallyHidden
src/constants.js
+1
-1
@@ -1,5 +1,5 @@
1
export const HEADER_HEIGHT = 66
2
3
-export const SKIP_NAV = 'skip-nav'
3
+export const SKIP_NAV = {id: 'skip-nav', as: 'main'}
4
5
export const NPM_RED = '#cb0000'
src/layout.js
+1
-4
@@ -4,7 +4,6 @@ import {Box} from '@primer/react'
4
import Slugger from 'github-slugger'
5
import Header from './components/header'
6
import Sidebar from './components/sidebar'
7
-import {SKIP_NAV} from './constants'
7
import useSiteMetdata from './hooks/use-site-metadata'
8
9
const SluggerContext = React.createContext(null)
@@ -48,9 +47,7 @@ const withLayout = Component => {
47
<Box sx={{display: ['none', null, null, 'block']}}>
48
<Sidebar />
49
</Box>
51
- <Box sx={{width: '100%'}} id={SKIP_NAV}>
52
- <Component {...props} />
53
- </Box>
50
+ <Component {...props} />
51
</Box>
52
</Box>
53
</LocationContext.Provider>
src/layout/default.js
+11
-13
@@ -3,7 +3,9 @@ import {Box, Heading} from '@primer/react'
3
import PageFooter from '../components/page-footer'
4
import * as TableOfContents from '../components/table-of-contents'
5
import VariantSelect from '../components/variant-select'
6
+import Breadcrumbs from '../components/breadcrumbs'
7
import withLayout from '../layout'
8
+import {SKIP_NAV} from '../constants'
9
10
const Layout = ({children, pageContext: {frontmatter}}) => {
11
const {title, description} = frontmatter
@@ -24,23 +26,19 @@ const Layout = ({children, pageContext: {frontmatter}}) => {
26
alignSelf: 'start',
27
}}
28
>
29
+ <Box css={{gridArea: 'table-of-contents'}}>
30
+ <TableOfContents.Desktop />
31
+ </Box>
32
<Box css={{gridArea: 'heading'}}>
28
- <Box
29
- sx={{
30
- borderStyle: 'solid',
31
- borderColor: 'border.default',
32
- borderWidth: 0,
33
- borderBottomWidth: 1,
34
- borderRadius: 0,
35
- pb: 2,
36
- }}
37
- >
38
- <Heading as="h1">{title}</Heading>
39
- {description}
33
+ <Box {...SKIP_NAV} sx={{mb: 4}}>
34
+ <Breadcrumbs />
35
+ <Heading as="h1" sx={{fontSize: 7}}>
36
+ {title}
37
+ </Heading>
38
+ {description ? <Box sx={{fontSize: 3, mb: 3}}>{description}</Box> : null}
39
</Box>
40
<VariantSelect />
41
</Box>
43
- <TableOfContents.Desktop />
42
<Box css={{gridArea: 'content'}}>
43
<TableOfContents.Mobile />
44
{children}
src/layout/hero.js
+2
-1
@@ -3,9 +3,10 @@ import {Box} from '@primer/react'
3
import Container from '../components/container'
4
import Hero from '../components/hero'
5
import withLayout from '../layout'
6
+import {SKIP_NAV} from '../constants'
7
8
const HeroLayout = ({children}) => (
8
- <Box sx={{width: '100%'}}>
9
+ <Box sx={{width: '100%'}} {...SKIP_NAV}>
10
<Hero />
11
<Container>{children}</Container>
12
</Box>
src/mdx/index.js
+1
-1
@@ -24,7 +24,7 @@ export const Pre = ({children}) => children
24
const SkipLinkBase = props => (
25
<Link
26
{...props}
27
- href={`#${SKIP_NAV}`}
27
+ href={`#${SKIP_NAV.id}`}
28
sx={{
29
p: 3,
30
color: 'fg.onEmphasis',
src/mdx/nav-hierarchy.js
+10
-14
@@ -1,10 +1,10 @@
1
import React from 'react'
2
import {Box, Link} from '@primer/react'
3
import {Link as GatsbyLink} from 'gatsby'
4
-import getNav from '../util/get-nav'
4
+import * as getNav from '../util/get-nav'
5
import {useLocation} from '../layout'
6
7
-const HierarchyItem = ({item, currentDepth, ...props}) => {
7
+const HierarchyItem = ({item, depth, ...props}) => {
8
const hierarchy = getNav.getHierarchy(item, props)
9
10
return (
@@ -13,38 +13,34 @@ const HierarchyItem = ({item, currentDepth, ...props}) => {
13
{item.title}
14
</Link>
15
{item.description ? <Box style={{fontSize: '0.85em', marginBottom: '0.5em'}}>{item.description}</Box> : null}
16
- {hierarchy ? <Hierarchy items={hierarchy} currentDepth={currentDepth + 1} {...props} /> : null}
16
+ {hierarchy ? <Hierarchy items={hierarchy} depth={depth + 1} {...props} /> : null}
17
</Box>
18
)
19
}
20
21
-const Hierarchy = ({items, currentDepth = 1, ...props}) => {
22
- if (props.depth && currentDepth > props.depth) {
21
+const Hierarchy = ({items, ...props}) => {
22
+ if (props.maxDepth && props.depth > props.maxDepth) {
23
return null
24
}
25
26
return (
27
<Box as="ul">
28
{items.map(item => (
29
- <HierarchyItem key={item.url} item={item} currentDepth={currentDepth} {...props} />
29
+ <HierarchyItem key={item.url} item={item} {...props} />
30
))}
31
</Box>
32
)
33
}
34
35
-function NavHierarchy(props) {
35
+function NavHierarchy({root, depth, ...props}) {
36
const location = useLocation()
37
const path = getNav.getLocation(location.pathname)
38
- const root = (props.root ? props.root : path).replace(/\/+$/g, '')
38
+ const navRoot = (root || path).replace(/\/+$/g, '')
39
40
- const rootItem = getNav.getItem(root)
40
+ const rootItem = getNav.getItem(navRoot)
41
const hierarchy = getNav.getHierarchy(rootItem, props)
42
43
- if (!hierarchy) {
44
- throw new Error(`could not find entry for ${root}`)
45
- }
46
-
47
- return <Hierarchy items={hierarchy} {...props} />
43
+ return <Hierarchy items={hierarchy} maxDepth={depth} depth={1} {...props} />
44
}
45
46
export default NavHierarchy
src/util/get-nav.js
+190
-184
@@ -1,253 +1,259 @@
1
import {withPrefix} from 'gatsby'
2
import navItems from '../../content/nav.yml'
3
4
-const NavHierarchy = {
5
- getLocation(path) {
6
- const pathPrefix = withPrefix('/')
7
-
8
- if (!pathPrefix || pathPrefix === '/') {
9
- return path
10
- }
11
-
12
- const match = new RegExp(`^${pathPrefix}`)
13
- return path.replace(match, '/')
14
- },
4
+export const getItemBreadcrumbs = path => {
5
+ const hierarchy = getItemHierarchy(path)
6
+ return hierarchy
7
+ ? hierarchy.map(item => {
8
+ item.title = item.shortName || item.title
9
+ return item
10
+ })
11
+ : null
12
+}
13
16
- getPath(path) {
17
- while (path && path.endsWith('/')) {
18
- path = path.substring(0, path.length - 1)
19
- }
14
+export const getLocation = path => {
15
+ const pathPrefix = withPrefix('/')
16
17
+ if (!pathPrefix || pathPrefix === '/') {
18
return path
22
- },
19
+ }
20
24
- getVariantRoot(path) {
25
- path = this.getPath(path)
26
-
27
- return this.findItem(item => {
28
- if (item.variants && path.startsWith(`${item.url}/`)) {
29
- return item.url
30
- }
21
+ const match = new RegExp(`^${pathPrefix}`)
22
+ return path.replace(match, '/')
23
+}
24
32
- return null
33
- })
34
- },
25
+export const getPath = path => {
26
+ while (path && path.endsWith('/')) {
27
+ path = path.substring(0, path.length - 1)
28
+ }
29
36
- findItem(fn, items = navItems) {
37
- for (let i = 0; i < items.length; i++) {
38
- const item = items[i]
39
- let result = fn(item)
30
+ return path
31
+}
32
41
- if (!result && item.children) {
42
- result = this.findItem(fn, item.children)
43
- }
33
+export const getVariantRoot = path => {
34
+ path = getPath(path)
35
45
- if (result) {
46
- return result
47
- }
36
+ return findItem(item => {
37
+ if (item.variants && path.startsWith(`${item.url}/`)) {
38
+ return item.url
39
}
40
41
return null
51
- },
42
+ })
43
+}
44
+
45
+export const findItem = (fn, items = navItems) => {
46
+ for (let i = 0; i < items.length; i++) {
47
+ const item = items[i]
48
+ let result = fn(item)
49
53
- getItemHierarchy(path, items = navItems) {
54
- if (!path) {
55
- return null
50
+ if (!result && item.children) {
51
+ result = findItem(fn, item.children)
52
}
53
58
- for (let i = 0; i < items.length; i++) {
59
- const item = items[i]
54
+ if (result) {
55
+ return result
56
+ }
57
+ }
58
61
- if (this.getPath(item.url) === this.getPath(path)) {
62
- return [item]
63
- }
59
+ return null
60
+}
61
65
- const children = item.variants ? item.variants : item.children
62
+export const getItemHierarchy = (path, items = navItems) => {
63
+ if (!path) {
64
+ return null
65
+ }
66
67
- if (children) {
68
- const hierarchy = this.getItemHierarchy(path, children)
67
+ for (let i = 0; i < items.length; i++) {
68
+ const item = items[i]
69
70
- if (hierarchy) {
71
- return [item, ...hierarchy]
72
- }
73
- }
70
+ if (getPath(item.url) === getPath(path)) {
71
+ return [item]
72
}
73
76
- return null
77
- },
78
-
79
- getItem(path, items = navItems) {
80
- if (!path) {
81
- return {url: '/', children: items}
82
- }
74
+ const children = item.variants ? item.variants : item.children
75
84
- for (let i = 0; i < items.length; i++) {
85
- const item = items[i]
76
+ if (children) {
77
+ const hierarchy = getItemHierarchy(path, children)
78
87
- if (this.getPath(item.url) === this.getPath(path)) {
88
- return item
79
+ if (hierarchy) {
80
+ return [item, ...hierarchy]
81
}
82
+ }
83
+ }
84
91
- const children = item.variants ? item.variants : item.children
85
+ return null
86
+}
87
93
- if (children) {
94
- const child = this.getItem(path, children)
88
+export const getItem = (path, items = navItems) => {
89
+ if (!path) {
90
+ return {url: '/', children: items}
91
+ }
92
96
- if (child) {
97
- return child
98
- }
99
- }
93
+ for (let i = 0; i < items.length; i++) {
94
+ const item = items[i]
95
+
96
+ if (getPath(item.url) === getPath(path)) {
97
+ return item
98
}
99
102
- return null
103
- },
100
+ const children = item.variants ? item.variants : item.children
101
105
- isPathForItem(path, item) {
106
- return this.getPath(item.url) === this.getPath(path)
107
- },
102
+ if (children) {
103
+ const child = getItem(path, children)
104
109
- isChildItem(path, items = navItems) {
110
- if (!path) {
111
- return false
105
+ if (child) {
106
+ return child
107
+ }
108
}
109
+ }
110
114
- return this.findItem(item => (this.isPathForItem(path, item) ? item : null), items) != null
115
- },
111
+ return null
112
+}
113
117
- getHierarchy(root, props = {}) {
118
- let children
114
+export const isPathForItem = (path, item) => {
115
+ return getPath(item.url) === getPath(path)
116
+}
117
120
- if (!root) {
121
- children = navItems
122
- } else if (root.variants && props.hideVariants === true) {
123
- const variant = this.getCurrentOrDefaultVariant(root, props.path)
124
- children = variant.children
125
- } else if (root.variants) {
126
- children = root.variants
127
- } else {
128
- children = root.children
129
- }
118
+export const isChildItem = (path, items = navItems) => {
119
+ if (!path) {
120
+ return false
121
+ }
122
131
- if (children && props.hideVariants === true) {
132
- children = this.hideVariantsForItems(children, props)
133
- }
123
+ return findItem(item => (isPathForItem(path, item) ? item : null), items) != null
124
+}
125
135
- return children
136
- },
126
+export const getHierarchy = (root, props = {}) => {
127
+ let children
128
+
129
+ if (!root) {
130
+ children = navItems
131
+ } else if (root.variants && props.hideVariants === true) {
132
+ const variant = getCurrentOrDefaultVariant(root, props.path)
133
+ children = variant.children
134
+ } else if (root.variants) {
135
+ children = root.variants
136
+ } else {
137
+ children = root.children
138
+ }
139
+
140
+ if (children && props.hideVariants === true) {
141
+ children = hideVariantsForItems(children, props.path)
142
+ }
143
+
144
+ return children
145
+}
146
138
- hideVariantsForItems(items, props) {
139
- if (!items) {
140
- return null
141
- }
147
+export const hideVariantsForItems = (items, path) => {
148
+ if (!items) {
149
+ return null
150
+ }
151
143
- const updated = []
152
+ const updated = []
153
145
- for (const item of items) {
146
- if (item.variants) {
147
- const {url} = this.getCurrentOrDefaultVariant(item, props.path)
148
- updated.push({
149
- ...item,
150
- url,
151
- })
152
- } else {
153
- updated.push(item)
154
- }
154
+ for (const item of items) {
155
+ if (item.variants) {
156
+ const {url} = getCurrentOrDefaultVariant(item, path)
157
+ updated.push({
158
+ ...item,
159
+ url,
160
+ })
161
+ } else {
162
+ updated.push(item)
163
}
164
+ }
165
157
- return updated
158
- },
166
+ return updated
167
+}
168
160
- getCurrentOrDefaultVariant(root, path) {
161
- let variant = path ? this.getCurrentVariant(root, path) : null
169
+export const getCurrentOrDefaultVariant = (root, path) => {
170
+ let variant = path ? getCurrentVariant(root, path) : null
171
163
- if (!variant) {
164
- variant = this.getDefaultVariant(root)
165
- }
172
+ if (!variant) {
173
+ variant = getDefaultVariant(root)
174
+ }
175
167
- return variant
168
- },
176
+ return variant
177
+}
178
170
- getCurrentVariant(root, path) {
171
- for (const v of root.variants) {
172
- if (this.isActiveItem(path, v)) {
173
- return v
174
- }
179
+export const getCurrentVariant = (root, path) => {
180
+ for (const v of root.variants) {
181
+ if (isActiveItem(path, v)) {
182
+ return v
183
}
184
+ }
185
177
- return null
178
- },
186
+ return null
187
+}
188
180
- getDefaultVariant(root) {
181
- for (const v of root.variants) {
182
- if (v.default) {
183
- return v
184
- }
189
+export const getDefaultVariant = root => {
190
+ for (const v of root.variants) {
191
+ if (v.default) {
192
+ return v
193
}
194
+ }
195
187
- return root.variants[0]
188
- },
189
-
190
- getVariantAndPage(root, path) {
191
- if (!root || !path.startsWith(`${root}/`)) {
192
- return null
193
- }
196
+ return root.variants[0]
197
+}
198
195
- path = path.substring(root.length + 1)
199
+export const getVariantAndPage = (root, path) => {
200
+ if (!root || !path.startsWith(`${root}/`)) {
201
+ return null
202
+ }
203
197
- const match = /^([^/]+)(?:\/(.*))?/.exec(path)
204
+ path = path.substring(root.length + 1)
205
199
- if (!match) {
200
- return null
201
- }
206
+ const match = /^([^/]+)(?:\/(.*))?/.exec(path)
207
203
- return {variant: match[1], page: match[2]}
204
- },
208
+ if (!match) {
209
+ return null
210
+ }
211
206
- getVariantsForPage(root, page) {
207
- const pages = []
208
- const rootItem = this.findItem(item => (this.getPath(item.url) === this.getPath(root) ? item : null))
212
+ return {variant: match[1], page: match[2]}
213
+}
214
210
- if (rootItem && rootItem.variants) {
211
- for (const variant of rootItem.variants) {
212
- if (!variant.children) {
213
- continue
214
- }
215
+export const getVariantsForPage = (root, page) => {
216
+ const pages = []
217
+ const rootItem = findItem(item => (getPath(item.url) === getPath(root) ? item : null))
218
216
- const vp = this.getVariantAndPage(root, variant.url)
217
- let variantPage
219
+ if (rootItem && rootItem.variants) {
220
+ for (const variant of rootItem.variants) {
221
+ if (!variant.children) {
222
+ continue
223
+ }
224
219
- if (vp.page === page) {
220
- variantPage = variant
221
- } else {
222
- variantPage = this.findItem(item => {
223
- const itemVp = this.getVariantAndPage(root, item.url)
224
- return itemVp && itemVp.page === page ? item : null
225
- }, variant.children)
226
- }
225
+ const vp = getVariantAndPage(root, variant.url)
226
+ let variantPage
227
228
- if (!variantPage) {
229
- continue
230
- }
228
+ if (vp.page === page) {
229
+ variantPage = variant
230
+ } else {
231
+ variantPage = findItem(item => {
232
+ const itemVp = getVariantAndPage(root, item.url)
233
+ return itemVp && itemVp.page === page ? item : null
234
+ }, variant.children)
235
+ }
236
232
- pages.push({variant, page: variantPage})
237
+ if (!variantPage) {
238
+ continue
239
}
234
- }
240
236
- return pages
237
- },
241
+ pages.push({variant, page: variantPage})
242
+ }
243
+ }
244
239
- isActiveItem(currentPath, linkItem) {
240
- return (
241
- this.isPathForItem(currentPath, linkItem) ||
242
- (linkItem.children && this.isChildItem(currentPath, linkItem.children)) ||
243
- (linkItem.variants && this.isChildItem(currentPath, linkItem.variants))
244
- )
245
- },
245
+ return pages
246
+}
247
247
- isActiveUrl(currentPath, linkPath) {
248
- const linkItem = this.getItem(linkPath)
249
- return linkItem ? this.isActiveItem(currentPath, linkItem) : false
250
- },
248
+export const isActiveItem = (currentPath, linkItem) => {
249
+ return (
250
+ isPathForItem(currentPath, linkItem) ||
251
+ (linkItem.children && isChildItem(currentPath, linkItem.children)) ||
252
+ (linkItem.variants && isChildItem(currentPath, linkItem.variants))
253
+ )
254
}
255
253
-export default NavHierarchy
256
+export const isActiveUrl = (currentPath, linkPath) => {
257
+ const linkItem = getItem(linkPath)
258
+ return linkItem ? isActiveItem(currentPath, linkItem) : false
259
+}