@primer/doctocat-nextjs 0.0.3-rc.4b671a6 → 0.0.4-rc.68fc11e

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 CHANGED
@@ -1,9 +1,47 @@
1
1
  # @primer/doctocat-nextjs
2
2
 
3
+ ## 0.0.4
4
+
5
+ ### Patch Changes
6
+
7
+ - [#8](https://github.com/primer/doctocat-nextjs/pull/8) [`fd7f838`](https://github.com/primer/doctocat-nextjs/commit/fd7f83883152512b34dd7601346c03fe53e3ffb3) Thanks [@rezrah](https://github.com/rezrah)! - Added OpenGraph tags for improved social sharing experience.
8
+
9
+ - [#8](https://github.com/primer/doctocat-nextjs/pull/8) [`0d0879b`](https://github.com/primer/doctocat-nextjs/commit/0d0879b8e732e74a50861e22a0ff534d0e191a45) Thanks [@rezrah](https://github.com/rezrah)! - Enabled related content navigation using `keywords` and `related` properties in Markdown frontmatter.
10
+
11
+ Example:
12
+
13
+ ```
14
+ ---
15
+ title: Page A
16
+ keywords: ['keyword', 'another keyword']
17
+ ---
18
+ ```
19
+
20
+ ```
21
+ ---
22
+ title: Page B
23
+ keywords: ['keyword', 'another keyword']
24
+ ---
25
+ ```
26
+
27
+ The matching keyword values above across both pages, will enable automatic related content navigation between the two pages.
28
+
29
+ or using the `related` property:
30
+
31
+ ```
32
+ ---
33
+ related: [{title: External link example, href: https://example.com}]
34
+ ---
35
+ ```
36
+
37
+ - [#8](https://github.com/primer/doctocat-nextjs/pull/8) [`fd7f838`](https://github.com/primer/doctocat-nextjs/commit/fd7f83883152512b34dd7601346c03fe53e3ffb3) Thanks [@rezrah](https://github.com/rezrah)! - Fixed accessibility violations arising from duplicate landmarks and missing aria labels.
38
+
3
39
  ## 0.0.3
4
40
 
5
41
  ### Patch Changes
6
42
 
43
+ - [`937f773`](https://github.com/primer/doctocat-nextjs/commit/937f77385bc6c4d2c6289d0a6f12122373789f73) Thanks [@rezrah](https://github.com/rezrah)! - Redesign index pages to match current Doctocat styles
44
+
7
45
  - [`7703a7b`](https://github.com/primer/doctocat-nextjs/commit/7703a7b86bc906ff981a7f864a9916c963a35087) Thanks [@rezrah](https://github.com/rezrah)! - Fixed code bg in dark mode and applied fixed width to toc to prevent layout shift
8
46
 
9
47
  ## 0.0.2
@@ -163,7 +163,11 @@ export function Header({pageMap, docsDirectories, siteTitle}: HeaderProps) {
163
163
  }, [])
164
164
 
165
165
  return (
166
- <nav className={clsx(styles.Header, isSearchOpen && styles['Header--searchAreaOpen'])}>
166
+ <nav
167
+ className={clsx(styles.Header, isSearchOpen && styles['Header--searchAreaOpen'])}
168
+ role="navigation"
169
+ aria-label="Header Navigation"
170
+ >
167
171
  <Link href="/" className={styles.Header__siteTitle}>
168
172
  <MarkGithubIcon size={24} />
169
173
  <Text as="p" size="300" weight="semibold">
@@ -1,5 +1,12 @@
1
- .IndexCards {
2
- --brand-Grid-spacing-margin: 0;
3
- --brand-Card-maxWidth: 100%;
4
- --brand-Grid-spacing-row: var(--brand-Grid-spacing-column-gap);
1
+ .heading a {
2
+ color: var(--brand-InlineLink-color-rest);
3
+ text-decoration: none;
4
+ }
5
+
6
+ .heading a:hover {
7
+ text-decoration: underline;
8
+ }
9
+
10
+ .heading a:active {
11
+ color: var(--brand-InlineLink-color-pressed);
5
12
  }
@@ -1,9 +1,9 @@
1
1
  import React from 'react'
2
- import {Card, Grid} from '@primer/react-brand'
2
+ import {Heading, Stack, Text} from '@primer/react-brand'
3
3
  import {Folder, MdxFile} from 'nextra'
4
4
 
5
- import styles from './IndexCards.module.css'
6
5
  import Link from 'next/link'
6
+ import styles from './IndexCards.module.css'
7
7
 
8
8
  type IndexCardsProps = {
9
9
  route: string
@@ -25,22 +25,22 @@ export function IndexCards({route, folderData}: IndexCardsProps) {
25
25
  const filteredData = folderData.filter(item => item.kind === 'MdxPage' && item.route.includes(`${route}/`))
26
26
 
27
27
  return (
28
- <Grid className={styles.IndexCards}>
28
+ <Stack direction="vertical" padding="none" gap="spacious">
29
29
  {filteredData.map((item: DocsItem) => {
30
- if (item.kind !== 'MdxPage') return null
30
+ if (item.kind !== 'MdxPage' || !item.frontMatter) return null
31
+
31
32
  return (
32
- <Grid.Column span={{medium: 6}} key={`cell-${item.route}`}>
33
- <Link href={item.route} legacyBehavior passHref>
34
- <Card href="#" hasBorder style={{width: '100%'}}>
35
- {item.frontMatter && <Card.Heading>{item.frontMatter.title}</Card.Heading>}
36
- {item.frontMatter && item.frontMatter.description && (
37
- <Card.Description>{item.frontMatter.description}</Card.Description>
38
- )}
39
- </Card>
40
- </Link>
41
- </Grid.Column>
33
+ <Stack direction="vertical" padding="none" gap="condensed" key={item.frontMatter.title}>
34
+ <Heading as="h2" size="6" className={styles.heading}>
35
+ <Link href={item.route} legacyBehavior passHref>
36
+ {item.frontMatter.title}
37
+ </Link>
38
+ </Heading>
39
+
40
+ {item.frontMatter.description && <Text as="p">{item.frontMatter.description}</Text>}
41
+ </Stack>
42
42
  )
43
43
  })}
44
- </Grid>
44
+ </Stack>
45
45
  )
46
46
  }
@@ -0,0 +1,4 @@
1
+ .list {
2
+ margin-block: var(--base-size-24);
3
+ margin-inline-start: 0 !important;
4
+ }
@@ -0,0 +1,34 @@
1
+ import React from 'react'
2
+ import {NavList} from '@primer/react'
3
+ import {Text, Heading, UnorderedList, InlineLink} from '@primer/react-brand'
4
+ import {MdxFile} from 'nextra'
5
+
6
+ import styles from './RelatedContentLinks.module.css'
7
+ import Link from 'next/link'
8
+ import {LinkExternalIcon} from '@primer/octicons-react'
9
+
10
+ export type RelatedContentLink = MdxFile & {
11
+ title: string
12
+ }
13
+
14
+ export type RelatedContentLinksProps = {
15
+ links: RelatedContentLink[]
16
+ }
17
+
18
+ export function RelatedContentLinks({links}: RelatedContentLinksProps) {
19
+ if (!links.length) return null
20
+
21
+ return (
22
+ <div className="custom-component">
23
+ <Heading as="h2">Related content</Heading>
24
+ <UnorderedList className={styles.list}>
25
+ {links.map(page => (
26
+ <UnorderedList.Item key={page.route}>
27
+ <InlineLink href={page.route}>{page.title}</InlineLink>{' '}
28
+ {page.route.startsWith('http') ? <LinkExternalIcon /> : null}
29
+ </UnorderedList.Item>
30
+ ))}
31
+ </UnorderedList>
32
+ </div>
33
+ )
34
+ }
@@ -36,6 +36,7 @@ import {IndexCards} from '../index-cards/IndexCards'
36
36
  import {useColorMode} from '../../context/color-modes/useColorMode'
37
37
  import {getComponents} from '../../mdx-components/mdx-components'
38
38
  import {SkipToMainContent} from '../skip-to-main-content/SkipToMainContent'
39
+ import {RelatedContentLink, RelatedContentLinks} from '../related-content-links/RelatedContentLinks'
39
40
 
40
41
  const {publicRuntimeConfig} = getConfig()
41
42
 
@@ -64,6 +65,55 @@ export function Theme({children, pageOpts}: NextraThemeLayoutProps) {
64
65
  ? ((data as Folder).children.filter(child => child.kind === 'MdxPage') as MdxFile[])
65
66
  : []
66
67
 
68
+ /**
69
+ * Uses a frontmatter 'keywords' value (as an array)
70
+ * to find adjacent pages that share the same values.
71
+ * @returns {RelatedContentLink[]}
72
+ */
73
+ const getRelatedPages = () => {
74
+ const currentPageKeywords = frontMatter.keywords || []
75
+ const relatedLinks = frontMatter['related'] || []
76
+ const matches: RelatedContentLink[] = []
77
+
78
+ // 1. Check keywords property and find local matches
79
+ for (const page of flatDocsDirectories) {
80
+ if (page.route === route) continue
81
+
82
+ if ('frontMatter' in page) {
83
+ const pageKeywords = page.frontMatter?.keywords || []
84
+ const intersection = pageKeywords.filter(keyword => currentPageKeywords.includes(keyword))
85
+
86
+ if (intersection.length) {
87
+ matches.push(page)
88
+ }
89
+ }
90
+ }
91
+
92
+ // 2. Check related property for internal and external links
93
+ for (const link of relatedLinks) {
94
+ if (!link.title || !link.href || link.href === route) continue
95
+
96
+ if (link.href.startsWith('/')) {
97
+ const page = flatDocsDirectories.find(localPage => localPage.route === link.href) as
98
+ | RelatedContentLink
99
+ | undefined
100
+
101
+ if (page) {
102
+ const entry = {
103
+ ...page,
104
+ title: link.title || page.title,
105
+ route: link.href || page.route,
106
+ }
107
+ matches.push(entry)
108
+ }
109
+ } else {
110
+ matches.push({...link, route: link.href})
111
+ }
112
+ }
113
+
114
+ return matches
115
+ }
116
+
67
117
  return (
68
118
  <>
69
119
  <BrandThemeProvider dir="ltr" colorMode={colorMode}>
@@ -71,7 +121,28 @@ export function Theme({children, pageOpts}: NextraThemeLayoutProps) {
71
121
  <BaseStyles>
72
122
  <Head>
73
123
  <title>{title}</title>
74
- <meta name="og:image" content={frontMatter.image} />
124
+ {frontMatter.description && <meta name="description" content={frontMatter.description} />}
125
+ <meta property="og:type" content="website" />
126
+ <meta property="og:title" content={title} />
127
+ {frontMatter.description && <meta property="og:description" content={frontMatter.description} />}
128
+ <meta
129
+ property="og:image"
130
+ content={
131
+ frontMatter.image ||
132
+ 'https://github.com/primer/brand/assets/19292210/8562a9a5-a1e4-4722-9ec7-47ebccd5901e'
133
+ }
134
+ />
135
+ {/* X (Twitter) OG */}
136
+ <meta name="twitter:card" content="summary_large_image" />
137
+ <meta name="twitter:title" content={title} />
138
+ {frontMatter.description && <meta name="twitter:description" content={frontMatter.description} />}
139
+ <meta
140
+ name="twitter:image"
141
+ content={
142
+ frontMatter.image ||
143
+ 'https://github.com/primer/brand/assets/19292210/8562a9a5-a1e4-4722-9ec7-47ebccd5901e'
144
+ }
145
+ />
75
146
  </Head>
76
147
  <AnimationProvider runOnce visibilityOptions={1} autoStaggerChildren={false}>
77
148
  <Animate animate="fade-in">
@@ -92,7 +163,7 @@ export function Theme({children, pageOpts}: NextraThemeLayoutProps) {
92
163
  </PRCBox>
93
164
  <PageLayout rowGap="none" columnGap="none" padding="none" containerWidth="full">
94
165
  <PageLayout.Content padding="normal">
95
- <main id="main">
166
+ <div id="main">
96
167
  <PRCBox sx={!isHomePage && {display: 'flex', maxWidth: 1600, margin: '0 auto'}}>
97
168
  <PRCBox sx={!isHomePage && {maxWidth: 800, width: '100%', margin: '0 auto'}}>
98
169
  <Stack direction="vertical" padding="none" gap="spacious">
@@ -170,7 +241,14 @@ export function Theme({children, pageOpts}: NextraThemeLayoutProps) {
170
241
  {isIndexPage ? (
171
242
  <IndexCards folderData={flatDocsDirectories} route={route} />
172
243
  ) : (
173
- <MDXProvider components={getComponents()}>{children}</MDXProvider>
244
+ <>
245
+ <MDXProvider components={getComponents()}>{children}</MDXProvider>
246
+ {getRelatedPages().length > 0 && (
247
+ <PRCBox sx={{pt: 5}}>
248
+ <RelatedContentLinks links={getRelatedPages()} />
249
+ </PRCBox>
250
+ )}
251
+ </>
174
252
  )}
175
253
  </article>
176
254
  <footer>
@@ -203,16 +281,22 @@ export function Theme({children, pageOpts}: NextraThemeLayoutProps) {
203
281
  </footer>
204
282
  </Stack>
205
283
  </PRCBox>
206
- {!isHomePage && headings.length > 0 && (
207
- <PRCBox sx={{py: 2, pr: 3, display: ['none', null, null, null, 'block']}}>
208
- <TableOfContents headings={headings} />
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} />}
209
293
  </PRCBox>
210
- )}
294
+ </PRCBox>
211
295
  </PRCBox>
212
- </main>
296
+ </div>
213
297
  </PageLayout.Content>
214
298
  <PageLayout.Pane
215
- aria-label="Sticky pane"
299
+ aria-label="Side navigation"
216
300
  width="small"
217
301
  sticky
218
302
  padding="none"
@@ -55,7 +55,7 @@ export function Sidebar({pageMap}: SidebarProps) {
55
55
 
56
56
  return (
57
57
  <div className={styles.Sidebar}>
58
- <NavList className={styles.NavList}>
58
+ <NavList className={styles.NavList} aria-label="Menu links">
59
59
  {reorderedPageMap.map(item => {
60
60
  if (item.kind === 'MdxPage' && item.route === '/') return null
61
61
 
@@ -1,9 +1,3 @@
1
- .wrapper {
2
- position: sticky;
3
- top: var(--base-size-112);
4
- width: 220px;
5
- }
6
-
7
1
  .heading {
8
2
  font-size: var(--base-size-12);
9
3
  padding-inline-start: var(--base-size-16);
@@ -15,6 +9,7 @@
15
9
  margin-block-end: var(--base-size-4);
16
10
  transition: transform var(--brand-animation-duration-fast) var(--brand-animation-easing-default);
17
11
  }
12
+
18
13
  .item[aria-current='location'] {
19
14
  transform: translateX(var(--base-size-4));
20
15
  }
@@ -61,7 +61,7 @@ export function TableOfContents({headings}: TableOfContentsProps) {
61
61
  <Text as="p" size="100" variant="muted" weight="normal" className={styles.heading}>
62
62
  On this page
63
63
  </Text>
64
- <NavList>
64
+ <NavList aria-label="Table of contents">
65
65
  {depth2Headings.map(heading => (
66
66
  <NavList.Item
67
67
  className={styles.item}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primer/doctocat-nextjs",
3
- "version": "0.0.3-rc.4b671a6",
3
+ "version": "0.0.4-rc.68fc11e",
4
4
  "description": "A Next.js theme for building Primer documentation sites",
5
5
  "main": "index.js",
6
6
  "scripts": {