@primer/doctocat-nextjs 0.1.0 → 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,29 +1,32 @@
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} from 'nextra'
5
- import {useRouter} from 'next/router'
6
- import getConfig from 'next/config'
4
+ import {Folder, MdxFile, PageMapItem} from 'nextra'
7
5
 
8
6
  import styles from './Sidebar.module.css'
9
7
  import {LinkExternalIcon} from '@primer/octicons-react'
10
- import type {DocsItem, ThemeConfig} from '../../../index'
8
+ import type {DocsItem, ExtendedPageItem} from '../../../index'
11
9
  import {hasChildren} from '../../../helpers/hasChildren'
10
+ import {usePathname} from 'next/navigation'
12
11
 
13
12
  type SidebarProps = {
14
- pageMap: DocsItem[]
13
+ pageMap: PageMapItem[]
15
14
  }
16
15
 
17
- const {publicRuntimeConfig} = getConfig()
18
-
19
- const hasShowTabs = (child: DocsItem): boolean => {
16
+ const hasShowTabs = (child: ExtendedPageItem): boolean => {
20
17
  return child.name === 'index' && (child as MdxFile).frontMatter?.['show-tabs'] === true
21
18
  }
22
19
 
23
- export function Sidebar({pageMap}: SidebarProps) {
24
- const router = useRouter()
20
+ export function Sidebar({pageMap: pageMapIn}: SidebarProps) {
21
+ const pageMap = pageMapIn as ExtendedPageItem[]
22
+
23
+ const pathname = usePathname()
25
24
 
26
- const {sidebarLinks}: ThemeConfig = publicRuntimeConfig
25
+ const externalLinks = pageMap.filter(page => {
26
+ if (page.href && page.href.startsWith('http')) {
27
+ return page
28
+ }
29
+ })
27
30
 
28
31
  /**
29
32
  * Sorts the incoming data so that folders with a menu-position frontmatter value
@@ -33,11 +36,11 @@ export function Sidebar({pageMap}: SidebarProps) {
33
36
  const reorderedPageMap = useMemo(
34
37
  () =>
35
38
  [...pageMap].sort((a, b) => {
36
- if (hasChildren(a) && a.children) {
37
- const aIndex = a.children.find(child => child.name === 'index' && child.type === 'doc')
39
+ if (hasChildren(a)) {
40
+ const aIndex = a.children.find(child => (child as MdxFile).name === 'index')
38
41
  const aPosition = (aIndex as MdxFile | undefined)?.frontMatter?.['menu-position'] ?? Infinity
39
- if (hasChildren(b) && b.children) {
40
- const bIndex = b.children.find(child => child.name === 'index' && child.type === 'doc')
42
+ if (hasChildren(b)) {
43
+ const bIndex = b.children.find(child => (child as MdxFile).name === 'index')
41
44
  const bPosition = (bIndex as MdxFile | undefined)?.frontMatter?.['menu-position'] ?? Infinity
42
45
  return aPosition - bPosition
43
46
  }
@@ -51,95 +54,73 @@ export function Sidebar({pageMap}: SidebarProps) {
51
54
  <div className={styles.Sidebar}>
52
55
  <NavList className={styles.NavList} aria-label="Menu links">
53
56
  {reorderedPageMap.map(item => {
54
- if (item.type === 'doc' && item.route === '/') return null
57
+ if (item.hasOwnProperty('data')) return null
58
+
59
+ if (!hasChildren(item)) return null
55
60
 
56
- if (!hasChildren(item) && item.type === 'doc') {
61
+ const indexPage = (item as Folder).children.find(child => (child as MdxFile).name === 'index') as MdxFile
62
+ const subNavName = indexPage.frontMatter?.title ?? ''
63
+ const shouldShowTabs = indexPage.frontMatter?.['show-tabs'] ?? false
64
+ if (shouldShowTabs) {
57
65
  return (
58
- <NavList.Item as={NextLink} key={item.name} href={item.route} sx={{textTransform: 'capitalize'}}>
59
- {(item as MdxFile).frontMatter?.title ?? item.name}
66
+ <NavList.Item as={NextLink} key={indexPage.name} href={indexPage.route}>
67
+ {(indexPage as MdxFile).frontMatter?.title || item.name}
60
68
  </NavList.Item>
61
69
  )
62
70
  }
63
71
 
64
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
65
- if (hasChildren(item)) {
66
- const indexPage = (item as Folder).children.find(child => (child as MdxFile).name === 'index') as MdxFile
67
- const subNavName = indexPage.frontMatter?.title ?? ''
68
- const shouldShowTabs = indexPage.frontMatter?.['show-tabs'] ?? false
69
- if (shouldShowTabs) {
70
- return (
71
- <NavList.Item as={NextLink} key={indexPage.name} href={indexPage.route}>
72
- {(indexPage as MdxFile).frontMatter?.title || item.name}
73
- </NavList.Item>
74
- )
75
- }
72
+ return (
73
+ <NavList.Group title={subNavName} key={item.name} sx={{mb: 24}}>
74
+ {item.children
75
+ .sort((a, b) => ((a as MdxFile).name === 'index' ? -1 : (b as MdxFile).name === 'index' ? 1 : 0)) // puts index page first
76
+ // only show index page if it has show-tabs
77
+ .filter(child => (child as MdxFile).name !== 'index' || hasShowTabs(child as ExtendedPageItem))
78
+ .map(child => {
79
+ if (!hasChildren(child)) {
80
+ const {name, route} = child as MdxFile
76
81
 
77
- return (
78
- <NavList.Group title={subNavName} key={item.name} sx={{mb: 24}}>
79
- {item.children &&
80
- item.children
81
- .sort((a, b) => (a.name === 'index' ? -1 : b.name === 'index' ? 1 : 0)) // puts index page first
82
- // only show index page if it has show-tabs
83
- .filter(child => child.name !== 'index' || hasShowTabs(child))
84
- .map((child: DocsItem) => {
85
- if (child.type === 'doc') {
86
- return (
87
- <NavList.Item
88
- as={NextLink}
89
- key={child.name}
90
- href={child.route}
91
- sx={{textTransform: 'capitalize'}}
92
- aria-current={child.route === router.pathname ? 'page' : undefined}
93
- >
94
- {child.title}
95
- </NavList.Item>
96
- )
97
- }
82
+ return (
83
+ <NavList.Item
84
+ as={NextLink}
85
+ key={name}
86
+ href={route}
87
+ aria-current={route === pathname ? 'page' : undefined}
88
+ >
89
+ {(child as MdxFile).frontMatter?.title || name}
90
+ </NavList.Item>
91
+ )
92
+ }
98
93
 
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',
103
- )
104
- return (
105
- <NavList.Item
106
- as={NextLink}
107
- key={(landingPageItem as MdxFile).route}
108
- href={(landingPageItem as MdxFile).route}
109
- sx={{textTransform: 'capitalize'}}
110
- aria-current={(landingPageItem as MdxFile).route === router.pathname ? 'page' : undefined}
111
- >
112
- {(landingPageItem as MdxFile).frontMatter?.title || item.name}
113
- </NavList.Item>
114
- )
115
- }
94
+ if (hasChildren(child)) {
95
+ const landingPageItem = (child as Folder).children.find(
96
+ innerChild => (innerChild as DocsItem).name === 'index',
97
+ )
116
98
 
117
- return null
118
- })}
119
- </NavList.Group>
120
- )
121
- }
122
-
123
- return null
99
+ return (
100
+ <NavList.Item
101
+ as={NextLink}
102
+ key={(landingPageItem as MdxFile).route}
103
+ href={(landingPageItem as MdxFile).route}
104
+ sx={{textTransform: 'capitalize'}}
105
+ aria-current={(landingPageItem as MdxFile).route === pathname ? 'page' : undefined}
106
+ >
107
+ {(landingPageItem as MdxFile).frontMatter?.title || item.name}
108
+ </NavList.Item>
109
+ )
110
+ }
111
+ })}
112
+ </NavList.Group>
113
+ )
124
114
  })}
125
- {sidebarLinks && sidebarLinks.length > 0 && (
115
+ {externalLinks.length > 0 && (
126
116
  <NavList.Group title="" sx={{mb: 24}}>
127
- {sidebarLinks.map(link => {
128
- const isExternalUrl = link.href.startsWith('http')
129
-
117
+ {externalLinks.map(link => {
130
118
  return (
131
- <NavList.Item
132
- as={NextLink}
133
- key={link.title}
134
- href={link.href}
135
- target={isExternalUrl ? '_blank' : undefined}
136
- >
119
+ <NavList.Item as={NextLink} key={link.title} href={link.href}>
137
120
  {link.title}
138
- {isExternalUrl && (
139
- <NavList.TrailingVisual>
140
- <LinkExternalIcon />
141
- </NavList.TrailingVisual>
142
- )}
121
+ <NavList.TrailingVisual>
122
+ <LinkExternalIcon />
123
+ </NavList.TrailingVisual>
143
124
  </NavList.Item>
144
125
  )
145
126
  })}
@@ -1,3 +1,8 @@
1
+ .wrapper {
2
+ position: sticky;
3
+ top: 0;
4
+ }
5
+
1
6
  .heading {
2
7
  font-size: var(--base-size-12);
3
8
  padding-inline-start: var(--base-size-16);
@@ -1,7 +1,9 @@
1
+ 'use client'
1
2
  import React, {useEffect, useMemo} from 'react'
2
3
  import {NavList} from '@primer/react'
3
4
  import {Text} from '@primer/react-brand'
4
5
  import {Heading as HeadingType} from 'nextra'
6
+ import clsx from 'clsx'
5
7
 
6
8
  import styles from './TableOfContents.module.css'
7
9
 
@@ -57,14 +59,14 @@ export function TableOfContents({headings}: TableOfContentsProps) {
57
59
  if (!depth2Headings.length) return null
58
60
 
59
61
  return (
60
- <aside className={styles.wrapper}>
62
+ <aside className={clsx(styles.wrapper, 'exclude-from-prose', 'custom-component')}>
61
63
  <Text as="p" size="100" variant="muted" weight="normal" className={styles.heading}>
62
64
  On this page
63
65
  </Text>
64
- <NavList aria-label="Table of contents">
66
+ <NavList aria-label="Table of contents" className="custom-component">
65
67
  {depth2Headings.map(heading => (
66
68
  <NavList.Item
67
- className={styles.item}
69
+ className={clsx(styles.item, 'custom-component')}
68
70
  key={heading.id}
69
71
  id={`toc-heading-${heading.id}`}
70
72
  href={`#${heading.id}`}
@@ -1,6 +1,7 @@
1
- import React from 'react'
1
+ import React, {useEffect, useState} from 'react'
2
2
  import {UnderlineNav as PrimerUnderlineNav} from '@primer/react'
3
- import {useRouter} from 'next/router'
3
+ import {usePathname} from 'next/navigation'
4
+
4
5
  import Link from 'next/link'
5
6
  import {MdxFile} from 'nextra'
6
7
 
@@ -9,8 +10,10 @@ type UnderlineNavProps = {
9
10
  }
10
11
 
11
12
  export function UnderlineNav({tabData}: UnderlineNavProps) {
12
- const router = useRouter()
13
- const currentRoute = router.pathname
13
+ const [isClient, setIsClient] = useState(false)
14
+ const pathname = usePathname()
15
+
16
+ const currentRoute = pathname
14
17
 
15
18
  // Reorders tabData so the tab with name === index is always first
16
19
  if (tabData.length > 1) {
@@ -21,6 +24,12 @@ export function UnderlineNav({tabData}: UnderlineNavProps) {
21
24
  }
22
25
  }
23
26
 
27
+ useEffect(() => {
28
+ setIsClient(true)
29
+ }, [])
30
+
31
+ if (!isClient) return null
32
+
24
33
  return (
25
34
  <PrimerUnderlineNav aria-label="Sibling pages">
26
35
  {tabData.length > 1 &&
@@ -0,0 +1,3 @@
1
+ 'use client'
2
+
3
+ export {Box, Button, Heading, Text, Label, Stack} from '@primer/react-brand'
@@ -244,6 +244,14 @@
244
244
  overflow: hidden;
245
245
  }
246
246
 
247
+ .Prose video {
248
+ --spacing: var(--brand-Prose-img-spacing);
249
+ display: block;
250
+ max-width: 100%;
251
+ height: auto;
252
+ margin-block-start: var(--spacing);
253
+ }
254
+
247
255
  /* ---------------------------------------------------------- */
248
256
  /* 9. Block elements */
249
257
  /* ---------------------------------------------------------- */
@@ -1,29 +1,23 @@
1
1
  import nextra from 'nextra'
2
- import transpiler from 'next-transpile-modules'
3
2
 
4
3
  const withNextra = nextra({
5
- theme: '@primer/doctocat-nextjs',
6
4
  staticImage: true,
7
5
  })
8
6
 
9
- const withTM = transpiler(['@primer/doctocat-nextjs'])
10
-
11
7
  /*
12
8
  * Wrapper for next config, that allows us to do some shared configuration
13
9
  * Provides a default config, but allows overriding it
14
- * Uses next-transpile-modules to allow Next.js webpack to transpile the theme
10
+ * Relies on `transpilePackages: ['@primer/doctocat-nextjs'],` being set in the next.config
15
11
  */
16
12
  function withDoctocat(config = {}) {
17
- return withTM({
13
+ return {
18
14
  ...withNextra(),
19
15
  images: {
20
16
  unoptimized: true,
21
17
  },
22
- publicRuntimeConfig: {
23
- siteTitle: config.publicRuntimeConfig.siteTitle || 'Doctocat', // to add a custom site title
24
- },
18
+
25
19
  ...config,
26
- })
20
+ }
27
21
  }
28
22
 
29
23
  export default withDoctocat
@@ -1,5 +1,5 @@
1
1
  import {PageMapItem, Folder} from 'nextra'
2
- import {DocsItem} from '../types'
3
2
 
4
- export const hasChildren = (item: DocsItem | PageMapItem): boolean =>
5
- 'children' in item && Array.isArray((item as Folder).children) && (item as Folder).children.length > 0
3
+ export function hasChildren(item: PageMapItem): item is Folder {
4
+ return 'children' in item && Array.isArray(item.children)
5
+ }
package/index.tsx CHANGED
@@ -2,15 +2,11 @@
2
2
  * Root layout / theme (required)
3
3
  */
4
4
  import Theme from './components/layout/root-layout/index'
5
-
5
+ export {generateStaticParamsFor, importPage} from 'nextra/pages'
6
+ export {getPageMap} from 'nextra/page-map'
6
7
  export default Theme
7
8
 
8
9
  /**
9
10
  * Types
10
11
  */
11
12
  export * from './types'
12
- /**
13
- * Components
14
- */
15
- export {DoDontContainer, Do, Dont} from './components/content/dos-and-donts/DosAndDonts'
16
- export {Caption} from './components/content/caption/Caption'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primer/doctocat-nextjs",
3
- "version": "0.1.0",
3
+ "version": "0.2.0-rc.10bf384",
4
4
  "description": "A Next.js theme for building Primer documentation sites",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -17,16 +17,17 @@
17
17
  "react-dom": "18.3.1"
18
18
  },
19
19
  "dependencies": {
20
- "@primer/octicons-react": "19.14.0",
21
- "@primer/react": "36.27.0",
20
+ "@next/mdx": "^15.1.6",
21
+ "@primer/octicons-react": "19.15.0",
22
+ "@primer/react": "^37.11.0",
22
23
  "@primer/react-brand": "0.46.0",
23
- "next-transpile-modules": "^10.0.1",
24
- "framer-motion": "12.0.1",
24
+ "@types/lodash.debounce": "^4.0.9",
25
+ "framer-motion": "12.4.0",
25
26
  "lodash.debounce": "^4.0.8",
26
- "react-focus-on": "3.9.4",
27
- "nextra": "3.3.1",
27
+ "nextra": "^4.2.1",
28
28
  "react": "18.3.1",
29
- "react-dom": "18.3.1"
29
+ "react-dom": "18.3.1",
30
+ "react-focus-on": "3.9.4"
30
31
  },
31
32
  "devDependencies": {
32
33
  "@github/prettier-config": "^0.0.6",
@@ -35,7 +36,7 @@
35
36
  "@types/react-dom": "18.3.1",
36
37
  "clsx": "2.1.1",
37
38
  "next": "15.1.6",
38
- "styled-components": "6.1.13",
39
+ "styled-components": "5.3.11",
39
40
  "typescript": "5.7.3"
40
41
  },
41
42
  "overrides": {
package/tsconfig.json CHANGED
@@ -5,13 +5,15 @@
5
5
  "declaration": false,
6
6
  "noEmit": true,
7
7
  "esModuleInterop": true,
8
- "strict": false,
8
+ "strict": true,
9
9
  "skipLibCheck": true,
10
10
  "allowJs": true,
11
11
  "jsx": "react-jsx",
12
- "moduleResolution": "node",
12
+ "moduleResolution": "bundler",
13
13
  "lib": ["ESNext", "DOM", "DOM.Iterable"],
14
14
  "resolveJsonModule": true,
15
- "strictNullChecks": true
15
+ "strictNullChecks": true,
16
+ "strictFunctionTypes": true,
17
+ "noImplicitAny": true
16
18
  }
17
19
  }
package/types.ts CHANGED
@@ -1,4 +1,4 @@
1
- import {Folder, MdxFile} from 'nextra'
1
+ import {Folder, PageMapItem, MdxFile} from 'nextra'
2
2
 
3
3
  export type ThemeConfig = {
4
4
  docsRepositoryBase: string
@@ -8,6 +8,12 @@ export type ThemeConfig = {
8
8
  }[]
9
9
  }
10
10
 
11
+ export type ExtendedPageItem = PageMapItem & {
12
+ name: string
13
+ title: string
14
+ href: string
15
+ }
16
+
11
17
  export type FolderWithoutChildren = Omit<Folder, 'children'>
12
18
 
13
19
  export type DocsItem = MdxFile & {
@@ -18,3 +24,16 @@ export type DocsItem = MdxFile & {
18
24
  withIndexPage?: boolean
19
25
  isUnderCurrentDocsTree?: boolean
20
26
  }
27
+
28
+ export type FrontMatter = {
29
+ description?: string
30
+ filePath?: string
31
+ keywords?: string[]
32
+ related?: {
33
+ title: string
34
+ href: string
35
+ }[]
36
+ timestamp?: number
37
+ title?: string
38
+ [key: string]: unknown
39
+ }