@primer/doctocat-nextjs 0.1.0 → 0.2.0-rc.eeac884

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.
Files changed (33) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/components/content/caption/Caption.tsx +3 -3
  3. package/components/content/dos-and-donts/DosAndDonts.module.css +60 -0
  4. package/components/content/dos-and-donts/DosAndDonts.tsx +17 -44
  5. package/components/context/color-modes/ColorModeProvider.tsx +2 -2
  6. package/components/index.ts +9 -0
  7. package/components/layout/article/Article.module.css +8 -0
  8. package/components/layout/article/Article.tsx +24 -0
  9. package/components/layout/footer/Footer.module.css +0 -0
  10. package/components/layout/footer/Footer.tsx +41 -0
  11. package/components/layout/header/Header.tsx +10 -15
  12. package/components/layout/heading-link/HeadingLink.module.css +16 -0
  13. package/components/layout/heading-link/HeadingLinks.tsx +31 -0
  14. package/components/layout/nav-drawer/Drawer.tsx +7 -2
  15. package/components/layout/nav-drawer/NavDrawer.module.css +51 -0
  16. package/components/layout/nav-drawer/NavDrawer.tsx +16 -36
  17. package/components/layout/nav-drawer/useNavDrawerState.ts +1 -1
  18. package/components/layout/related-content-links/RelatedContentLinks.tsx +5 -4
  19. package/components/layout/related-content-links/getRelatedPages.tsx +57 -0
  20. package/components/layout/root-layout/Theme.tsx +144 -262
  21. package/components/layout/root-layout/index.tsx +11 -4
  22. package/components/layout/sidebar/Sidebar.tsx +72 -91
  23. package/components/layout/table-of-contents/TableOfContents.module.css +5 -0
  24. package/components/layout/table-of-contents/TableOfContents.tsx +5 -3
  25. package/components/layout/underline-nav/UnderlineNav.tsx +13 -4
  26. package/components/library.ts +3 -0
  27. package/css/prose.module.css +8 -0
  28. package/doctocat.config.js +4 -10
  29. package/helpers/hasChildren.ts +3 -3
  30. package/index.tsx +2 -6
  31. package/package.json +10 -9
  32. package/tsconfig.json +5 -3
  33. 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: 'gray.5'}} />
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, Text} from '@primer/react'
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" bg="success.fg" borderColor="success.muted" {...rest}>
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" bg="danger.fg" borderColor="danger.muted" {...rest}>
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
- bg: string
39
- borderColor: string
35
+ className?: string
40
36
  indented?: boolean
41
37
  }
42
38
 
43
- export function DoDontBase({children, title, bg, borderColor, indented}: React.PropsWithChildren<DoDontBaseProps>) {
39
+ export function DoDontBase({children, title, indented, className, ...rest}: React.PropsWithChildren<DoDontBaseProps>) {
44
40
  return (
45
- <Box className="exclude-from-prose" sx={{display: 'flex', flexDirection: 'column'}}>
41
+ <div className={clsx(`exclude-from-prose`, styles.doDontBase, className)} {...rest}>
46
42
  <Box
43
+ className={styles.header}
47
44
  sx={{
48
- display: 'flex',
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
- <Text sx={{fontWeight: 'bold', fontSize: '1', color: 'fg.onEmphasis'}}>{title}</Text>
48
+ <span className={styles.headerText}>{title}</span>
60
49
  </Box>
61
- <Box
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
- <Box
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
- </Box>
54
+ </blockquote>
82
55
  ) : (
83
56
  children
84
57
  )}
85
- </Box>
86
- </Box>
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,8 @@
1
+ .Article {
2
+ display: grid;
3
+ }
4
+
5
+ .Article--withToc {
6
+ grid-template-columns: 1fr 200px;
7
+ gap: var(--base-size-48);
8
+ }
@@ -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
+ &copy; {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, docsDirectories, siteTitle}: HeaderProps) {
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(({frontMatter, route}: MdxFile) => {
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
- <Box className={clsx(styles.Header__searchArea, isSearchOpen && styles['Header__searchArea--open'])}>
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
- </Box>
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
- export function Drawer({isOpen, onDismiss, children}) {
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 {Box, IconButton, Link, ThemeProvider} from '@primer/react'
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 {PageItem} from 'nextra/normalize-pages'
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?: PageItem[]
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
- <Box
22
- style={{overflow: 'auto', WebkitOverflowScrolling: 'touch'}}
23
- sx={{flexDirection: 'column', height: '100%', bg: 'canvas.default', display: 'flex'}}
24
- >
25
- <Box
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
- </Box>
43
- </Box>
44
- <Box sx={{flexDirection: 'column', display: 'flex'}}>{/* <PrimerNavItems items={primerNavItems} /> */}</Box>
45
- </Box>
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
- <Box
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
- </Box>
38
+ </div>
59
39
  </ThemeProvider>
60
40
  ) : null}
61
- </Box>
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: RelatedContentLink[]
13
+ links?: RelatedContentLink[] | []
14
14
  }
15
15
 
16
16
  export function RelatedContentLinks({links}: RelatedContentLinksProps) {
17
- if (!links.length) return null
18
-
17
+ if (!links?.length) return null
19
18
  return (
20
19
  <div className="custom-component">
21
- <Heading as="h2">Related content</Heading>
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
+ }