@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 +38 -0
- package/components/layout/header/Header.tsx +5 -1
- package/components/layout/index-cards/IndexCards.module.css +11 -4
- package/components/layout/index-cards/IndexCards.tsx +15 -15
- package/components/layout/related-content-links/RelatedContentLinks.module.css +4 -0
- package/components/layout/related-content-links/RelatedContentLinks.tsx +34 -0
- package/components/layout/root-layout/Theme.tsx +93 -9
- package/components/layout/sidebar/Sidebar.tsx +1 -1
- package/components/layout/table-of-contents/TableOfContents.module.css +1 -6
- package/components/layout/table-of-contents/TableOfContents.tsx +1 -1
- package/package.json +1 -1
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
|
|
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
|
-
.
|
|
2
|
-
--brand-
|
|
3
|
-
|
|
4
|
-
|
|
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 {
|
|
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
|
-
<
|
|
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
|
-
<
|
|
33
|
-
<
|
|
34
|
-
<
|
|
35
|
-
{item.frontMatter
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
</
|
|
44
|
+
</Stack>
|
|
45
45
|
)
|
|
46
46
|
}
|
|
@@ -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="
|
|
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
|
-
<
|
|
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
|
-
|
|
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
|
-
{
|
|
207
|
-
<PRCBox
|
|
208
|
-
|
|
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
|
-
</
|
|
296
|
+
</div>
|
|
213
297
|
</PageLayout.Content>
|
|
214
298
|
<PageLayout.Pane
|
|
215
|
-
aria-label="
|
|
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}
|