@primer/doctocat-nextjs 0.0.4-rc.68fc11e → 0.1.0-rc.61e8cc5

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.
@@ -1,7 +1,8 @@
1
- const base = require('../../.eslintrc')
1
+ const base = require('../../.eslintrc.cjs')
2
2
 
3
3
  module.exports = {
4
4
  ...base,
5
+ plugins: ['react-hooks'],
5
6
  parserOptions: {
6
7
  tsconfigRootDir: __dirname,
7
8
  },
@@ -0,0 +1,5 @@
1
+ {
2
+ "eslint.options": {
3
+ "configFile": ".eslintrc.cjs"
4
+ }
5
+ }
package/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # @primer/doctocat-nextjs
2
2
 
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#18](https://github.com/primer/doctocat-nextjs/pull/18) [`bfe68b1`](https://github.com/primer/doctocat-nextjs/commit/bfe68b14e8e3b4383ea41dcbf47373df8a130567) Thanks [@rezrah](https://github.com/rezrah)! - Upgraded internal framework to [Nextra v3](https://the-guild.dev/blog/nextra-3).
8
+
9
+ To migrate Doctocat to this release, follow these steps:
10
+
11
+ 1. Install the latest version `npm i @primer/doctocat-nextjs@0.1.0`
12
+ 2. Rename your `next.config.js` to be `next.config.mjs`. Add `type="module"` to your `package.json` and update the file contents to match the following:
13
+
14
+ ```diff
15
+ - const withDoctocat = require('@primer/doctocat-nextjs/doctocat.config.js')
16
+
17
+ - module.exports = {
18
+ - ...withDoctocat({
19
+
20
+ - }),
21
+ - }
22
+
23
+ + import withDoctocat from '@primer/doctocat-nextjs/doctocat.config.js'
24
+
25
+ + export default {
26
+ + ...withDoctocat({
27
+
28
+ + }),
29
+ + }
30
+
31
+ ```
32
+
3
33
  ## 0.0.4
4
34
 
5
35
  ### Patch Changes
@@ -1,4 +1,4 @@
1
- import React from 'react'
1
+ import {createContext} from 'react'
2
2
 
3
3
  export type ColorMode = 'light' | 'dark'
4
4
 
@@ -12,4 +12,4 @@ const defaultValues: ColorModeContextProps = {
12
12
  setColorMode: () => {},
13
13
  }
14
14
 
15
- export const ColorModeContext = React.createContext<ColorModeContextProps>(defaultValues)
15
+ export const ColorModeContext = createContext<ColorModeContextProps>(defaultValues)
@@ -1,10 +1,10 @@
1
1
  import {MarkGithubIcon, MoonIcon, SearchIcon, SunIcon, ThreeBarsIcon, XIcon} from '@primer/octicons-react'
2
2
  import {Box, FormControl, IconButton, TextInput} from '@primer/react'
3
3
  import {Heading, Stack, Text} from '@primer/react-brand'
4
- import clsx from 'clsx'
5
- import {MdxFile, PageMapItem} from 'nextra'
4
+ import {clsx} from 'clsx'
5
+ import {MdxFile, Folder, PageMapItem} from 'nextra'
6
6
  import type {PageItem} from 'nextra/normalize-pages'
7
- import React, {useCallback, useEffect, useMemo} from 'react'
7
+ import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'
8
8
  import {debounce} from 'lodash'
9
9
 
10
10
  import Link from 'next/link'
@@ -12,6 +12,8 @@ import styles from './Header.module.css'
12
12
  import {NavDrawer} from '../nav-drawer/NavDrawer'
13
13
  import {useNavDrawerState} from '../nav-drawer/useNavDrawerState'
14
14
  import {useColorMode} from '../../context/color-modes/useColorMode'
15
+ import {hasChildren} from '../../../helpers/hasChildren'
16
+ import {DocsItem} from '../../../types'
15
17
 
16
18
  type HeaderProps = {
17
19
  pageMap: PageMapItem[]
@@ -28,14 +30,14 @@ type SearchResults = {
28
30
 
29
31
  export function Header({pageMap, docsDirectories, siteTitle}: HeaderProps) {
30
32
  const {colorMode, setColorMode} = useColorMode()
31
- const inputRef = React.useRef<HTMLInputElement | null>(null)
32
- const searchResultsRef = React.useRef<HTMLElement | null>(null)
33
+ const inputRef = useRef<HTMLInputElement | null>(null)
34
+ const searchResultsRef = useRef<HTMLElement | null>(null)
33
35
  const [isNavDrawerOpen, setIsNavDrawerOpen] = useNavDrawerState('768')
34
- const [isSearchOpen, setIsSearchOpen] = React.useState(false)
35
- const [isSearchResultOpen, setIsSearchResultOpen] = React.useState(false)
36
- const [searchResults, setSearchResults] = React.useState<SearchResults[] | undefined>()
37
- const [searchTerm, setSearchTerm] = React.useState<string | undefined>('')
38
- const [activeDescendant] = React.useState<number>(-1)
36
+ const [isSearchOpen, setIsSearchOpen] = useState(false)
37
+ const [isSearchResultOpen, setIsSearchResultOpen] = useState(false)
38
+ const [searchResults, setSearchResults] = useState<SearchResults[] | undefined>()
39
+ const [searchTerm, setSearchTerm] = useState<string | undefined>('')
40
+ const [activeDescendant] = useState<number>(-1)
39
41
 
40
42
  useEffect(() => {
41
43
  if (isSearchOpen && inputRef.current) {
@@ -80,10 +82,10 @@ export function Header({pageMap, docsDirectories, siteTitle}: HeaderProps) {
80
82
  () =>
81
83
  pageMap
82
84
  .map(item => {
83
- if (item.kind === 'Folder') {
84
- return item.children.filter(child => child.kind === 'MdxPage')
85
+ if (hasChildren(item)) {
86
+ return (item as Folder).children.filter(child => !hasChildren(child))
85
87
  }
86
- if (item.kind === 'MdxPage') {
88
+ if ((item as DocsItem).type === 'doc') {
87
89
  return item
88
90
  }
89
91
  })
@@ -1,33 +1,21 @@
1
1
  import React from 'react'
2
2
  import {Heading, Stack, Text} from '@primer/react-brand'
3
- import {Folder, MdxFile} from 'nextra'
4
3
 
5
4
  import Link from 'next/link'
6
5
  import styles from './IndexCards.module.css'
6
+ import {DocsItem} from '../../../types'
7
7
 
8
8
  type IndexCardsProps = {
9
9
  route: string
10
10
  folderData: DocsItem[]
11
11
  }
12
12
 
13
- type FolderWithoutChildren = Omit<Folder, 'children'>
14
-
15
- type DocsItem = (MdxFile | FolderWithoutChildren) & {
16
- title: string
17
- type: string
18
- children?: DocsItem[]
19
- firstChildRoute?: string
20
- withIndexPage?: boolean
21
- isUnderCurrentDocsTree?: boolean
22
- }
23
-
24
13
  export function IndexCards({route, folderData}: IndexCardsProps) {
25
- const filteredData = folderData.filter(item => item.kind === 'MdxPage' && item.route.includes(`${route}/`))
26
-
14
+ const filteredData = folderData.filter(item => item.type === 'doc' && item.route.includes(`${route}/`))
27
15
  return (
28
16
  <Stack direction="vertical" padding="none" gap="spacious">
29
17
  {filteredData.map((item: DocsItem) => {
30
- if (item.kind !== 'MdxPage' || !item.frontMatter) return null
18
+ if (item.type !== 'doc' || !item.frontMatter) return null
31
19
 
32
20
  return (
33
21
  <Stack direction="vertical" padding="none" gap="condensed" key={item.frontMatter.title}>
@@ -1,28 +1,28 @@
1
- import React from 'react'
1
+ import {useCallback, useEffect, useMemo, useState} from 'react'
2
2
  import debounce from 'lodash.debounce'
3
3
 
4
4
  export function useNavDrawerState(breakpoint): [boolean, (value: boolean) => void] {
5
5
  if (typeof breakpoint === 'string') {
6
6
  breakpoint = parseInt(breakpoint, 10)
7
7
  }
8
- const [isOpen, setOpen] = React.useState<boolean>(false)
8
+ const [isOpen, setOpen] = useState<boolean>(false)
9
9
 
10
- const onResize = React.useCallback(() => {
10
+ const onResize = useCallback(() => {
11
11
  if (window.innerWidth >= breakpoint) {
12
12
  setOpen(false)
13
13
  }
14
14
  }, [setOpen, breakpoint])
15
15
 
16
- const handleSetOpen = React.useCallback(
16
+ const handleSetOpen = useCallback(
17
17
  (value: boolean) => {
18
18
  setOpen(value)
19
19
  },
20
20
  [setOpen],
21
21
  )
22
22
 
23
- const debouncedOnResize = React.useMemo(() => debounce(onResize, 250), [onResize])
23
+ const debouncedOnResize = useMemo(() => debounce(onResize, 250), [onResize])
24
24
 
25
- React.useEffect(() => {
25
+ useEffect(() => {
26
26
  if (isOpen) {
27
27
  // eslint-disable-next-line github/prefer-observers
28
28
  window.addEventListener('resize', debouncedOnResize)
@@ -1,10 +1,8 @@
1
1
  import React from 'react'
2
- import {NavList} from '@primer/react'
3
- import {Text, Heading, UnorderedList, InlineLink} from '@primer/react-brand'
2
+ import {Heading, UnorderedList, InlineLink} from '@primer/react-brand'
4
3
  import {MdxFile} from 'nextra'
5
4
 
6
5
  import styles from './RelatedContentLinks.module.css'
7
- import Link from 'next/link'
8
6
  import {LinkExternalIcon} from '@primer/octicons-react'
9
7
 
10
8
  export type RelatedContentLink = MdxFile & {
@@ -1,4 +1,5 @@
1
- import React, {useMemo} from 'react'
1
+ import React, {useMemo, useRef} from 'react'
2
+ import {createPortal} from 'react-dom'
2
3
  import NextLink from 'next/link'
3
4
  import Head from 'next/head'
4
5
  import type {Folder, MdxFile, NextraThemeLayoutProps} from 'nextra'
@@ -34,36 +35,33 @@ import {TableOfContents} from '../table-of-contents/TableOfContents'
34
35
  import bodyStyles from '../../../css/prose.module.css'
35
36
  import {IndexCards} from '../index-cards/IndexCards'
36
37
  import {useColorMode} from '../../context/color-modes/useColorMode'
37
- import {getComponents} from '../../mdx-components/mdx-components'
38
38
  import {SkipToMainContent} from '../skip-to-main-content/SkipToMainContent'
39
39
  import {RelatedContentLink, RelatedContentLinks} from '../related-content-links/RelatedContentLinks'
40
+ import {hasChildren} from '../../../helpers/hasChildren'
40
41
 
41
42
  const {publicRuntimeConfig} = getConfig()
42
43
 
43
44
  export function Theme({children, pageOpts}: NextraThemeLayoutProps) {
44
- const {title, frontMatter, headings, filePath, pageMap, route} = pageOpts
45
- const {locale = 'en-US', defaultLocale} = useRouter()
45
+ const tocPortalRef = useRef<HTMLDivElement | null>(null)
46
+ const {title, frontMatter, filePath, pageMap} = pageOpts
47
+
48
+ const {asPath: route} = useRouter()
46
49
  const fsPath = useFSRoute()
47
50
  const {colorMode} = useColorMode()
48
51
  const {activePath, topLevelNavbarItems, docsDirectories, flatDocsDirectories} = useMemo(
49
52
  () =>
50
53
  normalizePages({
51
54
  list: pageMap,
52
- locale,
53
- defaultLocale,
54
55
  route: fsPath,
55
56
  }),
56
- [pageMap, locale, defaultLocale, fsPath],
57
+ [pageMap, fsPath],
57
58
  )
58
59
 
59
60
  const {siteTitle} = publicRuntimeConfig
60
61
  const isHomePage = route === '/'
61
62
  const isIndexPage = /index\.mdx?$/.test(filePath) && !isHomePage && !frontMatter['show-tabs']
62
63
  const data = !isHomePage && activePath[activePath.length - 2]
63
- const filteredTabData: MdxFile[] =
64
- data && data.kind === 'Folder'
65
- ? ((data as Folder).children.filter(child => child.kind === 'MdxPage') as MdxFile[])
66
- : []
64
+ const filteredTabData: MdxFile[] = data && hasChildren(data) ? ((data as Folder).children as MdxFile[]) : []
67
65
 
68
66
  /**
69
67
  * Uses a frontmatter 'keywords' value (as an array)
@@ -165,133 +163,152 @@ export function Theme({children, pageOpts}: NextraThemeLayoutProps) {
165
163
  <PageLayout.Content padding="normal">
166
164
  <div id="main">
167
165
  <PRCBox sx={!isHomePage && {display: 'flex', maxWidth: 1600, margin: '0 auto'}}>
168
- <PRCBox sx={!isHomePage && {maxWidth: 800, width: '100%', margin: '0 auto'}}>
169
- <Stack direction="vertical" padding="none" gap="spacious">
170
- {!isHomePage && (
171
- <>
172
- {activePath.length && (
173
- <Breadcrumbs>
174
- {siteTitle && (
175
- <Breadcrumbs.Item
176
- as={NextLink}
177
- href="/"
178
- sx={{
179
- color: 'var(--brand-InlineLink-color-rest)',
180
- }}
181
- >
182
- {siteTitle}
183
- </Breadcrumbs.Item>
166
+ <MDXProvider
167
+ components={{
168
+ wrapper: ({children: mdxChildren, toc}) => {
169
+ return (
170
+ <>
171
+ {tocPortalRef.current &&
172
+ createPortal(
173
+ <PRCBox sx={{py: 2, pr: 3, display: ['none', null, null, null, 'block']}}>
174
+ <PRCBox
175
+ sx={{
176
+ position: 'sticky',
177
+ top: 112,
178
+ width: 220,
179
+ }}
180
+ >
181
+ {toc.length > 0 && <TableOfContents headings={toc} />}
182
+ </PRCBox>
183
+ </PRCBox>,
184
+
185
+ tocPortalRef.current,
184
186
  )}
185
- {activePath.map((item, index) => {
186
- return (
187
+
188
+ <div>{mdxChildren}</div>
189
+ </>
190
+ )
191
+ },
192
+ }}
193
+ >
194
+ <PRCBox sx={!isHomePage && {maxWidth: 800, width: '100%', margin: '0 auto'}}>
195
+ <Stack direction="vertical" padding="none" gap="spacious">
196
+ {!isHomePage && (
197
+ <>
198
+ {activePath.length && (
199
+ <Breadcrumbs>
200
+ {siteTitle && (
187
201
  <Breadcrumbs.Item
188
202
  as={NextLink}
189
- key={item.name}
190
- href={item.route}
191
- selected={index === activePath.length - 1}
203
+ href="/"
192
204
  sx={{
193
- textTransform: 'capitalize',
194
205
  color: 'var(--brand-InlineLink-color-rest)',
195
206
  }}
196
207
  >
197
- {item.title.replace(/-/g, ' ')}
208
+ {siteTitle}
198
209
  </Breadcrumbs.Item>
199
- )
200
- })}
201
- </Breadcrumbs>
202
- )}
210
+ )}
211
+ {activePath.map((item, index) => {
212
+ return (
213
+ <Breadcrumbs.Item
214
+ as={NextLink}
215
+ key={item.name}
216
+ href={item.route}
217
+ selected={index === activePath.length - 1}
218
+ sx={{
219
+ textTransform: 'capitalize',
220
+ color: 'var(--brand-InlineLink-color-rest)',
221
+ }}
222
+ >
223
+ {item.title.replace(/-/g, ' ')}
224
+ </Breadcrumbs.Item>
225
+ )
226
+ })}
227
+ </Breadcrumbs>
228
+ )}
203
229
 
204
- <Box>
205
- <Stack direction="vertical" padding="none" gap={12} alignItems="flex-start">
206
- {frontMatter.title && (
207
- <Heading as="h1" size="3">
208
- {frontMatter.title}
209
- </Heading>
210
- )}
211
- {frontMatter.description && (
212
- <Text as="p" variant="muted" size="300">
213
- {frontMatter.description}
214
- </Text>
215
- )}
216
- {frontMatter.image && (
217
- <Box paddingBlockStart={16}>
218
- <Hero.Image src={frontMatter.image} alt={frontMatter['image-alt']} />
219
- </Box>
220
- )}
221
- {frontMatter['action-1-text'] && (
222
- <Box paddingBlockStart={16}>
223
- <ButtonGroup>
224
- <Button as="a" href={frontMatter['action-1-link']}>
225
- {frontMatter['action-1-text']}
226
- </Button>
227
- {frontMatter['action-2-text'] && (
228
- <Button as="a" variant="secondary" href={frontMatter['action-2-link']}>
229
- {frontMatter['action-2-text']}
230
+ <Box>
231
+ <Stack direction="vertical" padding="none" gap={12} alignItems="flex-start">
232
+ {frontMatter.title && (
233
+ <Heading as="h1" size="3">
234
+ {frontMatter.title}
235
+ </Heading>
236
+ )}
237
+ {frontMatter.description && (
238
+ <Text as="p" variant="muted" size="300">
239
+ {frontMatter.description}
240
+ </Text>
241
+ )}
242
+ {frontMatter.image && (
243
+ <Box paddingBlockStart={16}>
244
+ <Hero.Image src={frontMatter.image} alt={frontMatter['image-alt']} />
245
+ </Box>
246
+ )}
247
+ {frontMatter['action-1-text'] && (
248
+ <Box paddingBlockStart={16}>
249
+ <ButtonGroup>
250
+ <Button as="a" href={frontMatter['action-1-link']}>
251
+ {frontMatter['action-1-text']}
230
252
  </Button>
231
- )}
232
- </ButtonGroup>
233
- </Box>
234
- )}
235
- </Stack>
236
- </Box>
237
- {Boolean(frontMatter['show-tabs']) && <UnderlineNav tabData={filteredTabData} />}
238
- </>
239
- )}
240
- <article className={route !== '/' && !isIndexPage ? bodyStyles.Prose : ''}>
241
- {isIndexPage ? (
242
- <IndexCards folderData={flatDocsDirectories} route={route} />
243
- ) : (
244
- <>
245
- <MDXProvider components={getComponents()}>{children}</MDXProvider>
246
- {getRelatedPages().length > 0 && (
247
- <PRCBox sx={{pt: 5}}>
248
- <RelatedContentLinks links={getRelatedPages()} />
249
- </PRCBox>
250
- )}
253
+ {frontMatter['action-2-text'] && (
254
+ <Button as="a" variant="secondary" href={frontMatter['action-2-link']}>
255
+ {frontMatter['action-2-text']}
256
+ </Button>
257
+ )}
258
+ </ButtonGroup>
259
+ </Box>
260
+ )}
261
+ </Stack>
262
+ </Box>
263
+ {Boolean(frontMatter['show-tabs']) && <UnderlineNav tabData={filteredTabData} />}
251
264
  </>
252
265
  )}
253
- </article>
254
- <footer>
255
- <Box marginBlockStart={64}>
256
- <Stack direction="vertical" padding="none" gap={16}>
257
- <Stack direction="horizontal" padding="none" alignItems="center" gap={8}>
258
- <PencilIcon size={16} fill="var(--brand-InlineLink-color-rest)" />
266
+ <article className={route !== '/' && !isIndexPage ? bodyStyles.Prose : ''}>
267
+ {isIndexPage ? (
268
+ <IndexCards folderData={flatDocsDirectories} route={route} />
269
+ ) : (
270
+ <>
271
+ <>{children}</>
272
+ {getRelatedPages().length > 0 && (
273
+ <PRCBox sx={{pt: 5}}>
274
+ <RelatedContentLinks links={getRelatedPages()} />
275
+ </PRCBox>
276
+ )}
277
+ </>
278
+ )}
279
+ </article>
280
+ <footer>
281
+ <Box marginBlockStart={64}>
282
+ <Stack direction="vertical" padding="none" gap={16}>
283
+ <Stack direction="horizontal" padding="none" alignItems="center" gap={8}>
284
+ <PencilIcon size={16} fill="var(--brand-InlineLink-color-rest)" />
259
285
 
260
- <InlineLink
261
- href={`${publicRuntimeConfig.repo}/blob/main/${
262
- publicRuntimeConfig.repoSrcPath ? `${publicRuntimeConfig.repoSrcPath}/` : ''
263
- }${filePath}`}
286
+ <InlineLink
287
+ href={`${publicRuntimeConfig.repo}/blob/main/${
288
+ publicRuntimeConfig.repoSrcPath ? `${publicRuntimeConfig.repoSrcPath}/` : ''
289
+ }${filePath}`}
290
+ >
291
+ Edit this page
292
+ </InlineLink>
293
+ </Stack>
294
+ <Box
295
+ marginBlockStart={8}
296
+ paddingBlockStart={24}
297
+ borderStyle="solid"
298
+ borderBlockStartWidth="thin"
299
+ borderColor="default"
264
300
  >
265
- Edit this page
266
- </InlineLink>
301
+ <Text as="p" variant="muted" size="100">
302
+ &copy; {new Date().getFullYear()} GitHub, Inc. All rights reserved.
303
+ </Text>
304
+ </Box>
267
305
  </Stack>
268
- <Box
269
- marginBlockStart={8}
270
- paddingBlockStart={24}
271
- borderStyle="solid"
272
- borderBlockStartWidth="thin"
273
- borderColor="default"
274
- >
275
- <Text as="p" variant="muted" size="100">
276
- &copy; {new Date().getFullYear()} GitHub, Inc. All rights reserved.
277
- </Text>
278
- </Box>
279
- </Stack>
280
- </Box>
281
- </footer>
282
- </Stack>
283
- </PRCBox>
284
- <PRCBox sx={{py: 2, pr: 3, display: ['none', null, null, null, 'block']}}>
285
- <PRCBox
286
- sx={{
287
- position: 'sticky',
288
- top: 112,
289
- width: 220,
290
- }}
291
- >
292
- {!isHomePage && headings.length > 0 && <TableOfContents headings={headings} />}
306
+ </Box>
307
+ </footer>
308
+ </Stack>
293
309
  </PRCBox>
294
- </PRCBox>
310
+ </MDXProvider>
311
+ <div ref={tocPortalRef} />
295
312
  </PRCBox>
296
313
  </div>
297
314
  </PageLayout.Content>
@@ -315,3 +332,22 @@ export function Theme({children, pageOpts}: NextraThemeLayoutProps) {
315
332
  </>
316
333
  )
317
334
  }
335
+
336
+ export function MdxWrapper({children, toc}) {
337
+ return (
338
+ <>
339
+ <PRCBox sx={{py: 2, pr: 3, display: ['none', null, null, null, 'block']}}>
340
+ <PRCBox
341
+ sx={{
342
+ position: 'sticky',
343
+ top: 112,
344
+ width: 220,
345
+ }}
346
+ >
347
+ {toc.length > 0 && <TableOfContents headings={toc} />}
348
+ </PRCBox>
349
+ </PRCBox>
350
+ <div>{children}</div>
351
+ </>
352
+ )
353
+ }
@@ -1,31 +1,25 @@
1
1
  import React, {useMemo} from 'react'
2
2
  import NextLink from 'next/link'
3
3
  import {NavList} from '@primer/react'
4
- import {Folder, MdxFile, PageMapItem} from 'nextra'
4
+ import {Folder, MdxFile} from 'nextra'
5
5
  import {useRouter} from 'next/router'
6
6
  import getConfig from 'next/config'
7
7
 
8
8
  import styles from './Sidebar.module.css'
9
9
  import {LinkExternalIcon} from '@primer/octicons-react'
10
- import type {ThemeConfig} from '../../../index'
10
+ import type {DocsItem, ThemeConfig} from '../../../index'
11
+ import {hasChildren} from '../../../helpers/hasChildren'
11
12
 
12
13
  type SidebarProps = {
13
14
  pageMap: DocsItem[]
14
15
  }
15
16
 
16
- type FolderWithoutChildren = Omit<Folder, 'children'>
17
+ const {publicRuntimeConfig} = getConfig()
17
18
 
18
- type DocsItem = (MdxFile | FolderWithoutChildren) & {
19
- title: string
20
- type: string
21
- children?: DocsItem[]
22
- firstChildRoute?: string
23
- withIndexPage?: boolean
24
- isUnderCurrentDocsTree?: boolean
19
+ const hasShowTabs = (child: DocsItem): boolean => {
20
+ return child.name === 'index' && (child as MdxFile).frontMatter?.['show-tabs'] === true
25
21
  }
26
22
 
27
- const {publicRuntimeConfig} = getConfig()
28
-
29
23
  export function Sidebar({pageMap}: SidebarProps) {
30
24
  const router = useRouter()
31
25
 
@@ -39,11 +33,11 @@ export function Sidebar({pageMap}: SidebarProps) {
39
33
  const reorderedPageMap = useMemo(
40
34
  () =>
41
35
  [...pageMap].sort((a, b) => {
42
- if (a.kind === 'Folder' && a.children) {
43
- const aIndex = a.children.find(child => child.name === 'index' && child.kind === 'MdxPage')
36
+ if (hasChildren(a) && a.children) {
37
+ const aIndex = a.children.find(child => child.name === 'index' && child.type === 'doc')
44
38
  const aPosition = (aIndex as MdxFile | undefined)?.frontMatter?.['menu-position'] ?? Infinity
45
- if (b.kind === 'Folder' && b.children) {
46
- const bIndex = b.children.find(child => child.name === 'index' && child.kind === 'MdxPage')
39
+ if (hasChildren(b) && b.children) {
40
+ const bIndex = b.children.find(child => child.name === 'index' && child.type === 'doc')
47
41
  const bPosition = (bIndex as MdxFile | undefined)?.frontMatter?.['menu-position'] ?? Infinity
48
42
  return aPosition - bPosition
49
43
  }
@@ -57,21 +51,19 @@ export function Sidebar({pageMap}: SidebarProps) {
57
51
  <div className={styles.Sidebar}>
58
52
  <NavList className={styles.NavList} aria-label="Menu links">
59
53
  {reorderedPageMap.map(item => {
60
- if (item.kind === 'MdxPage' && item.route === '/') return null
54
+ if (item.type === 'doc' && item.route === '/') return null
61
55
 
62
- if (item.kind === 'MdxPage') {
56
+ if (!hasChildren(item) && item.type === 'doc') {
63
57
  return (
64
58
  <NavList.Item as={NextLink} key={item.name} href={item.route} sx={{textTransform: 'capitalize'}}>
65
- {item.frontMatter?.title ?? item.name}
59
+ {(item as MdxFile).frontMatter?.title ?? item.name}
66
60
  </NavList.Item>
67
61
  )
68
62
  }
69
63
 
70
64
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
71
- if (item.kind === 'Folder') {
72
- const indexPage = (item as Folder).children.find(
73
- child => child.kind === 'MdxPage' && child.name === 'index',
74
- ) as MdxFile
65
+ if (hasChildren(item)) {
66
+ const indexPage = (item as Folder).children.find(child => (child as MdxFile).name === 'index') as MdxFile
75
67
  const subNavName = indexPage.frontMatter?.title ?? ''
76
68
  const shouldShowTabs = indexPage.frontMatter?.['show-tabs'] ?? false
77
69
  if (shouldShowTabs) {
@@ -87,28 +79,27 @@ export function Sidebar({pageMap}: SidebarProps) {
87
79
  {item.children &&
88
80
  item.children
89
81
  .sort((a, b) => (a.name === 'index' ? -1 : b.name === 'index' ? 1 : 0)) // puts index page first
90
- .filter(
91
- child =>
92
- (child as DocsItem).name !== 'index' ||
93
- ((child.name === 'index' && (child as MdxFile).frontMatter?.['show-tabs']) ?? false),
94
- ) // only show index page if it has show-tabs
82
+ // only show index page if it has show-tabs
83
+ .filter(child => child.name !== 'index' || hasShowTabs(child))
95
84
  .map((child: DocsItem) => {
96
- if (child.kind === 'MdxPage') {
85
+ if (child.type === 'doc') {
97
86
  return (
98
87
  <NavList.Item
99
88
  as={NextLink}
100
89
  key={child.name}
101
90
  href={child.route}
91
+ sx={{textTransform: 'capitalize'}}
102
92
  aria-current={child.route === router.pathname ? 'page' : undefined}
103
93
  >
104
- {(child as MdxFile).frontMatter?.title || item.name}
94
+ {child.title}
105
95
  </NavList.Item>
106
96
  )
107
97
  }
108
98
 
109
- if ((child as DocsItem).kind === 'Folder') {
110
- const landingPageItem: PageMapItem | undefined = (child as Folder).children.find(
111
- innerChild => innerChild.kind === 'MdxPage' && innerChild.name === 'index',
99
+ if (hasChildren(child)) {
100
+ const landingPageItem = (child as Folder).children.find(
101
+ innerChild =>
102
+ (innerChild as DocsItem).type === 'doc' && (innerChild as DocsItem).name === 'index',
112
103
  )
113
104
  return (
114
105
  <NavList.Item
package/css/global.css CHANGED
@@ -14,100 +14,6 @@ body {
14
14
  margin-block-start: 0 !important;
15
15
  }
16
16
 
17
- /**
18
- * Code block overrides
19
- */
20
-
21
- :root {
22
- --shiki-color-text: #414141;
23
- --shiki-color-background: transparent;
24
- --shiki-token-constant: #1976d2;
25
- --shiki-token-string: #22863a;
26
- --shiki-token-comment: #aaa;
27
- --shiki-token-keyword: #d32f2f;
28
- --shiki-token-parameter: #ff9801;
29
- --shiki-token-function: #6f42c1;
30
- --shiki-token-string-expression: var(--shiki-token-string);
31
- --shiki-token-punctuation: #212121;
32
- --shiki-token-link: var(--shiki-token-string);
33
- --shiki-color-ansi-black: #24292e;
34
- --shiki-color-ansi-black-dim: rgba(36, 41, 46, 0.5);
35
- --shiki-color-ansi-red: #d73a49;
36
- --shiki-color-ansi-red-dim: rgba(215, 58, 73, 0.5);
37
- --shiki-color-ansi-green: #28a745;
38
- --shiki-color-ansi-green-dim: rgba(40, 167, 69, 0.5);
39
- --shiki-color-ansi-yellow: #dbab09;
40
- --shiki-color-ansi-yellow-dim: rgba(219, 171, 9, 0.5);
41
- --shiki-color-ansi-blue: #0366d6;
42
- --shiki-color-ansi-blue-dim: rgba(3, 102, 214, 0.5);
43
- --shiki-color-ansi-magenta: #5a32a3;
44
- --shiki-color-ansi-magenta-dim: rgba(90, 50, 163, 0.5);
45
- --shiki-color-ansi-cyan: #1b7c83;
46
- --shiki-color-ansi-cyan-dim: rgba(27, 124, 131, 0.5);
47
- --shiki-color-ansi-white: #6a737d;
48
- --shiki-color-ansi-white-dim: rgba(106, 115, 125, 0.5);
49
- --shiki-color-ansi-bright-black: #959da5;
50
- --shiki-color-ansi-bright-black-dim: rgba(149, 157, 165, 0.5);
51
- --shiki-color-ansi-bright-red: #cb2431;
52
- --shiki-color-ansi-bright-red-dim: rgba(203, 36, 49, 0.5);
53
- --shiki-color-ansi-bright-green: #22863a;
54
- --shiki-color-ansi-bright-green-dim: rgba(34, 134, 58, 0.5);
55
- --shiki-color-ansi-bright-yellow: #b08800;
56
- --shiki-color-ansi-bright-yellow-dim: rgba(176, 136, 0, 0.5);
57
- --shiki-color-ansi-bright-blue: #005cc5;
58
- --shiki-color-ansi-bright-blue-dim: rgba(0, 92, 197, 0.5);
59
- --shiki-color-ansi-bright-magenta: #5a32a3;
60
- --shiki-color-ansi-bright-magenta-dim: rgba(90, 50, 163, 0.5);
61
- --shiki-color-ansi-bright-cyan: #3192aa;
62
- --shiki-color-ansi-bright-cyan-dim: rgba(49, 146, 170, 0.5);
63
- --shiki-color-ansi-bright-white: #d1d5da;
64
- --shiki-color-ansi-bright-white-dim: rgba(209, 213, 218, 0.5);
65
- }
66
-
67
- [data-color-mode='dark'] {
68
- --shiki-color-text: #d1d1d1;
69
- --shiki-token-constant: #79b8ff;
70
- --shiki-token-string: #ffab70;
71
- --shiki-token-comment: #6b737c;
72
- --shiki-token-keyword: #f97583;
73
- --shiki-token-function: #b392f0;
74
- --shiki-token-string-expression: #4bb74a;
75
- --shiki-token-punctuation: #bbb;
76
- --shiki-token-link: var(--shiki-token-string);
77
- --shiki-color-ansi-black: #586069;
78
- --shiki-color-ansi-black-dim: rgba(88, 96, 105, 0.5);
79
- --shiki-color-ansi-red: #ea4a5a;
80
- --shiki-color-ansi-red-dim: rgba(234, 74, 90, 0.5);
81
- --shiki-color-ansi-green: #34d058;
82
- --shiki-color-ansi-green-dim: rgba(52, 208, 88, 0.5);
83
- --shiki-color-ansi-yellow: #ffea7f;
84
- --shiki-color-ansi-yellow-dim: rgba(255, 234, 127, 0.5);
85
- --shiki-color-ansi-blue: #2188ff;
86
- --shiki-color-ansi-blue-dim: rgba(33, 136, 255, 0.5);
87
- --shiki-color-ansi-magenta: #b392f0;
88
- --shiki-color-ansi-magenta-dim: rgba(179, 146, 240, 0.5);
89
- --shiki-color-ansi-cyan: #39c5cf;
90
- --shiki-color-ansi-cyan-dim: rgba(57, 197, 207, 0.5);
91
- --shiki-color-ansi-white: #d1d5da;
92
- --shiki-color-ansi-white-dim: rgba(209, 213, 218, 0.5);
93
- --shiki-color-ansi-bright-black: #959da5;
94
- --shiki-color-ansi-bright-black-dim: rgba(149, 157, 165, 0.5);
95
- --shiki-color-ansi-bright-red: #f97583;
96
- --shiki-color-ansi-bright-red-dim: rgba(249, 117, 131, 0.5);
97
- --shiki-color-ansi-bright-green: #85e89d;
98
- --shiki-color-ansi-bright-green-dim: rgba(133, 232, 157, 0.5);
99
- --shiki-color-ansi-bright-yellow: #ffea7f;
100
- --shiki-color-ansi-bright-yellow-dim: rgba(255, 234, 127, 0.5);
101
- --shiki-color-ansi-bright-blue: #79b8ff;
102
- --shiki-color-ansi-bright-blue-dim: rgba(121, 184, 255, 0.5);
103
- --shiki-color-ansi-bright-magenta: #b392f0;
104
- --shiki-color-ansi-bright-magenta-dim: rgba(179, 146, 240, 0.5);
105
- --shiki-color-ansi-bright-cyan: #56d4dd;
106
- --shiki-color-ansi-bright-cyan-dim: rgba(86, 212, 221, 0.5);
107
- --shiki-color-ansi-bright-white: #fafbfc;
108
- --shiki-color-ansi-bright-white-dim: rgba(250, 251, 252, 0.5);
109
- }
110
-
111
17
  pre {
112
18
  background-color: var(--brand-color-canvas-subtle);
113
19
  border-radius: var(--brand-borderRadius-large);
@@ -123,6 +29,15 @@ code {
123
29
  'calt' 1,
124
30
  'ss01' 1;
125
31
  }
32
+
33
+ code span {
34
+ color: var(--shiki-light);
35
+ }
36
+
37
+ [data-color-mode='dark'] code span {
38
+ color: var(--shiki-dark);
39
+ }
40
+
126
41
  code[data-line-numbers] > .line {
127
42
  padding-left: 0.5rem;
128
43
  }
@@ -1,9 +1,12 @@
1
- const withNextra = require('nextra')({
1
+ import nextra from 'nextra'
2
+ import transpiler from 'next-transpile-modules'
3
+
4
+ const withNextra = nextra({
2
5
  theme: '@primer/doctocat-nextjs',
3
6
  staticImage: true,
4
7
  })
5
8
 
6
- const withTM = require('next-transpile-modules')(['@primer/doctocat-nextjs'])
9
+ const withTM = transpiler(['@primer/doctocat-nextjs'])
7
10
 
8
11
  /*
9
12
  * Wrapper for next config, that allows us to do some shared configuration
@@ -23,4 +26,4 @@ function withDoctocat(config = {}) {
23
26
  })
24
27
  }
25
28
 
26
- module.exports = withDoctocat
29
+ export default withDoctocat
@@ -0,0 +1,5 @@
1
+ import {PageMapItem, Folder} from 'nextra'
2
+ import {DocsItem} from '../types'
3
+
4
+ export const hasChildren = (item: DocsItem | PageMapItem): boolean =>
5
+ 'children' in item && Array.isArray((item as Folder).children) && (item as Folder).children.length > 0
package/package.json CHANGED
@@ -1,37 +1,45 @@
1
1
  {
2
2
  "name": "@primer/doctocat-nextjs",
3
- "version": "0.0.4-rc.68fc11e",
3
+ "version": "0.1.0-rc.61e8cc5",
4
4
  "description": "A Next.js theme for building Primer documentation sites",
5
5
  "main": "index.js",
6
+ "type": "module",
6
7
  "scripts": {
7
- "lint": "eslint '**/*.{js,ts,tsx,md,mdx}' --max-warnings=0 --config ./.eslintrc.js",
8
+ "check": "tsc --noEmit",
9
+ "lint": "eslint '**/*.{js,ts,tsx,md,mdx}' --max-warnings=0 --config ./.eslintrc.cjs",
8
10
  "test": "echo \"Error: no test specified\" && exit 1"
9
11
  },
10
12
  "author": "GitHub, Inc.",
11
13
  "license": "MIT",
12
14
  "peerDependencies": {
13
- "next": "^14.0.3",
14
- "react": "^18.2.0",
15
- "react-dom": "^18.2.0"
15
+ "next": "15.1.6",
16
+ "react": "18.3.1",
17
+ "react-dom": "18.3.1"
16
18
  },
17
19
  "dependencies": {
18
- "@primer/octicons-react": "^19.8.0",
19
- "@primer/react": "^36.2.0",
20
- "@primer/react-brand": "^0.29.1",
20
+ "@primer/octicons-react": "19.14.0",
21
+ "@primer/react": "36.27.0",
22
+ "@primer/react-brand": "0.46.0",
21
23
  "next-transpile-modules": "^10.0.1",
22
- "framer-motion": "^10.16.12",
24
+ "framer-motion": "12.0.1",
23
25
  "lodash.debounce": "^4.0.8",
24
- "react-focus-on": "^3.9.1",
25
- "nextra": "^2.13.2"
26
+ "react-focus-on": "3.9.4",
27
+ "nextra": "3.3.1",
28
+ "react": "18.3.1",
29
+ "react-dom": "18.3.1"
26
30
  },
27
31
  "devDependencies": {
28
32
  "@github/prettier-config": "^0.0.6",
29
33
  "@types/node": "18.11.10",
30
- "@types/react": "^18.2.39",
31
- "@types/react-dom": "^18.2.17",
32
- "clsx": "^2.0.0",
33
- "next": "^14.0.3",
34
- "styled-components": "^6.1.1",
35
- "typescript": "^5.3.2"
34
+ "@types/react": "18.3.12",
35
+ "@types/react-dom": "18.3.1",
36
+ "clsx": "2.1.1",
37
+ "next": "15.1.6",
38
+ "styled-components": "6.1.13",
39
+ "typescript": "5.7.3"
40
+ },
41
+ "overrides": {
42
+ "@types/react": "18.3.12",
43
+ "@types/react-dom": "18.3.1"
36
44
  }
37
45
  }
package/types.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import {Folder, MdxFile} from 'nextra'
2
+
1
3
  export type ThemeConfig = {
2
4
  docsRepositoryBase: string
3
5
  sidebarLinks?: {
@@ -5,3 +7,14 @@ export type ThemeConfig = {
5
7
  href: string
6
8
  }[]
7
9
  }
10
+
11
+ export type FolderWithoutChildren = Omit<Folder, 'children'>
12
+
13
+ export type DocsItem = MdxFile & {
14
+ title: string
15
+ type: string
16
+ children?: DocsItem[]
17
+ firstChildRoute?: string
18
+ withIndexPage?: boolean
19
+ isUnderCurrentDocsTree?: boolean
20
+ }
@@ -1,14 +0,0 @@
1
- .Heading__anchor {
2
- color: var(--brand-color-text-default) !important;
3
- text-decoration: none !important;
4
- }
5
-
6
- .Heading__anchorIcon {
7
- visibility: hidden;
8
- margin-left: var(--base-size-8);
9
- vertical-align: middle !important;
10
- }
11
-
12
- .Heading__anchor:hover .Heading__anchorIcon {
13
- visibility: visible;
14
- }
@@ -1,43 +0,0 @@
1
- import React from 'react'
2
- import type {ComponentProps, ReactElement} from 'react'
3
- import clsx from 'clsx'
4
- import type {Components} from 'nextra/mdx'
5
-
6
- import {LinkIcon} from '@primer/octicons-react'
7
- import NextLink from 'next/link'
8
-
9
- import styles from './mdx-components.module.css'
10
-
11
- // Anchor links for Headings
12
- function HeadingLink({
13
- tag: Tag,
14
- context,
15
- children,
16
- id,
17
- className,
18
- ...props
19
- }: ComponentProps<'h2'> & {
20
- tag: `h${2 | 3 | 4 | 5 | 6}`
21
- context: {index: number}
22
- }): ReactElement {
23
- return (
24
- <Tag {...props} id={id} className={clsx(className)}>
25
- <NextLink href={`#${id}`} className={styles.Heading__anchor}>
26
- {children} <LinkIcon className={styles.Heading__anchorIcon} size={16} />
27
- </NextLink>
28
- </Tag>
29
- )
30
- }
31
-
32
- export const Link = ({href = '', className, ...props}) => <NextLink href={href} {...props} />
33
- export const getComponents = (): Components => {
34
- const context = {index: 0}
35
- return {
36
- h2: props => <HeadingLink tag="h2" context={context} {...props} />,
37
- h3: props => <HeadingLink tag="h3" context={context} {...props} />,
38
- h4: props => <HeadingLink tag="h4" context={context} {...props} />,
39
- h5: props => <HeadingLink tag="h5" context={context} {...props} />,
40
- h6: props => <HeadingLink tag="h6" context={context} {...props} />,
41
- a: Link,
42
- }
43
- }
File without changes