@primer/doctocat-nextjs 0.1.0 → 0.2.0-rc.10bf384
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/components/content/caption/Caption.tsx +3 -3
- package/components/content/dos-and-donts/DosAndDonts.module.css +60 -0
- package/components/content/dos-and-donts/DosAndDonts.tsx +17 -44
- package/components/context/color-modes/ColorModeProvider.tsx +2 -2
- package/components/index.ts +9 -0
- package/components/layout/article/Article.module.css +8 -0
- package/components/layout/article/Article.tsx +24 -0
- package/components/layout/footer/Footer.module.css +0 -0
- package/components/layout/footer/Footer.tsx +41 -0
- package/components/layout/header/Header.tsx +10 -15
- package/components/layout/heading-link/HeadingLink.module.css +16 -0
- package/components/layout/heading-link/HeadingLinks.tsx +31 -0
- package/components/layout/nav-drawer/Drawer.tsx +7 -2
- package/components/layout/nav-drawer/NavDrawer.module.css +51 -0
- package/components/layout/nav-drawer/NavDrawer.tsx +16 -36
- package/components/layout/nav-drawer/useNavDrawerState.ts +1 -1
- package/components/layout/related-content-links/RelatedContentLinks.tsx +5 -4
- package/components/layout/related-content-links/getRelatedPages.tsx +57 -0
- package/components/layout/root-layout/Theme.tsx +144 -262
- package/components/layout/root-layout/index.tsx +11 -4
- package/components/layout/sidebar/Sidebar.tsx +72 -91
- package/components/layout/table-of-contents/TableOfContents.module.css +5 -0
- package/components/layout/table-of-contents/TableOfContents.tsx +5 -3
- package/components/layout/underline-nav/UnderlineNav.tsx +13 -4
- package/components/library.ts +3 -0
- package/css/prose.module.css +8 -0
- package/doctocat.config.js +4 -10
- package/helpers/hasChildren.ts +3 -3
- package/index.tsx +2 -6
- package/package.json +10 -9
- package/tsconfig.json +5 -3
- package/types.ts +20 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @primer/doctocat-nextjs
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#20](https://github.com/primer/doctocat-nextjs/pull/20) [`be8bc6a`](https://github.com/primer/doctocat-nextjs/commit/be8bc6af733ba40bdd4393b876b2653017d7e846) Thanks [@rezrah](https://github.com/rezrah)! - Dropped support for Next.js Pages router in favor of App Router + Nextra v4
|
|
8
|
+
|
|
9
|
+
- [Read the migration guide from `v0.1.0` to `v0.2.0`](https://github.com/primer/doctocat-nextjs/blob/main/migration-guides/v0.2.0-app-router.md)
|
|
10
|
+
|
|
3
11
|
## 0.1.0
|
|
4
12
|
|
|
5
13
|
### Minor Changes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import React from 'react'
|
|
1
|
+
import React, {PropsWithChildren} from 'react'
|
|
2
2
|
import {Text} from '@primer/react'
|
|
3
3
|
|
|
4
|
-
export function Caption(props) {
|
|
5
|
-
return <Text as="p" {...props} sx={{mt: 2, mb: 3, fontSize: 1, color: '
|
|
4
|
+
export function Caption(props: PropsWithChildren) {
|
|
5
|
+
return <Text as="p" {...props} sx={{mt: 2, mb: 3, fontSize: 1, color: 'var(--brand-color-text-default)'}} />
|
|
6
6
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
.container {
|
|
2
|
+
display: grid;
|
|
3
|
+
gap: 1rem;
|
|
4
|
+
margin: 1.5rem 0;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
@media (min-width: 768px) {
|
|
8
|
+
.container:not(.stacked) {
|
|
9
|
+
grid-template-columns: 1fr 1fr;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.doDontBase {
|
|
14
|
+
display: flex;
|
|
15
|
+
flex-direction: column;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.doLabel .header {
|
|
19
|
+
background-color: var(--brand-color-success-fg);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.dontLabel .header {
|
|
23
|
+
background-color: var(--brand-color-error-fg);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.header {
|
|
27
|
+
display: flex;
|
|
28
|
+
align-self: start;
|
|
29
|
+
flex-direction: row;
|
|
30
|
+
align-items: center;
|
|
31
|
+
margin-bottom: 0.5rem;
|
|
32
|
+
border-radius: 0.25rem;
|
|
33
|
+
padding: 0 0.5rem;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.headerText {
|
|
37
|
+
font-weight: bold;
|
|
38
|
+
font-size: 0.875rem;
|
|
39
|
+
color: var(--base-color-scale-white-0);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.content {
|
|
43
|
+
display: flex;
|
|
44
|
+
flex-direction: column;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.content :last-child {
|
|
48
|
+
margin-bottom: 0;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.content img {
|
|
52
|
+
max-width: 100%;
|
|
53
|
+
margin-block-end: 0 !important;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.indentedContent {
|
|
57
|
+
margin: 0;
|
|
58
|
+
border-left: 4px solid;
|
|
59
|
+
padding-left: 1rem;
|
|
60
|
+
}
|
|
@@ -1,25 +1,22 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
|
-
import {Box
|
|
2
|
+
import {Box} from '@primer/react'
|
|
3
|
+
import clsx from 'clsx'
|
|
4
|
+
import styles from './DosAndDonts.module.css'
|
|
3
5
|
|
|
4
6
|
type DoDontContainerProps = {
|
|
5
7
|
stacked?: boolean
|
|
6
8
|
}
|
|
7
9
|
|
|
8
10
|
export function DoDontContainer({stacked = false, children}: React.PropsWithChildren<DoDontContainerProps>) {
|
|
9
|
-
return
|
|
10
|
-
<Box sx={{display: 'grid', gridTemplateColumns: ['1fr', null, stacked ? '1fr' : '1fr 1fr'], gridGap: 4, my: 6}}>
|
|
11
|
-
{children}
|
|
12
|
-
</Box>
|
|
13
|
-
)
|
|
11
|
+
return <div className={`${styles.container} ${stacked ? styles.stacked : ''}`}>{children}</div>
|
|
14
12
|
}
|
|
15
|
-
|
|
16
13
|
type DoDontProps = {
|
|
17
14
|
indented?: boolean
|
|
18
15
|
}
|
|
19
16
|
|
|
20
17
|
export function Do({children, ...rest}: React.PropsWithChildren<DoDontProps>) {
|
|
21
18
|
return (
|
|
22
|
-
<DoDontBase title="Do"
|
|
19
|
+
<DoDontBase title="Do" className={styles.doLabel} {...rest}>
|
|
23
20
|
{children}
|
|
24
21
|
</DoDontBase>
|
|
25
22
|
)
|
|
@@ -27,7 +24,7 @@ export function Do({children, ...rest}: React.PropsWithChildren<DoDontProps>) {
|
|
|
27
24
|
|
|
28
25
|
export function Dont({children, ...rest}: React.PropsWithChildren<DoDontProps>) {
|
|
29
26
|
return (
|
|
30
|
-
<DoDontBase title="Don’t"
|
|
27
|
+
<DoDontBase title="Don’t" className={styles.dontLabel} {...rest}>
|
|
31
28
|
{children}
|
|
32
29
|
</DoDontBase>
|
|
33
30
|
)
|
|
@@ -35,54 +32,30 @@ export function Dont({children, ...rest}: React.PropsWithChildren<DoDontProps>)
|
|
|
35
32
|
|
|
36
33
|
type DoDontBaseProps = {
|
|
37
34
|
title: string
|
|
38
|
-
|
|
39
|
-
borderColor: string
|
|
35
|
+
className?: string
|
|
40
36
|
indented?: boolean
|
|
41
37
|
}
|
|
42
38
|
|
|
43
|
-
export function DoDontBase({children, title,
|
|
39
|
+
export function DoDontBase({children, title, indented, className, ...rest}: React.PropsWithChildren<DoDontBaseProps>) {
|
|
44
40
|
return (
|
|
45
|
-
<
|
|
41
|
+
<div className={clsx(`exclude-from-prose`, styles.doDontBase, className)} {...rest}>
|
|
46
42
|
<Box
|
|
43
|
+
className={styles.header}
|
|
47
44
|
sx={{
|
|
48
|
-
|
|
49
|
-
alignSelf: 'start',
|
|
50
|
-
flexDirection: 'row',
|
|
51
|
-
alignItems: 'center',
|
|
52
|
-
mb: '2',
|
|
53
|
-
backgroundColor: bg,
|
|
54
|
-
borderRadius: '2',
|
|
55
|
-
color: 'fg.onEmphasis',
|
|
56
|
-
paddingX: '2',
|
|
45
|
+
color: 'var(--fgColor-onEmphasis, var(--color-fg-on-emphasis))',
|
|
57
46
|
}}
|
|
58
47
|
>
|
|
59
|
-
<
|
|
48
|
+
<span className={styles.headerText}>{title}</span>
|
|
60
49
|
</Box>
|
|
61
|
-
<
|
|
62
|
-
sx={{
|
|
63
|
-
'& *:last-child': {mb: 0},
|
|
64
|
-
' img': {maxWidth: '100%', marginBlockEnd: 0},
|
|
65
|
-
display: 'flex',
|
|
66
|
-
flexDirection: 'column',
|
|
67
|
-
}}
|
|
68
|
-
>
|
|
50
|
+
<div className={styles.content}>
|
|
69
51
|
{indented ? (
|
|
70
|
-
<
|
|
71
|
-
as="blockquote"
|
|
72
|
-
sx={{
|
|
73
|
-
margin: '0',
|
|
74
|
-
borderLeftWidth: '4px',
|
|
75
|
-
borderLeftStyle: 'solid',
|
|
76
|
-
borderLeftColor: borderColor,
|
|
77
|
-
paddingLeft: '3',
|
|
78
|
-
}}
|
|
79
|
-
>
|
|
52
|
+
<blockquote className={styles.indentedContent} style={{borderLeftColor: 'var(--brand-color-border-default)'}}>
|
|
80
53
|
{children}
|
|
81
|
-
</
|
|
54
|
+
</blockquote>
|
|
82
55
|
) : (
|
|
83
56
|
children
|
|
84
57
|
)}
|
|
85
|
-
</
|
|
86
|
-
</
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
87
60
|
)
|
|
88
61
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import React, {useState, useEffect, useCallback} from 'react'
|
|
1
|
+
import React, {useState, useEffect, useCallback, PropsWithChildren} from 'react'
|
|
2
2
|
import {ColorMode, ColorModeContext} from './context'
|
|
3
3
|
|
|
4
|
-
const ColorModeProvider = ({children}) => {
|
|
4
|
+
const ColorModeProvider = ({children}: PropsWithChildren) => {
|
|
5
5
|
const [colorMode, setColorMode] = useState<ColorMode>('light')
|
|
6
6
|
|
|
7
7
|
useEffect(() => {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Documentation components
|
|
3
|
+
*/
|
|
4
|
+
export {DoDontContainer, Do, Dont} from './content/dos-and-donts/DosAndDonts'
|
|
5
|
+
export {Caption} from './content/caption/Caption'
|
|
6
|
+
export * from './library'
|
|
7
|
+
export {TableOfContents} from './layout/table-of-contents/TableOfContents'
|
|
8
|
+
export {Article} from './layout/article/Article'
|
|
9
|
+
export {HeadingLink} from './layout/heading-link/HeadingLinks'
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import React, {PropsWithChildren} from 'react'
|
|
2
|
+
import {TableOfContents} from '../table-of-contents/TableOfContents'
|
|
3
|
+
import styles from './Article.module.css'
|
|
4
|
+
import bodyStyles from '../../../css/prose.module.css'
|
|
5
|
+
import {Heading as HeadingType} from 'nextra'
|
|
6
|
+
|
|
7
|
+
import clsx from 'clsx'
|
|
8
|
+
|
|
9
|
+
type Props = {
|
|
10
|
+
toc: HeadingType[]
|
|
11
|
+
metadata: {
|
|
12
|
+
layout?: string
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function Article({children, toc, metadata}: PropsWithChildren<Props>) {
|
|
17
|
+
const hasToc = toc.length > 0
|
|
18
|
+
return (
|
|
19
|
+
<div className={clsx(styles.Article, hasToc && styles['Article--withToc'])}>
|
|
20
|
+
<div className={clsx(metadata.layout !== 'custom' && bodyStyles.Prose)}>{children}</div>
|
|
21
|
+
{hasToc && <TableOfContents headings={toc} />}
|
|
22
|
+
</div>
|
|
23
|
+
)
|
|
24
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import {PencilIcon} from '@primer/octicons-react'
|
|
3
|
+
import {Box, InlineLink, Stack, Text} from '@primer/react-brand'
|
|
4
|
+
|
|
5
|
+
type FooterProps = {
|
|
6
|
+
filePath: string
|
|
7
|
+
repoURL: string
|
|
8
|
+
repoSrcPath: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function Footer({repoURL, repoSrcPath, filePath}: FooterProps) {
|
|
12
|
+
return (
|
|
13
|
+
<footer>
|
|
14
|
+
<Box marginBlockStart={64}>
|
|
15
|
+
<Stack direction="vertical" padding="none" gap={16}>
|
|
16
|
+
<Stack direction="horizontal" padding="none" alignItems="center" gap={8}>
|
|
17
|
+
<PencilIcon size={16} fill="var(--brand-InlineLink-color-rest)" />
|
|
18
|
+
|
|
19
|
+
<InlineLink
|
|
20
|
+
target="_blank"
|
|
21
|
+
href={`${repoURL}/blob/main/${repoSrcPath ? `${repoSrcPath}/` : ''}${filePath}`}
|
|
22
|
+
>
|
|
23
|
+
Edit this page
|
|
24
|
+
</InlineLink>
|
|
25
|
+
</Stack>
|
|
26
|
+
<Box
|
|
27
|
+
marginBlockStart={8}
|
|
28
|
+
paddingBlockStart={24}
|
|
29
|
+
borderStyle="solid"
|
|
30
|
+
borderBlockStartWidth="thin"
|
|
31
|
+
borderColor="default"
|
|
32
|
+
>
|
|
33
|
+
<Text as="p" variant="muted" size="100">
|
|
34
|
+
© {new Date().getFullYear()} GitHub, Inc. All rights reserved.
|
|
35
|
+
</Text>
|
|
36
|
+
</Box>
|
|
37
|
+
</Stack>
|
|
38
|
+
</Box>
|
|
39
|
+
</footer>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
+
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'
|
|
1
2
|
import {MarkGithubIcon, MoonIcon, SearchIcon, SunIcon, ThreeBarsIcon, XIcon} from '@primer/octicons-react'
|
|
2
3
|
import {Box, FormControl, IconButton, TextInput} from '@primer/react'
|
|
3
4
|
import {Heading, Stack, Text} from '@primer/react-brand'
|
|
4
5
|
import {clsx} from 'clsx'
|
|
5
6
|
import {MdxFile, Folder, PageMapItem} from 'nextra'
|
|
6
|
-
import type {PageItem} from 'nextra/normalize-pages'
|
|
7
|
-
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'
|
|
8
7
|
import {debounce} from 'lodash'
|
|
9
8
|
|
|
10
9
|
import Link from 'next/link'
|
|
@@ -17,8 +16,6 @@ import {DocsItem} from '../../../types'
|
|
|
17
16
|
|
|
18
17
|
type HeaderProps = {
|
|
19
18
|
pageMap: PageMapItem[]
|
|
20
|
-
docsDirectories: PageItem[]
|
|
21
|
-
menuItems: PageItem[]
|
|
22
19
|
siteTitle: string
|
|
23
20
|
}
|
|
24
21
|
|
|
@@ -28,7 +25,7 @@ type SearchResults = {
|
|
|
28
25
|
url: string
|
|
29
26
|
}
|
|
30
27
|
|
|
31
|
-
export function Header({pageMap,
|
|
28
|
+
export function Header({pageMap, siteTitle}: HeaderProps) {
|
|
32
29
|
const {colorMode, setColorMode} = useColorMode()
|
|
33
30
|
const inputRef = useRef<HTMLInputElement | null>(null)
|
|
34
31
|
const searchResultsRef = useRef<HTMLElement | null>(null)
|
|
@@ -76,7 +73,7 @@ export function Header({pageMap, docsDirectories, siteTitle}: HeaderProps) {
|
|
|
76
73
|
document.documentElement.setAttribute('data-color-mode', colorMode)
|
|
77
74
|
}, [colorMode])
|
|
78
75
|
|
|
79
|
-
const setSearchResultsDebounced = debounce(data => setSearchResults(data), 1000)
|
|
76
|
+
const setSearchResultsDebounced = debounce((data: SearchResults[] | undefined) => setSearchResults(data), 1000)
|
|
80
77
|
|
|
81
78
|
const searchData = useMemo(
|
|
82
79
|
() =>
|
|
@@ -91,7 +88,9 @@ export function Header({pageMap, docsDirectories, siteTitle}: HeaderProps) {
|
|
|
91
88
|
})
|
|
92
89
|
.flat()
|
|
93
90
|
.filter(Boolean)
|
|
94
|
-
.map(
|
|
91
|
+
.map(item => {
|
|
92
|
+
const {frontMatter, route} = item as MdxFile
|
|
93
|
+
|
|
95
94
|
if (!frontMatter) return null
|
|
96
95
|
const result = {
|
|
97
96
|
title: frontMatter.title ? frontMatter.title : '',
|
|
@@ -148,7 +147,7 @@ export function Header({pageMap, docsDirectories, siteTitle}: HeaderProps) {
|
|
|
148
147
|
}
|
|
149
148
|
}
|
|
150
149
|
|
|
151
|
-
const handleSubmit = e => {
|
|
150
|
+
const handleSubmit = (e: React.FormEvent) => {
|
|
152
151
|
e.preventDefault()
|
|
153
152
|
if (!inputRef.current) return
|
|
154
153
|
if (!inputRef.current.value) {
|
|
@@ -176,7 +175,7 @@ export function Header({pageMap, docsDirectories, siteTitle}: HeaderProps) {
|
|
|
176
175
|
{siteTitle}
|
|
177
176
|
</Text>
|
|
178
177
|
</Link>
|
|
179
|
-
<
|
|
178
|
+
<div className={clsx(styles.Header__searchArea, isSearchOpen && styles['Header__searchArea--open'])}>
|
|
180
179
|
<FormControl>
|
|
181
180
|
<FormControl.Label visuallyHidden>Search</FormControl.Label>
|
|
182
181
|
<TextInput
|
|
@@ -294,7 +293,7 @@ export function Header({pageMap, docsDirectories, siteTitle}: HeaderProps) {
|
|
|
294
293
|
/>
|
|
295
294
|
</Stack>
|
|
296
295
|
</div>
|
|
297
|
-
</
|
|
296
|
+
</div>
|
|
298
297
|
<div>
|
|
299
298
|
<Stack direction="horizontal" padding="none" gap={4}>
|
|
300
299
|
<IconButton
|
|
@@ -318,11 +317,7 @@ export function Header({pageMap, docsDirectories, siteTitle}: HeaderProps) {
|
|
|
318
317
|
aria-expanded={isNavDrawerOpen}
|
|
319
318
|
onClick={() => setIsNavDrawerOpen(true)}
|
|
320
319
|
/>
|
|
321
|
-
<NavDrawer
|
|
322
|
-
isOpen={isNavDrawerOpen}
|
|
323
|
-
onDismiss={() => setIsNavDrawerOpen(false)}
|
|
324
|
-
navItems={docsDirectories}
|
|
325
|
-
/>
|
|
320
|
+
<NavDrawer isOpen={isNavDrawerOpen} onDismiss={() => setIsNavDrawerOpen(false)} navItems={pageMap} />
|
|
326
321
|
</Box>
|
|
327
322
|
</Stack>
|
|
328
323
|
</div>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
.Heading__anchor {
|
|
2
|
+
color: var(--brand-color-text-default) !important;
|
|
3
|
+
text-decoration: none !important;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
.Heading__anchorIcon {
|
|
7
|
+
position: relative;
|
|
8
|
+
visibility: hidden;
|
|
9
|
+
margin-left: var(--base-size-4);
|
|
10
|
+
vertical-align: middle !important;
|
|
11
|
+
top: -1px;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.Heading__anchor:hover .Heading__anchorIcon {
|
|
15
|
+
visibility: visible;
|
|
16
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import React from 'react'
|
|
3
|
+
import type {ComponentProps, ReactElement} from 'react'
|
|
4
|
+
import {LinkIcon} from '@primer/octicons-react'
|
|
5
|
+
import styles from './HeadingLink.module.css'
|
|
6
|
+
import NextLink from 'next/link'
|
|
7
|
+
import {Heading, type HeadingProps} from '@primer/react-brand'
|
|
8
|
+
|
|
9
|
+
const headingSizeMap: Record<string, HeadingProps['size']> = {
|
|
10
|
+
h1: '4',
|
|
11
|
+
h2: '5',
|
|
12
|
+
h3: '6',
|
|
13
|
+
h4: 'subhead-large',
|
|
14
|
+
h5: 'subhead-medium',
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function HeadingLink({
|
|
18
|
+
tag,
|
|
19
|
+
children,
|
|
20
|
+
...props
|
|
21
|
+
}: ComponentProps<'h2'> & {
|
|
22
|
+
tag: `h${2 | 3 | 4 | 5 | 6}`
|
|
23
|
+
}): ReactElement {
|
|
24
|
+
return (
|
|
25
|
+
<Heading as={tag} size={headingSizeMap[tag]} {...props}>
|
|
26
|
+
<NextLink href={`#${props.id}`} className={styles.Heading__anchor}>
|
|
27
|
+
{children} <LinkIcon className={styles.Heading__anchorIcon} size={14} />
|
|
28
|
+
</NextLink>
|
|
29
|
+
</Heading>
|
|
30
|
+
)
|
|
31
|
+
}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
import React from 'react'
|
|
1
|
+
import React, {PropsWithChildren} from 'react'
|
|
2
2
|
import {Box} from '@primer/react'
|
|
3
3
|
import {AnimatePresence, motion} from 'framer-motion'
|
|
4
4
|
import {FocusOn} from 'react-focus-on'
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
type Drawer = {
|
|
7
|
+
isOpen: boolean
|
|
8
|
+
onDismiss: () => void
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function Drawer({isOpen, onDismiss, children}: PropsWithChildren<Drawer>) {
|
|
7
12
|
return (
|
|
8
13
|
<AnimatePresence>
|
|
9
14
|
{isOpen ? (
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
.scrollContainer {
|
|
2
|
+
overflow: auto;
|
|
3
|
+
-webkit-overflow-scrolling: touch;
|
|
4
|
+
display: flex;
|
|
5
|
+
flex-direction: column;
|
|
6
|
+
height: 100%;
|
|
7
|
+
background-color: var(--brand-color-canvas-default);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.header {
|
|
11
|
+
display: flex;
|
|
12
|
+
flex-direction: column;
|
|
13
|
+
flex: 0 0 auto;
|
|
14
|
+
color: var(--brand-color-text-default);
|
|
15
|
+
background-color: var(--brand-color-canvas-default);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.headerBorder {
|
|
19
|
+
border-width: 0;
|
|
20
|
+
border-radius: 0;
|
|
21
|
+
border-bottom-width: 1px;
|
|
22
|
+
border-color: var(--brand-color-border-muted);
|
|
23
|
+
border-style: solid;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.headerContent {
|
|
27
|
+
padding: 20px;
|
|
28
|
+
padding-left: 16px;
|
|
29
|
+
padding-right: 12px;
|
|
30
|
+
display: flex;
|
|
31
|
+
align-items: center;
|
|
32
|
+
justify-content: space-between;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.headerLink {
|
|
36
|
+
font-weight: bold;
|
|
37
|
+
color: inherit;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.navContainer {
|
|
41
|
+
display: flex;
|
|
42
|
+
flex-direction: column;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.sidebarWrapper {
|
|
46
|
+
display: flex;
|
|
47
|
+
flex-direction: column;
|
|
48
|
+
flex: 1 0 auto;
|
|
49
|
+
color: var(--brand-color-text-default);
|
|
50
|
+
background-color: var(--brand-color-canvas-subtle);
|
|
51
|
+
}
|
|
@@ -1,64 +1,44 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
2
|
import NextLink from 'next/link'
|
|
3
|
-
import {
|
|
3
|
+
import {IconButton, Link, ThemeProvider} from '@primer/react'
|
|
4
4
|
import {XIcon} from '@primer/octicons-react'
|
|
5
5
|
|
|
6
6
|
import {Drawer} from './Drawer'
|
|
7
|
-
import type {
|
|
7
|
+
import type {PageMapItem} from 'nextra'
|
|
8
8
|
import {Sidebar} from '../sidebar/Sidebar'
|
|
9
9
|
import {useColorMode} from '../../context/color-modes/useColorMode'
|
|
10
|
+
import styles from './NavDrawer.module.css'
|
|
10
11
|
|
|
11
12
|
type NavDrawerProps = {
|
|
12
13
|
isOpen: boolean
|
|
13
14
|
onDismiss: () => void
|
|
14
|
-
navItems?:
|
|
15
|
+
navItems?: PageMapItem[]
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
export function NavDrawer({isOpen, onDismiss, navItems}: NavDrawerProps) {
|
|
18
19
|
const {colorMode} = useColorMode()
|
|
19
20
|
return (
|
|
20
21
|
<Drawer isOpen={isOpen} onDismiss={onDismiss}>
|
|
21
|
-
<
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
sx={{flexDirection: 'column', flex: '0 0 auto', color: 'fg.default', bg: 'canvas.default', display: 'flex'}}
|
|
27
|
-
>
|
|
28
|
-
<Box
|
|
29
|
-
sx={{
|
|
30
|
-
borderWidth: 0,
|
|
31
|
-
borderRadius: 0,
|
|
32
|
-
borderBottomWidth: 1,
|
|
33
|
-
borderColor: 'border.muted',
|
|
34
|
-
borderStyle: 'solid',
|
|
35
|
-
}}
|
|
36
|
-
>
|
|
37
|
-
<Box sx={{py: 20, pl: 4, pr: 3, alignItems: 'center', justifyContent: 'space-between', display: 'flex'}}>
|
|
38
|
-
<Link as={NextLink} href="https://primer.style" sx={{fontWeight: 'bold', color: 'inherit'}}>
|
|
22
|
+
<div className={styles.scrollContainer}>
|
|
23
|
+
<div className={styles.header}>
|
|
24
|
+
<div className={styles.headerBorder}>
|
|
25
|
+
<div className={styles.headerContent}>
|
|
26
|
+
<Link as={NextLink} href="https://primer.style" className={styles.headerLink}>
|
|
39
27
|
Explore
|
|
40
28
|
</Link>
|
|
41
29
|
<IconButton icon={XIcon} aria-label="Close" onClick={onDismiss} variant="invisible" />
|
|
42
|
-
</
|
|
43
|
-
</
|
|
44
|
-
<
|
|
45
|
-
</
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
32
|
+
<div className={styles.navContainer}>{/* <PrimerNavItems items={primerNavItems} /> */}</div>
|
|
33
|
+
</div>
|
|
46
34
|
{navItems && navItems.length > 0 ? (
|
|
47
35
|
<ThemeProvider colorMode={colorMode}>
|
|
48
|
-
<
|
|
49
|
-
sx={{
|
|
50
|
-
flexDirection: 'column',
|
|
51
|
-
flex: '1 0 auto',
|
|
52
|
-
color: 'fg.default',
|
|
53
|
-
bg: 'canvas.subtle',
|
|
54
|
-
display: 'flex',
|
|
55
|
-
}}
|
|
56
|
-
>
|
|
36
|
+
<div className={styles.sidebarWrapper}>
|
|
57
37
|
<Sidebar pageMap={navItems} />
|
|
58
|
-
</
|
|
38
|
+
</div>
|
|
59
39
|
</ThemeProvider>
|
|
60
40
|
) : null}
|
|
61
|
-
</
|
|
41
|
+
</div>
|
|
62
42
|
</Drawer>
|
|
63
43
|
)
|
|
64
44
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {useCallback, useEffect, useMemo, useState} from 'react'
|
|
2
2
|
import debounce from 'lodash.debounce'
|
|
3
3
|
|
|
4
|
-
export function useNavDrawerState(breakpoint): [boolean, (value: boolean) => void] {
|
|
4
|
+
export function useNavDrawerState(breakpoint: string | number): [boolean, (value: boolean) => void] {
|
|
5
5
|
if (typeof breakpoint === 'string') {
|
|
6
6
|
breakpoint = parseInt(breakpoint, 10)
|
|
7
7
|
}
|
|
@@ -10,15 +10,16 @@ export type RelatedContentLink = MdxFile & {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
export type RelatedContentLinksProps = {
|
|
13
|
-
links
|
|
13
|
+
links?: RelatedContentLink[] | []
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
export function RelatedContentLinks({links}: RelatedContentLinksProps) {
|
|
17
|
-
if (!links
|
|
18
|
-
|
|
17
|
+
if (!links?.length) return null
|
|
19
18
|
return (
|
|
20
19
|
<div className="custom-component">
|
|
21
|
-
<Heading as="h2"
|
|
20
|
+
<Heading as="h2" size="subhead-large">
|
|
21
|
+
Related content
|
|
22
|
+
</Heading>
|
|
22
23
|
<UnorderedList className={styles.list}>
|
|
23
24
|
{links.map(page => (
|
|
24
25
|
<UnorderedList.Item key={page.route}>
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import {DocsItem, FrontMatter} from '../../../types'
|
|
2
|
+
import {RelatedContentLink} from './RelatedContentLinks'
|
|
3
|
+
|
|
4
|
+
type GetRelatedPages = (
|
|
5
|
+
route: string,
|
|
6
|
+
activeMetadata?: FrontMatter,
|
|
7
|
+
flatDocsDirectories?: DocsItem[],
|
|
8
|
+
) => RelatedContentLink[]
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Uses a frontmatter 'keywords' value (as an array)
|
|
12
|
+
* to find adjacent pages that share the same values.
|
|
13
|
+
* @returns {RelatedContentLink[]}
|
|
14
|
+
*/
|
|
15
|
+
export const getRelatedPages: GetRelatedPages = (route, activeMetadata, flatDocsDirectories) => {
|
|
16
|
+
if (!activeMetadata || !flatDocsDirectories) return []
|
|
17
|
+
const currentPageKeywords = activeMetadata.keywords || []
|
|
18
|
+
|
|
19
|
+
const relatedLinks = activeMetadata['related'] || []
|
|
20
|
+
const matches: RelatedContentLink[] = []
|
|
21
|
+
|
|
22
|
+
if (!relatedLinks.length || !flatDocsDirectories.length) return []
|
|
23
|
+
// 1. Check keywords property and find local matches
|
|
24
|
+
for (const page of flatDocsDirectories) {
|
|
25
|
+
if (page.route === route) continue
|
|
26
|
+
|
|
27
|
+
const pageKeywords = activeMetadata.keywords || []
|
|
28
|
+
const intersection = pageKeywords.filter(keyword => currentPageKeywords.includes(keyword))
|
|
29
|
+
|
|
30
|
+
if (intersection.length) {
|
|
31
|
+
matches.push(page)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 2. Check related property for internal and external links
|
|
36
|
+
for (const link of relatedLinks) {
|
|
37
|
+
if (!link.title || !link.href || link.href === route) continue
|
|
38
|
+
if (link.href.startsWith('/')) {
|
|
39
|
+
const page = flatDocsDirectories.find(localPage => localPage.route === link.href) as
|
|
40
|
+
| RelatedContentLink
|
|
41
|
+
| undefined
|
|
42
|
+
|
|
43
|
+
if (page) {
|
|
44
|
+
const entry = {
|
|
45
|
+
...page,
|
|
46
|
+
title: link.title || page.title,
|
|
47
|
+
route: link.href || page.route,
|
|
48
|
+
}
|
|
49
|
+
matches.push(entry)
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
matches.push({...link, route: link.href, name: link.title})
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return matches
|
|
57
|
+
}
|