@primer/doctocat-nextjs 0.0.0-20250716161640 → 0.0.0-20250904115752
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,16 +1,28 @@
|
|
|
1
1
|
# @primer/doctocat-nextjs
|
|
2
2
|
|
|
3
|
-
## 0.0.0-
|
|
3
|
+
## 0.0.0-20250904115752
|
|
4
4
|
|
|
5
5
|
### Patch Changes
|
|
6
6
|
|
|
7
7
|
- Fake entry to force publishing
|
|
8
8
|
|
|
9
|
-
## 0.0.0-
|
|
9
|
+
## 0.0.0-20250904115750
|
|
10
10
|
|
|
11
11
|
### Minor Changes
|
|
12
12
|
|
|
13
|
-
- [
|
|
13
|
+
- [`84ff648`](https://github.com/primer/doctocat-nextjs/commit/84ff648a8248712d61e13a7e6432ab2ab8e07435) Thanks [@rezrah](https://github.com/rezrah)! - Updated Next.js compatibility to v15.5.x, Nextra to v4, and fix React code block rendering
|
|
14
|
+
|
|
15
|
+
- **Next.js v15.5.2**: Upgraded to latest stable version across all workspaces
|
|
16
|
+
- **Nextra v4 compatibility**: Updated type definitions for `ReactNode` titles
|
|
17
|
+
- **Fixed code block rendering**: Added client-side rendering for interactive code examples to handle React lazy components properly
|
|
18
|
+
|
|
19
|
+
Next.js v15.4+ changed how lazy components render on the server, breaking interactive code blocks. This update uses client-side code snippet extraction to convert lazy components to clean code strings for the live editor, preventing hydration mismatches and preserving existing behavior.
|
|
20
|
+
|
|
21
|
+
## 0.6.0
|
|
22
|
+
|
|
23
|
+
### Minor Changes
|
|
24
|
+
|
|
25
|
+
- [#63](https://github.com/primer/doctocat-nextjs/pull/63) [`9a090ee`](https://github.com/primer/doctocat-nextjs/commit/9a090eec47c64dc46f8eac14ec00548d421e8019) Thanks [@rezrah](https://github.com/rezrah)! - Moves `@primer/react-brand` to a peer-dependency.
|
|
14
26
|
|
|
15
27
|
This change prevents a mismatch of versions between Doctocat and the project using it. Going forward, the latter will be preferred.
|
|
16
28
|
|
|
@@ -1,8 +1,31 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
import React, {PropsWithChildren} from 'react'
|
|
3
|
+
import dynamic from 'next/dynamic'
|
|
4
|
+
import {renderToStaticMarkup} from 'react-dom/server'
|
|
3
5
|
|
|
4
|
-
import {ReactCodeBlock} from './ReactCodeBlock'
|
|
5
6
|
import {Pre} from 'nextra/components'
|
|
7
|
+
import {Box, Stack, Text} from '@primer/react-brand'
|
|
8
|
+
import {Spinner} from '@primer/react'
|
|
9
|
+
|
|
10
|
+
// We need to load this on the client to avoid a hydration mismatch.
|
|
11
|
+
const ReactCodeBlock = dynamic(
|
|
12
|
+
async () => {
|
|
13
|
+
const module = await import('./ReactCodeBlock')
|
|
14
|
+
return {default: module.ReactCodeBlock}
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
ssr: false,
|
|
18
|
+
loading: () => (
|
|
19
|
+
<Box borderStyle="solid" borderWidth="thin" borderColor="default" borderRadius="medium" marginBlockEnd="normal">
|
|
20
|
+
<Stack style={{minHeight: 340}} alignItems="center" justifyContent="center">
|
|
21
|
+
<Text>
|
|
22
|
+
<Spinner />
|
|
23
|
+
</Text>
|
|
24
|
+
</Stack>
|
|
25
|
+
</Box>
|
|
26
|
+
),
|
|
27
|
+
},
|
|
28
|
+
)
|
|
6
29
|
|
|
7
30
|
type CodeBlockProps = {
|
|
8
31
|
'data-language': string
|
|
@@ -11,7 +34,29 @@ type CodeBlockProps = {
|
|
|
11
34
|
|
|
12
35
|
export function CodeBlock(props: CodeBlockProps) {
|
|
13
36
|
if (['tsx', 'jsx'].includes(props['data-language'])) {
|
|
14
|
-
|
|
37
|
+
// Next.js v15.4+ will lazy render components on the server, which prevents us from
|
|
38
|
+
// sending usable React nodes to the ReactCodeBlock component.
|
|
39
|
+
// Workaround is to convert the code snippets to string on the client and pass to react-live.
|
|
40
|
+
try {
|
|
41
|
+
const childrenAsString = renderToStaticMarkup(<>{props.children}</>)
|
|
42
|
+
|
|
43
|
+
const textContent = childrenAsString.replace(/<[^>]*>/g, '')
|
|
44
|
+
|
|
45
|
+
// Restore escaped chars
|
|
46
|
+
const decodedText = textContent
|
|
47
|
+
.replace(/</g, '<')
|
|
48
|
+
.replace(/>/g, '>')
|
|
49
|
+
.replace(/"/g, '"')
|
|
50
|
+
.replace(/'/g, "'")
|
|
51
|
+
.replace(/&/g, '&')
|
|
52
|
+
|
|
53
|
+
return <ReactCodeBlock {...props} code={decodedText} />
|
|
54
|
+
} catch (error) {
|
|
55
|
+
// eslint-disable-next-line no-console
|
|
56
|
+
console.log('Error extracting code snippet. Forwarding children directly:', error)
|
|
57
|
+
// Fallback to original children-based approach
|
|
58
|
+
return <ReactCodeBlock {...props} />
|
|
59
|
+
}
|
|
15
60
|
}
|
|
16
61
|
|
|
17
62
|
return <Pre>{props.children}</Pre>
|
|
@@ -18,6 +18,7 @@ const COLLAPSE_HEIGHT = 400 // TODO: Hoist this to config to make user customiza
|
|
|
18
18
|
type ReactCodeBlockProps = {
|
|
19
19
|
'data-language': string
|
|
20
20
|
'data-filename'?: string
|
|
21
|
+
code?: string
|
|
21
22
|
jsxScope: Record<string, unknown>
|
|
22
23
|
} & PropsWithChildren<HTMLElement>
|
|
23
24
|
|
|
@@ -34,7 +35,7 @@ export function ReactCodeBlock(props: ReactCodeBlockProps) {
|
|
|
34
35
|
const uniqueId = useId()
|
|
35
36
|
const {colorMode, setColorMode} = useColorMode()
|
|
36
37
|
const {basePath} = useConfig()
|
|
37
|
-
const initialCode = getCodeFromChildren(props.children)
|
|
38
|
+
const initialCode = props.code || getCodeFromChildren(props.children)
|
|
38
39
|
const [code, setCode] = useState(initialCode)
|
|
39
40
|
const rootRef = useRef<HTMLDivElement>(null)
|
|
40
41
|
const [isCodePaneCollapsed, setIsCodePaneCollapsed] = useState<boolean | null>(null)
|
|
@@ -28,7 +28,10 @@ export const getRelatedPages: GetRelatedPages = (route, activeMetadata, flatDocs
|
|
|
28
28
|
const intersection = pageKeywords.filter(keyword => currentPageKeywords.includes(keyword))
|
|
29
29
|
|
|
30
30
|
if (intersection.length) {
|
|
31
|
-
matches.push(
|
|
31
|
+
matches.push({
|
|
32
|
+
...page,
|
|
33
|
+
title: titleToString(page.title), // Convert ReactNode to string
|
|
34
|
+
})
|
|
32
35
|
}
|
|
33
36
|
}
|
|
34
37
|
|
|
@@ -36,14 +39,12 @@ export const getRelatedPages: GetRelatedPages = (route, activeMetadata, flatDocs
|
|
|
36
39
|
for (const link of relatedLinks) {
|
|
37
40
|
if (!link.title || !link.href || link.href === route) continue
|
|
38
41
|
if (link.href.startsWith('/')) {
|
|
39
|
-
const page = flatDocsDirectories.find(localPage => localPage.route === link.href)
|
|
40
|
-
| RelatedContentLink
|
|
41
|
-
| undefined
|
|
42
|
+
const page = flatDocsDirectories.find(localPage => localPage.route === link.href)
|
|
42
43
|
|
|
43
44
|
if (page) {
|
|
44
45
|
const entry = {
|
|
45
46
|
...page,
|
|
46
|
-
title: link.title || page.title,
|
|
47
|
+
title: link.title || titleToString(page.title),
|
|
47
48
|
route: link.href || page.route,
|
|
48
49
|
}
|
|
49
50
|
matches.push(entry)
|
|
@@ -55,3 +56,9 @@ export const getRelatedPages: GetRelatedPages = (route, activeMetadata, flatDocs
|
|
|
55
56
|
|
|
56
57
|
return matches
|
|
57
58
|
}
|
|
59
|
+
|
|
60
|
+
function titleToString(title: React.ReactNode): string {
|
|
61
|
+
if (typeof title === 'string') return title
|
|
62
|
+
if (typeof title === 'number') return title.toString()
|
|
63
|
+
return ''
|
|
64
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@primer/doctocat-nextjs",
|
|
3
|
-
"version": "0.0.0-
|
|
3
|
+
"version": "0.0.0-20250904115752",
|
|
4
4
|
"description": "A Next.js theme for building Primer documentation sites",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -21,17 +21,16 @@
|
|
|
21
21
|
"react-dom": ">=18.0.0 <20.0.0"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@next/mdx": "15.
|
|
24
|
+
"@next/mdx": "15.5.2",
|
|
25
25
|
"@primer/octicons-react": "19.15.1",
|
|
26
26
|
"@primer/react": "^37.11.0",
|
|
27
27
|
"@types/lodash.debounce": "^4.0.9",
|
|
28
28
|
"framer-motion": "12.4.0",
|
|
29
29
|
"lodash.debounce": "^4.0.8",
|
|
30
|
-
"nextra": "4.
|
|
31
|
-
"react": "18.
|
|
32
|
-
"react-dom": "18.
|
|
33
|
-
"react-focus-on": "3.9.4"
|
|
34
|
-
"react-live": "^4.1.8"
|
|
30
|
+
"nextra": "4.4.0",
|
|
31
|
+
"react": ">=18.0.0 <20.0.0",
|
|
32
|
+
"react-dom": ">=18.0.0 <20.0.0",
|
|
33
|
+
"react-focus-on": "3.9.4"
|
|
35
34
|
},
|
|
36
35
|
"devDependencies": {
|
|
37
36
|
"@github/prettier-config": "^0.0.6",
|
|
@@ -47,7 +46,9 @@
|
|
|
47
46
|
"@vitest/ui": "^3.2.4",
|
|
48
47
|
"clsx": "2.1.1",
|
|
49
48
|
"jsdom": "^26.0.1",
|
|
50
|
-
"next": "15.
|
|
49
|
+
"next": "15.5.2",
|
|
50
|
+
"react-live": "^4.1.8",
|
|
51
|
+
"react-live-runner": "^1.0.7",
|
|
51
52
|
"styled-components": "5.3.11",
|
|
52
53
|
"vitest": "^3.2.4"
|
|
53
54
|
},
|
package/types.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {Folder, PageMapItem, MdxFile} from 'nextra'
|
|
2
|
+
import {ReactNode} from 'react'
|
|
2
3
|
|
|
3
4
|
export type ThemeConfig = {
|
|
4
5
|
docsRepositoryBase: string
|
|
@@ -19,7 +20,7 @@ export type ExtendedPageItem = PageMapItem & {
|
|
|
19
20
|
export type FolderWithoutChildren = Omit<Folder, 'children'>
|
|
20
21
|
|
|
21
22
|
export type DocsItem = MdxFile & {
|
|
22
|
-
title:
|
|
23
|
+
title: ReactNode
|
|
23
24
|
type: string
|
|
24
25
|
children?: DocsItem[]
|
|
25
26
|
firstChildRoute?: string
|