@primer/doctocat-nextjs 0.0.2-rc.cdbd6bf → 0.0.2

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
@@ -4,4 +4,14 @@
4
4
 
5
5
  ### Patch Changes
6
6
 
7
+ - [#4](https://github.com/primer/doctocat-nextjs/pull/4) [`4f28982`](https://github.com/primer/doctocat-nextjs/commit/4f28982e327e75f199f28fad987f1e827deafeb2) Thanks [@joseph-lozano](https://github.com/joseph-lozano)! - Wrap links with Next's Link component
8
+
9
+ - [`6f21970`](https://github.com/primer/doctocat-nextjs/commit/6f21970c74f7635be89fc4cd20376d7fe5ca35e7) Thanks [@rezrah](https://github.com/rezrah)! - Switch hero image order with description and reduce `h2` block start margin
10
+
11
+ - [#6](https://github.com/primer/doctocat-nextjs/pull/6) [`afd4e17`](https://github.com/primer/doctocat-nextjs/commit/afd4e1762f6294a14942d415c693319a874cd3fb) Thanks [@rezrah](https://github.com/rezrah)! - - Add MDX to HTML overrides mechanism and apply to headings and anchors
12
+
13
+ - Added anchor links to headings to match current functionality on primer.style
14
+
15
+ - [`b49f218`](https://github.com/primer/doctocat-nextjs/commit/b49f218e9bbc2de720476e21888956bee6081967) Thanks [@rezrah](https://github.com/rezrah)! - Removed sidebar links and added skip to main content a11y link
16
+
7
17
  - [#2](https://github.com/primer/doctocat-nextjs/pull/2) [`2742b32`](https://github.com/primer/doctocat-nextjs/commit/2742b3214e7a53416d23f0459dc389f7c22cf5a1) Thanks [@rezrah](https://github.com/rezrah)! - Remove code blocks stylesheet, now merged into global.css
@@ -4,7 +4,7 @@ import {Heading, Stack, Text} from '@primer/react-brand'
4
4
  import clsx from 'clsx'
5
5
  import {MdxFile, PageMapItem} from 'nextra'
6
6
  import type {PageItem} from 'nextra/normalize-pages'
7
- import React, {useEffect, useMemo} from 'react'
7
+ import React, {useCallback, useEffect, useMemo} from 'react'
8
8
  import {debounce} from 'lodash'
9
9
 
10
10
  import Link from 'next/link'
@@ -37,6 +37,12 @@ export function Header({pageMap, docsDirectories, siteTitle}: HeaderProps) {
37
37
  const [searchTerm, setSearchTerm] = React.useState<string | undefined>('')
38
38
  const [activeDescendant] = React.useState<number>(-1)
39
39
 
40
+ useEffect(() => {
41
+ if (isSearchOpen && inputRef.current) {
42
+ inputRef.current.focus()
43
+ }
44
+ }, [isSearchOpen])
45
+
40
46
  useEffect(() => {
41
47
  const handleKeyDown = (event: KeyboardEvent) => {
42
48
  if (event.key === 'Escape') {
@@ -152,6 +158,10 @@ export function Header({pageMap, docsDirectories, siteTitle}: HeaderProps) {
152
158
  alert(`Name: ${inputRef.current.value}`)
153
159
  }
154
160
 
161
+ const handleSearchButtonOpenClick = useCallback(() => {
162
+ setIsSearchOpen(true)
163
+ }, [])
164
+
155
165
  return (
156
166
  <nav className={clsx(styles.Header, isSearchOpen && styles['Header--searchAreaOpen'])}>
157
167
  <Link href="/" className={styles.Header__siteTitle}>
@@ -292,7 +302,7 @@ export function Header({pageMap, docsDirectories, siteTitle}: HeaderProps) {
292
302
  variant="invisible"
293
303
  aria-label={`Open search`}
294
304
  sx={{display: ['flex', null, 'none']}}
295
- onClick={() => setIsSearchOpen(true)}
305
+ onClick={handleSearchButtonOpenClick}
296
306
  />
297
307
  <Box sx={{display: ['flex', null, 'none']}}>
298
308
  <IconButton
@@ -1,9 +1,9 @@
1
1
  import React from 'react'
2
2
  import {Card, Grid} from '@primer/react-brand'
3
3
  import {Folder, MdxFile} from 'nextra'
4
- import {useRouter} from 'next/router'
5
4
 
6
5
  import styles from './IndexCards.module.css'
6
+ import Link from 'next/link'
7
7
 
8
8
  type IndexCardsProps = {
9
9
  route: string
@@ -22,7 +22,6 @@ type DocsItem = (MdxFile | FolderWithoutChildren) & {
22
22
  }
23
23
 
24
24
  export function IndexCards({route, folderData}: IndexCardsProps) {
25
- const {basePath} = useRouter()
26
25
  const filteredData = folderData.filter(item => item.kind === 'MdxPage' && item.route.includes(`${route}/`))
27
26
 
28
27
  return (
@@ -31,12 +30,14 @@ export function IndexCards({route, folderData}: IndexCardsProps) {
31
30
  if (item.kind !== 'MdxPage') return null
32
31
  return (
33
32
  <Grid.Column span={{medium: 6}} key={`cell-${item.route}`}>
34
- <Card href={`${basePath}${item.route}`} 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>
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>
40
41
  </Grid.Column>
41
42
  )
42
43
  })}
@@ -1,4 +1,5 @@
1
1
  import React from 'react'
2
+ import NextLink from 'next/link'
2
3
  import {Box, IconButton, Link, ThemeProvider} from '@primer/react'
3
4
  import {XIcon} from '@primer/octicons-react'
4
5
 
@@ -34,7 +35,7 @@ export function NavDrawer({isOpen, onDismiss, navItems}: NavDrawerProps) {
34
35
  }}
35
36
  >
36
37
  <Box sx={{py: 20, pl: 4, pr: 3, alignItems: 'center', justifyContent: 'space-between', display: 'flex'}}>
37
- <Link href="https://primer.style" sx={{fontWeight: 'bold', color: 'inherit'}}>
38
+ <Link as={NextLink} href="https://primer.style" sx={{fontWeight: 'bold', color: 'inherit'}}>
38
39
  Explore
39
40
  </Link>
40
41
  <IconButton icon={XIcon} aria-label="Close" onClick={onDismiss} variant="invisible" />
@@ -1,9 +1,12 @@
1
1
  import React, {useMemo} from 'react'
2
+ import NextLink from 'next/link'
2
3
  import Head from 'next/head'
3
4
  import type {Folder, MdxFile, NextraThemeLayoutProps} from 'nextra'
5
+ import {MDXProvider} from 'nextra/mdx'
4
6
  import {useFSRoute} from 'nextra/hooks'
5
7
  import {PencilIcon} from '@primer/octicons-react'
6
8
  import {BaseStyles, Box as PRCBox, Breadcrumbs, PageLayout, ThemeProvider} from '@primer/react'
9
+
7
10
  import {
8
11
  Animate,
9
12
  AnimationProvider,
@@ -31,12 +34,14 @@ import {TableOfContents} from '../table-of-contents/TableOfContents'
31
34
  import bodyStyles from '../../../css/prose.module.css'
32
35
  import {IndexCards} from '../index-cards/IndexCards'
33
36
  import {useColorMode} from '../../context/color-modes/useColorMode'
37
+ import {getComponents} from '../../mdx-components/mdx-components'
38
+ import {SkipToMainContent} from '../skip-to-main-content/SkipToMainContent'
34
39
 
35
40
  const {publicRuntimeConfig} = getConfig()
36
41
 
37
42
  export function Theme({children, pageOpts}: NextraThemeLayoutProps) {
38
43
  const {title, frontMatter, headings, filePath, pageMap, route} = pageOpts
39
- const {locale = 'en-US', defaultLocale, basePath} = useRouter()
44
+ const {locale = 'en-US', defaultLocale} = useRouter()
40
45
  const fsPath = useFSRoute()
41
46
  const {colorMode} = useColorMode()
42
47
  const {activePath, topLevelNavbarItems, docsDirectories, flatDocsDirectories} = useMemo(
@@ -77,6 +82,7 @@ export function Theme({children, pageOpts}: NextraThemeLayoutProps) {
77
82
  zIndex: 99,
78
83
  }}
79
84
  >
85
+ <SkipToMainContent href="#main">Skip to main content</SkipToMainContent>
80
86
  <Header
81
87
  pageMap={pageMap}
82
88
  docsDirectories={docsDirectories}
@@ -86,7 +92,7 @@ export function Theme({children, pageOpts}: NextraThemeLayoutProps) {
86
92
  </PRCBox>
87
93
  <PageLayout rowGap="none" columnGap="none" padding="none" containerWidth="full">
88
94
  <PageLayout.Content padding="normal">
89
- <main>
95
+ <main id="main">
90
96
  <PRCBox sx={!isHomePage && {display: 'flex', maxWidth: 1600, margin: '0 auto'}}>
91
97
  <PRCBox sx={!isHomePage && {maxWidth: 800, width: '100%', margin: '0 auto'}}>
92
98
  <Stack direction="vertical" padding="none" gap="spacious">
@@ -96,7 +102,8 @@ export function Theme({children, pageOpts}: NextraThemeLayoutProps) {
96
102
  <Breadcrumbs>
97
103
  {siteTitle && (
98
104
  <Breadcrumbs.Item
99
- href={basePath || '/'}
105
+ as={NextLink}
106
+ href="/"
100
107
  sx={{
101
108
  color: 'var(--brand-InlineLink-color-rest)',
102
109
  }}
@@ -107,8 +114,9 @@ export function Theme({children, pageOpts}: NextraThemeLayoutProps) {
107
114
  {activePath.map((item, index) => {
108
115
  return (
109
116
  <Breadcrumbs.Item
117
+ as={NextLink}
110
118
  key={item.name}
111
- href={`${basePath}${item.route}`}
119
+ href={item.route}
112
120
  selected={index === activePath.length - 1}
113
121
  sx={{
114
122
  textTransform: 'capitalize',
@@ -129,16 +137,16 @@ export function Theme({children, pageOpts}: NextraThemeLayoutProps) {
129
137
  {frontMatter.title}
130
138
  </Heading>
131
139
  )}
132
- {frontMatter.image && (
133
- <Box paddingBlockEnd={16}>
134
- <Hero.Image src={frontMatter.image} alt={frontMatter['image-alt']} />
135
- </Box>
136
- )}
137
140
  {frontMatter.description && (
138
141
  <Text as="p" variant="muted" size="300">
139
142
  {frontMatter.description}
140
143
  </Text>
141
144
  )}
145
+ {frontMatter.image && (
146
+ <Box paddingBlockStart={16}>
147
+ <Hero.Image src={frontMatter.image} alt={frontMatter['image-alt']} />
148
+ </Box>
149
+ )}
142
150
  {frontMatter['action-1-text'] && (
143
151
  <Box paddingBlockStart={16}>
144
152
  <ButtonGroup>
@@ -159,7 +167,11 @@ export function Theme({children, pageOpts}: NextraThemeLayoutProps) {
159
167
  </>
160
168
  )}
161
169
  <article className={route !== '/' && !isIndexPage ? bodyStyles.Prose : ''}>
162
- {isIndexPage ? <IndexCards folderData={flatDocsDirectories} route={route} /> : children}
170
+ {isIndexPage ? (
171
+ <IndexCards folderData={flatDocsDirectories} route={route} />
172
+ ) : (
173
+ <MDXProvider components={getComponents()}>{children}</MDXProvider>
174
+ )}
163
175
  </article>
164
176
  <footer>
165
177
  <Box marginBlockStart={64}>
@@ -1,21 +1,12 @@
1
1
  import React, {useMemo} from 'react'
2
+ import NextLink from 'next/link'
2
3
  import {NavList} from '@primer/react'
3
4
  import {Folder, MdxFile, PageMapItem} from 'nextra'
4
5
  import {useRouter} from 'next/router'
5
6
  import getConfig from 'next/config'
6
7
 
7
8
  import styles from './Sidebar.module.css'
8
- import {
9
- BookmarkIcon,
10
- BrowserIcon,
11
- Icon,
12
- ImageIcon,
13
- LinkExternalIcon,
14
- OrganizationIcon,
15
- RepoIcon,
16
- StackIcon,
17
- StarIcon,
18
- } from '@primer/octicons-react'
9
+ import {LinkExternalIcon} from '@primer/octicons-react'
19
10
  import type {ThemeConfig} from '../../../index'
20
11
 
21
12
  type SidebarProps = {
@@ -35,32 +26,8 @@ type DocsItem = (MdxFile | FolderWithoutChildren) & {
35
26
 
36
27
  const {publicRuntimeConfig} = getConfig()
37
28
 
38
- function getOcticonForType(type?: string): Icon | undefined {
39
- if (!type) return undefined
40
-
41
- switch (type) {
42
- case 'repo':
43
- return RepoIcon
44
- case 'org':
45
- return OrganizationIcon
46
- case 'bookmark':
47
- return BookmarkIcon
48
- case 'star':
49
- return StarIcon
50
- case 'browser':
51
- return BrowserIcon
52
- case 'stack':
53
- return StackIcon
54
- case 'img':
55
- return ImageIcon
56
- default:
57
- return StarIcon
58
- }
59
- }
60
-
61
29
  export function Sidebar({pageMap}: SidebarProps) {
62
30
  const router = useRouter()
63
- const basePath = router.basePath
64
31
 
65
32
  const {sidebarLinks}: ThemeConfig = publicRuntimeConfig
66
33
 
@@ -94,7 +61,7 @@ export function Sidebar({pageMap}: SidebarProps) {
94
61
 
95
62
  if (item.kind === 'MdxPage') {
96
63
  return (
97
- <NavList.Item key={item.name} href={`${basePath}${item.route}`} sx={{textTransform: 'capitalize'}}>
64
+ <NavList.Item as={NextLink} key={item.name} href={item.route} sx={{textTransform: 'capitalize'}}>
98
65
  {item.frontMatter?.title ?? item.name}
99
66
  </NavList.Item>
100
67
  )
@@ -109,7 +76,7 @@ export function Sidebar({pageMap}: SidebarProps) {
109
76
  const shouldShowTabs = indexPage.frontMatter?.['show-tabs'] ?? false
110
77
  if (shouldShowTabs) {
111
78
  return (
112
- <NavList.Item key={indexPage.name} href={`${basePath}${indexPage.route}`}>
79
+ <NavList.Item as={NextLink} key={indexPage.name} href={indexPage.route}>
113
80
  {(indexPage as MdxFile).frontMatter?.title || item.name}
114
81
  </NavList.Item>
115
82
  )
@@ -129,8 +96,9 @@ export function Sidebar({pageMap}: SidebarProps) {
129
96
  if (child.kind === 'MdxPage') {
130
97
  return (
131
98
  <NavList.Item
99
+ as={NextLink}
132
100
  key={child.name}
133
- href={`${basePath}${child.route}`}
101
+ href={child.route}
134
102
  aria-current={child.route === router.pathname ? 'page' : undefined}
135
103
  >
136
104
  {(child as MdxFile).frontMatter?.title || item.name}
@@ -144,8 +112,9 @@ export function Sidebar({pageMap}: SidebarProps) {
144
112
  )
145
113
  return (
146
114
  <NavList.Item
147
- key={`${(landingPageItem as MdxFile).route}`}
148
- href={`${basePath}${(landingPageItem as MdxFile).route}`}
115
+ as={NextLink}
116
+ key={(landingPageItem as MdxFile).route}
117
+ href={(landingPageItem as MdxFile).route}
149
118
  sx={{textTransform: 'capitalize'}}
150
119
  aria-current={(landingPageItem as MdxFile).route === router.pathname ? 'page' : undefined}
151
120
  >
@@ -165,13 +134,15 @@ export function Sidebar({pageMap}: SidebarProps) {
165
134
  {sidebarLinks && sidebarLinks.length > 0 && (
166
135
  <NavList.Group title="" sx={{mb: 24}}>
167
136
  {sidebarLinks.map(link => {
168
- const {leadingIcon} = link
169
137
  const isExternalUrl = link.href.startsWith('http')
170
- const LeadingIcon = getOcticonForType(leadingIcon)
171
138
 
172
139
  return (
173
- <NavList.Item key={link.title} href={link.href} target={isExternalUrl ? '_blank' : undefined}>
174
- <NavList.LeadingVisual>{LeadingIcon && <LeadingIcon />}</NavList.LeadingVisual>
140
+ <NavList.Item
141
+ as={NextLink}
142
+ key={link.title}
143
+ href={link.href}
144
+ target={isExternalUrl ? '_blank' : undefined}
145
+ >
175
146
  {link.title}
176
147
  {isExternalUrl && (
177
148
  <NavList.TrailingVisual>
@@ -0,0 +1,17 @@
1
+ .SkipToMainContent {
2
+ position: absolute;
3
+ top: 0;
4
+ left: 0;
5
+ z-index: 999;
6
+ padding: var(--base-size-16);
7
+ background-color: var(--base-color-scale-blue-5);
8
+ color: var(--base-color-scale-white-0);
9
+ }
10
+ .SkipToMainContent:not(:focus) {
11
+ clip: rect(1px, 1px, 1px, 1px);
12
+ clip-path: inset(50%);
13
+ height: 1px;
14
+ width: 1px;
15
+ margin: -1px;
16
+ padding: 0px;
17
+ }
@@ -0,0 +1,14 @@
1
+ import React, {HTMLAttributes, PropsWithChildren} from 'react'
2
+ import styles from './SkipToMainContent.module.css'
3
+
4
+ type SkipToMainContentProps = {
5
+ href: string
6
+ } & HTMLAttributes<HTMLAnchorElement>
7
+
8
+ export function SkipToMainContent({children, href, ...rest}: PropsWithChildren<SkipToMainContentProps>) {
9
+ return (
10
+ <a href={href} className={styles.SkipToMainContent} {...rest}>
11
+ {children}
12
+ </a>
13
+ )
14
+ }
@@ -1,4 +1,4 @@
1
- import React, {useEffect} from 'react'
1
+ import React, {useEffect, useMemo} from 'react'
2
2
  import {NavList} from '@primer/react'
3
3
  import {Text} from '@primer/react-brand'
4
4
  import {Heading as HeadingType} from 'nextra'
@@ -10,6 +10,8 @@ type TableOfContentsProps = {
10
10
  }
11
11
 
12
12
  export function TableOfContents({headings}: TableOfContentsProps) {
13
+ const depth2Headings = useMemo(() => headings.filter(heading => heading.depth === 2), [headings])
14
+
13
15
  useEffect(() => {
14
16
  const observer = new IntersectionObserver(
15
17
  entries => {
@@ -40,7 +42,7 @@ export function TableOfContents({headings}: TableOfContentsProps) {
40
42
  },
41
43
  )
42
44
 
43
- for (const heading of headings) {
45
+ for (const heading of depth2Headings) {
44
46
  const el = document.getElementById(heading.id)
45
47
  if (el) {
46
48
  observer.observe(el)
@@ -50,7 +52,7 @@ export function TableOfContents({headings}: TableOfContentsProps) {
50
52
  return () => {
51
53
  observer.disconnect()
52
54
  }
53
- }, [headings])
55
+ }, [depth2Headings])
54
56
 
55
57
  return (
56
58
  <aside className={styles.wrapper}>
@@ -58,7 +60,7 @@ export function TableOfContents({headings}: TableOfContentsProps) {
58
60
  On this page
59
61
  </Text>
60
62
  <NavList>
61
- {headings.map(heading => (
63
+ {depth2Headings.map(heading => (
62
64
  <NavList.Item
63
65
  className={styles.item}
64
66
  key={heading.id}
@@ -0,0 +1,14 @@
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
+ }
@@ -0,0 +1,43 @@
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
+ }
@@ -129,15 +129,12 @@
129
129
  letter-spacing: var(--brand-heading-letterSpacing-300);
130
130
  }
131
131
 
132
- .Prose :is(h1, h2, h3) {
133
- --spacing: var(--base-size-64);
134
- }
135
-
136
132
  .Prose :is(h1) {
133
+ --spacing: var(--base-size-64);
137
134
  margin-block-end: var(--spacing);
138
135
  }
139
136
 
140
- .Prose :is(h1, h2, h3) + * {
137
+ .Prose :is(h2, h3) + * {
141
138
  --spacing: var(--base-size-40);
142
139
  }
143
140
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primer/doctocat-nextjs",
3
- "version": "0.0.2-rc.cdbd6bf",
3
+ "version": "0.0.2",
4
4
  "description": "A Next.js theme for building Primer documentation sites",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/types.ts CHANGED
@@ -3,6 +3,5 @@ export type ThemeConfig = {
3
3
  sidebarLinks?: {
4
4
  title: string
5
5
  href: string
6
- leadingIcon?: 'repo' | 'org' | 'bookmark' | 'star' | 'img' | 'browser' | 'stack'
7
6
  }[]
8
7
  }