@primer/doctocat-nextjs 0.1.0-rc.61e8cc5 → 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.
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
@@ -1,11 +1,9 @@
1
- import React, {useMemo, useRef} from 'react'
2
- import {createPortal} from 'react-dom'
1
+ 'use client'
2
+ import React, {PropsWithChildren, useMemo} from 'react'
3
3
  import NextLink from 'next/link'
4
4
  import Head from 'next/head'
5
- import type {Folder, MdxFile, NextraThemeLayoutProps} from 'nextra'
6
- import {MDXProvider} from 'nextra/mdx'
5
+ import type {Folder, MdxFile, PageMapItem} from 'nextra'
7
6
  import {useFSRoute} from 'nextra/hooks'
8
- import {PencilIcon} from '@primer/octicons-react'
9
7
  import {BaseStyles, Box as PRCBox, Breadcrumbs, PageLayout, ThemeProvider} from '@primer/react'
10
8
 
11
9
  import {
@@ -17,7 +15,6 @@ import {
17
15
  Button,
18
16
  Hero,
19
17
  Heading,
20
- InlineLink,
21
18
  Stack,
22
19
  Text,
23
20
  } from '@primer/react-brand'
@@ -27,28 +24,49 @@ import {UnderlineNav} from '../underline-nav/UnderlineNav'
27
24
  import '@primer/react-brand/fonts/fonts.css'
28
25
  import '@primer/react-brand/lib/css/main.css'
29
26
 
30
- import {useRouter} from 'next/router'
31
- import getConfig from 'next/config'
32
27
  import {normalizePages} from 'nextra/normalize-pages'
28
+ import {usePathname} from 'next/navigation'
29
+
33
30
  import {Header} from '../header/Header'
34
- import {TableOfContents} from '../table-of-contents/TableOfContents'
35
- import bodyStyles from '../../../css/prose.module.css'
36
31
  import {IndexCards} from '../index-cards/IndexCards'
37
32
  import {useColorMode} from '../../context/color-modes/useColorMode'
38
33
  import {SkipToMainContent} from '../skip-to-main-content/SkipToMainContent'
39
- import {RelatedContentLink, RelatedContentLinks} from '../related-content-links/RelatedContentLinks'
34
+ import {RelatedContentLinks} from '../related-content-links/RelatedContentLinks'
35
+ import {getRelatedPages} from '../related-content-links/getRelatedPages'
40
36
  import {hasChildren} from '../../../helpers/hasChildren'
37
+ import {Footer} from '../footer/Footer'
38
+
39
+ const repoSrcPath = process.env.NEXT_PUBLIC_REPO_SRC_PATH || ''
40
+ const repoURL = process.env.NEXT_PUBLIC_REPO || ''
41
+
42
+ if (!repoURL) {
43
+ // eslint-disable-next-line no-console
44
+ console.warn(
45
+ 'NEXT_PUBLIC_REPO is not set. Edit the .env.local file to set the NEXT_PUBLIC_REPO environment variable.',
46
+ )
47
+ }
41
48
 
42
- const {publicRuntimeConfig} = getConfig()
49
+ export type ThemeProps = PropsWithChildren<{
50
+ pageMap: PageMapItem[]
51
+ }>
43
52
 
44
- export function Theme({children, pageOpts}: NextraThemeLayoutProps) {
45
- const tocPortalRef = useRef<HTMLDivElement | null>(null)
46
- const {title, frontMatter, filePath, pageMap} = pageOpts
53
+ export function Theme({children, pageMap}: ThemeProps) {
54
+ const pathname = usePathname()
55
+
56
+ const normalizedPages = normalizePages({
57
+ list: pageMap,
58
+ route: pathname,
59
+ })
60
+
61
+ const {activeMetadata} = normalizedPages
62
+
63
+ const {filePath = '', title = ''} = activeMetadata || {}
64
+
65
+ const route = usePathname()
47
66
 
48
- const {asPath: route} = useRouter()
49
67
  const fsPath = useFSRoute()
50
68
  const {colorMode} = useColorMode()
51
- const {activePath, topLevelNavbarItems, docsDirectories, flatDocsDirectories} = useMemo(
69
+ const {activePath, flatDocsDirectories} = useMemo(
52
70
  () =>
53
71
  normalizePages({
54
72
  list: pageMap,
@@ -57,91 +75,35 @@ export function Theme({children, pageOpts}: NextraThemeLayoutProps) {
57
75
  [pageMap, fsPath],
58
76
  )
59
77
 
60
- const {siteTitle} = publicRuntimeConfig
78
+ // eslint-disable-next-line i18n-text/no-en
79
+ const siteTitle = process.env.NEXT_PUBLIC_SITE_TITLE || 'Example Site'
61
80
  const isHomePage = route === '/'
62
- const isIndexPage = /index\.mdx?$/.test(filePath) && !isHomePage && !frontMatter['show-tabs']
81
+ const isIndexPage = /index\.mdx?$/.test(filePath) && !isHomePage && activeMetadata && !activeMetadata['show-tabs']
63
82
  const data = !isHomePage && activePath[activePath.length - 2]
64
83
  const filteredTabData: MdxFile[] = data && hasChildren(data) ? ((data as Folder).children as MdxFile[]) : []
65
84
 
66
- /**
67
- * Uses a frontmatter 'keywords' value (as an array)
68
- * to find adjacent pages that share the same values.
69
- * @returns {RelatedContentLink[]}
70
- */
71
- const getRelatedPages = () => {
72
- const currentPageKeywords = frontMatter.keywords || []
73
- const relatedLinks = frontMatter['related'] || []
74
- const matches: RelatedContentLink[] = []
75
-
76
- // 1. Check keywords property and find local matches
77
- for (const page of flatDocsDirectories) {
78
- if (page.route === route) continue
79
-
80
- if ('frontMatter' in page) {
81
- const pageKeywords = page.frontMatter?.keywords || []
82
- const intersection = pageKeywords.filter(keyword => currentPageKeywords.includes(keyword))
83
-
84
- if (intersection.length) {
85
- matches.push(page)
86
- }
87
- }
88
- }
89
-
90
- // 2. Check related property for internal and external links
91
- for (const link of relatedLinks) {
92
- if (!link.title || !link.href || link.href === route) continue
93
-
94
- if (link.href.startsWith('/')) {
95
- const page = flatDocsDirectories.find(localPage => localPage.route === link.href) as
96
- | RelatedContentLink
97
- | undefined
98
-
99
- if (page) {
100
- const entry = {
101
- ...page,
102
- title: link.title || page.title,
103
- route: link.href || page.route,
104
- }
105
- matches.push(entry)
106
- }
107
- } else {
108
- matches.push({...link, route: link.href})
109
- }
110
- }
111
-
112
- return matches
113
- }
114
-
85
+ const relatedLinks = getRelatedPages(route, activeMetadata, flatDocsDirectories)
115
86
  return (
116
87
  <>
117
88
  <BrandThemeProvider dir="ltr" colorMode={colorMode}>
118
89
  <ThemeProvider colorMode={colorMode}>
119
90
  <BaseStyles>
120
- <Head>
121
- <title>{title}</title>
122
- {frontMatter.description && <meta name="description" content={frontMatter.description} />}
123
- <meta property="og:type" content="website" />
124
- <meta property="og:title" content={title} />
125
- {frontMatter.description && <meta property="og:description" content={frontMatter.description} />}
126
- <meta
127
- property="og:image"
128
- content={
129
- frontMatter.image ||
130
- 'https://github.com/primer/brand/assets/19292210/8562a9a5-a1e4-4722-9ec7-47ebccd5901e'
131
- }
132
- />
133
- {/* X (Twitter) OG */}
134
- <meta name="twitter:card" content="summary_large_image" />
135
- <meta name="twitter:title" content={title} />
136
- {frontMatter.description && <meta name="twitter:description" content={frontMatter.description} />}
137
- <meta
138
- name="twitter:image"
139
- content={
140
- frontMatter.image ||
141
- 'https://github.com/primer/brand/assets/19292210/8562a9a5-a1e4-4722-9ec7-47ebccd5901e'
142
- }
143
- />
144
- </Head>
91
+ {activeMetadata && (
92
+ <Head>
93
+ <title>{title}</title>
94
+ {activeMetadata.description && <meta name="description" content={activeMetadata.description} />}
95
+ <meta property="og:type" content="website" />
96
+ <meta property="og:title" content={title} />
97
+ {activeMetadata.description && <meta property="og:description" content={activeMetadata.description} />}
98
+ <meta property="og:image" content={activeMetadata.image || '/og-image.png'} />
99
+ {/* X (Twitter) OG */}
100
+ <meta name="twitter:card" content="summary_large_image" />
101
+ <meta name="twitter:title" content={title} />
102
+ {activeMetadata.description && <meta name="twitter:description" content={activeMetadata.description} />}
103
+ <meta name="twitter:image" content={activeMetadata.image || '/og-image.png'} />
104
+ </Head>
105
+ )}
106
+
145
107
  <AnimationProvider runOnce visibilityOptions={1} autoStaggerChildren={false}>
146
108
  <Animate animate="fade-in">
147
109
  <PRCBox
@@ -152,163 +114,102 @@ export function Theme({children, pageOpts}: NextraThemeLayoutProps) {
152
114
  }}
153
115
  >
154
116
  <SkipToMainContent href="#main">Skip to main content</SkipToMainContent>
155
- <Header
156
- pageMap={pageMap}
157
- docsDirectories={docsDirectories}
158
- menuItems={topLevelNavbarItems}
159
- siteTitle={siteTitle}
160
- />
117
+ <Header pageMap={pageMap} siteTitle={siteTitle} />
161
118
  </PRCBox>
162
119
  <PageLayout rowGap="none" columnGap="none" padding="none" containerWidth="full">
163
120
  <PageLayout.Content padding="normal">
164
121
  <div id="main">
165
- <PRCBox sx={!isHomePage && {display: 'flex', maxWidth: 1600, margin: '0 auto'}}>
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,
186
- )}
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 && (
201
- <Breadcrumbs.Item
202
- as={NextLink}
203
- href="/"
204
- sx={{
205
- color: 'var(--brand-InlineLink-color-rest)',
206
- }}
207
- >
208
- {siteTitle}
209
- </Breadcrumbs.Item>
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>
122
+ <PRCBox sx={!isHomePage && {maxWidth: 1200, width: '100%', margin: '0 auto'}}>
123
+ <Stack direction="vertical" padding="none" gap="spacious">
124
+ {!isHomePage && (
125
+ <>
126
+ {activePath.length && (
127
+ <Breadcrumbs>
128
+ {siteTitle && (
129
+ <Breadcrumbs.Item
130
+ as={NextLink}
131
+ href="/"
132
+ sx={{
133
+ color: 'var(--brand-InlineLink-color-rest)',
134
+ }}
135
+ >
136
+ {siteTitle}
137
+ </Breadcrumbs.Item>
228
138
  )}
229
-
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']}
252
- </Button>
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} />}
264
- </>
139
+ {activePath.map((item, index) => {
140
+ return (
141
+ <Breadcrumbs.Item
142
+ as={NextLink}
143
+ key={item.name}
144
+ href={item.route}
145
+ selected={index === activePath.length - 1}
146
+ sx={{
147
+ textTransform: 'capitalize',
148
+ color: 'var(--brand-InlineLink-color-rest)',
149
+ }}
150
+ >
151
+ {item.title.replace(/-/g, ' ')}
152
+ </Breadcrumbs.Item>
153
+ )
154
+ })}
155
+ </Breadcrumbs>
265
156
  )}
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)" />
285
157
 
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"
300
- >
301
- <Text as="p" variant="muted" size="100">
302
- &copy; {new Date().getFullYear()} GitHub, Inc. All rights reserved.
303
- </Text>
158
+ <Box>
159
+ <Stack direction="vertical" padding="none" gap={12} alignItems="flex-start">
160
+ {activeMetadata?.title && (
161
+ <Heading as="h1" size="3">
162
+ {activeMetadata.title}
163
+ </Heading>
164
+ )}
165
+ {activeMetadata?.description && (
166
+ <Text as="p" variant="muted" size="300">
167
+ {activeMetadata.description}
168
+ </Text>
169
+ )}
170
+ {activeMetadata?.image && (
171
+ <Box paddingBlockStart={16} style={{width: '100%'}}>
172
+ <Hero.Image src={activeMetadata.image} alt={activeMetadata['image-alt']} />
173
+ </Box>
174
+ )}
175
+ {activeMetadata && activeMetadata['action-1-text'] && (
176
+ <Box paddingBlockStart={16}>
177
+ <ButtonGroup>
178
+ <Button as="a" href={activeMetadata['action-1-link']}>
179
+ {activeMetadata['action-1-text']}
180
+ </Button>
181
+ {activeMetadata['action-2-text'] && (
182
+ <Button as="a" variant="secondary" href={activeMetadata['action-2-link']}>
183
+ {activeMetadata['action-2-text']}
184
+ </Button>
185
+ )}
186
+ </ButtonGroup>
304
187
  </Box>
305
- </Stack>
306
- </Box>
307
- </footer>
308
- </Stack>
309
- </PRCBox>
310
- </MDXProvider>
311
- <div ref={tocPortalRef} />
188
+ )}
189
+ </Stack>
190
+ </Box>
191
+ {activeMetadata && activeMetadata['show-tabs'] && (
192
+ <UnderlineNav tabData={filteredTabData} />
193
+ )}
194
+ </>
195
+ )}
196
+ <article>
197
+ {isIndexPage ? (
198
+ <IndexCards folderData={flatDocsDirectories} route={route} />
199
+ ) : (
200
+ <>
201
+ <>{children}</>
202
+
203
+ {relatedLinks.length > 0 && (
204
+ <PRCBox sx={{pt: 5}}>
205
+ <RelatedContentLinks links={relatedLinks} />
206
+ </PRCBox>
207
+ )}
208
+ </>
209
+ )}
210
+ </article>
211
+ <Footer filePath={filePath} repoURL={repoURL} repoSrcPath={repoSrcPath} />
212
+ </Stack>
312
213
  </PRCBox>
313
214
  </div>
314
215
  </PageLayout.Content>
@@ -321,7 +222,7 @@ export function Theme({children, pageOpts}: NextraThemeLayoutProps) {
321
222
  hidden={{narrow: true}}
322
223
  divider="line"
323
224
  >
324
- <Sidebar pageMap={docsDirectories} />
225
+ <Sidebar pageMap={pageMap} />
325
226
  </PageLayout.Pane>
326
227
  </PageLayout>
327
228
  </Animate>
@@ -332,22 +233,3 @@ export function Theme({children, pageOpts}: NextraThemeLayoutProps) {
332
233
  </>
333
234
  )
334
235
  }
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,8 +1,13 @@
1
+ 'use client'
1
2
  import React from 'react'
2
- import type {NextraThemeLayoutProps} from 'nextra'
3
3
 
4
4
  import {ColorModeProvider} from '../../context/color-modes/ColorModeProvider'
5
- import {Theme} from './Theme'
5
+ import {Theme, ThemeProps} from './Theme'
6
+ import {PageMapItem} from 'nextra'
7
+
8
+ type Props = {
9
+ pageMap: PageMapItem[]
10
+ } & ThemeProps
6
11
 
7
12
  /**
8
13
  * Catch-all layout component
@@ -10,10 +15,12 @@ import {Theme} from './Theme'
10
15
  * To add custom layouts, create a new file in `pages/_layouts`
11
16
  * and export a component with the same name as the layout file
12
17
  */
13
- export default function Shell({children, ...rest}: NextraThemeLayoutProps) {
18
+ export default function Shell({children, pageMap, ...rest}: Props) {
14
19
  return (
15
20
  <ColorModeProvider>
16
- <Theme {...rest}>{children}</Theme>
21
+ <Theme {...rest} pageMap={pageMap}>
22
+ {children}
23
+ </Theme>
17
24
  </ColorModeProvider>
18
25
  )
19
26
  }