@primer/doctocat-nextjs 0.0.0-20250625090917 → 0.0.0-20250625110636
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,25 @@
|
|
|
1
1
|
# @primer/doctocat-nextjs
|
|
2
2
|
|
|
3
|
-
## 0.0.0-
|
|
3
|
+
## 0.0.0-20250625110636
|
|
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-20250625110634
|
|
10
10
|
|
|
11
11
|
### Patch Changes
|
|
12
12
|
|
|
13
|
-
- [
|
|
13
|
+
- [`b0e2e2a`](https://github.com/primer/doctocat-nextjs/commit/b0e2e2ae3b6c5f61f3de8e40eecb789e577fb8a3) Thanks [@rezrah](https://github.com/rezrah)! - Add auto-collapsed React code blocks for large code snippets. This feature only applies to code fences with the `jsx live` language identifiers.
|
|
14
|
+
|
|
15
|
+
E.g.
|
|
16
|
+
|
|
17
|
+
```jsx live
|
|
18
|
+
<>Your code</>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
- [#48](https://github.com/primer/doctocat-nextjs/pull/48) [`ce73c24`](https://github.com/primer/doctocat-nextjs/commit/ce73c24b2e4e924667bf7446a504bd88d8f2ccf0) Thanks [@rezrah](https://github.com/rezrah)! - - Fix inline code font-size in markdown headings. Now inherits size used in the heading.
|
|
22
|
+
- Increased spacing below React code blocks, which was previously too small.
|
|
14
23
|
|
|
15
24
|
## 0.5.3
|
|
16
25
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
.CodeBlock {
|
|
2
|
+
--spacing: var(--base-size-40);
|
|
2
3
|
margin-block: var(--base-size-24);
|
|
3
4
|
background-color: var(--brand-color-canvas-default);
|
|
4
5
|
border: var(--brand-borderWidth-thin) solid var(--brand-color-border-default);
|
|
@@ -27,6 +28,11 @@
|
|
|
27
28
|
font-family: var(--brand-fontStack-monospace);
|
|
28
29
|
background-color: var(--brand-color-canvas-subtle);
|
|
29
30
|
overflow: auto;
|
|
31
|
+
position: relative;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.EditorWrapper {
|
|
35
|
+
position: relative;
|
|
30
36
|
}
|
|
31
37
|
|
|
32
38
|
.Editor pre {
|
|
@@ -36,6 +42,11 @@
|
|
|
36
42
|
background-color: var(--brand-color-canvas-subtle) !important;
|
|
37
43
|
}
|
|
38
44
|
|
|
45
|
+
.Editor--is-collapsed {
|
|
46
|
+
max-height: 400px;
|
|
47
|
+
overflow: hidden;
|
|
48
|
+
}
|
|
49
|
+
|
|
39
50
|
.Toolbar {
|
|
40
51
|
display: flex;
|
|
41
52
|
gap: var(--base-size-8);
|
|
@@ -66,3 +77,44 @@
|
|
|
66
77
|
.colorModeButtonActive {
|
|
67
78
|
background-color: var(--brand-color-canvas-subtle);
|
|
68
79
|
}
|
|
80
|
+
|
|
81
|
+
.collapseButton {
|
|
82
|
+
position: relative;
|
|
83
|
+
display: flex;
|
|
84
|
+
justify-content: center;
|
|
85
|
+
width: 100%;
|
|
86
|
+
background-color: var(--brand-color-canvas-subtle);
|
|
87
|
+
border: none;
|
|
88
|
+
border-top: var(--brand-borderWidth-thin) solid var(--brand-color-border-default);
|
|
89
|
+
border-bottom-left-radius: var(--brand-borderRadius-medium);
|
|
90
|
+
border-bottom-right-radius: var(--brand-borderRadius-medium);
|
|
91
|
+
padding: var(--base-size-8) var(--base-size-12);
|
|
92
|
+
font-size: var(--brand-text-size-100);
|
|
93
|
+
font-family: var(--brand-fontStack-system);
|
|
94
|
+
cursor: pointer;
|
|
95
|
+
display: flex;
|
|
96
|
+
align-items: center;
|
|
97
|
+
gap: var(--base-size-4);
|
|
98
|
+
z-index: 10;
|
|
99
|
+
transition: background-color var(--brand-animation-duration-default) var(--brand-animation-easing-glide);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.collapseButton--collapsed::before {
|
|
103
|
+
content: '';
|
|
104
|
+
position: absolute;
|
|
105
|
+
top: -41px;
|
|
106
|
+
left: 0;
|
|
107
|
+
width: 100%;
|
|
108
|
+
height: 40px;
|
|
109
|
+
background: linear-gradient(to bottom, transparent, var(--brand-color-canvas-subtle));
|
|
110
|
+
z-index: -1;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.collapseButton:hover,
|
|
114
|
+
.collapseButton:focus {
|
|
115
|
+
background-color: var(--brand-color-canvas-default);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.collapseLabel {
|
|
119
|
+
font-size: var(--brand-text-size-100) !important;
|
|
120
|
+
}
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
'use client'
|
|
2
|
-
import React, {PropsWithChildren, useCallback, useState} from 'react'
|
|
2
|
+
import React, {PropsWithChildren, useCallback, useState, useRef, useEffect, useId} from 'react'
|
|
3
3
|
import clsx from 'clsx'
|
|
4
4
|
import {LiveProvider, LiveEditor, LiveError, LivePreview} from 'react-live'
|
|
5
5
|
import {useColorMode} from '../../context/color-modes/useColorMode'
|
|
6
6
|
import {useConfig} from '../../context/useConfig'
|
|
7
7
|
import styles from './ReactCodeBlock.module.css'
|
|
8
8
|
import {ActionBar, Button, ThemeProvider} from '@primer/react'
|
|
9
|
-
import {
|
|
9
|
+
import {Text} from '@primer/react-brand'
|
|
10
|
+
import {CopyIcon, MoonIcon, SunIcon, UndoIcon, UnfoldIcon, FoldIcon} from '@primer/octicons-react'
|
|
10
11
|
import {colorModes} from '../../context/color-modes/context'
|
|
11
12
|
|
|
12
13
|
import {lightTheme, darkTheme} from './syntax-highlighting-themes'
|
|
13
14
|
import {codeTransformer} from './code-transformer'
|
|
14
15
|
|
|
16
|
+
const COLLAPSE_HEIGHT = 400 // TODO: Hoist this to config to make user customizable eventually
|
|
17
|
+
|
|
15
18
|
type ReactCodeBlockProps = {
|
|
16
19
|
'data-language': string
|
|
17
20
|
'data-filename'?: string
|
|
@@ -19,12 +22,35 @@ type ReactCodeBlockProps = {
|
|
|
19
22
|
} & PropsWithChildren<HTMLElement>
|
|
20
23
|
|
|
21
24
|
export function ReactCodeBlock(props: ReactCodeBlockProps) {
|
|
25
|
+
const uniqueId = useId()
|
|
22
26
|
const {colorMode, setColorMode} = useColorMode()
|
|
23
27
|
const {basePath} = useConfig()
|
|
24
28
|
const initialCode = getCodeFromChildren(props.children)
|
|
25
29
|
const [code, setCode] = useState(initialCode)
|
|
30
|
+
const rootRef = useRef<HTMLDivElement>(null)
|
|
31
|
+
const [isCodePaneCollapsed, setIsCodePaneCollapsed] = useState<boolean | null>(null)
|
|
32
|
+
const [initialPosition, setInitialPosition] = useState<number | null>(null)
|
|
33
|
+
const editorRef = useRef<HTMLDivElement>(null)
|
|
26
34
|
const shouldShowPreview = ['tsx', 'jsx'].includes(props['data-language'])
|
|
27
35
|
|
|
36
|
+
// scroll back to the initial y pos on collapse state change
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
if (rootRef.current && initialPosition === null) {
|
|
39
|
+
const rect = rootRef.current.getBoundingClientRect()
|
|
40
|
+
const scrollTop = window.pageYOffset || document.documentElement.scrollTop
|
|
41
|
+
setInitialPosition(rect.top + scrollTop)
|
|
42
|
+
}
|
|
43
|
+
}, [initialPosition])
|
|
44
|
+
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (editorRef.current && isCodePaneCollapsed === null) {
|
|
47
|
+
const editorHeight = editorRef.current.scrollHeight
|
|
48
|
+
setIsCodePaneCollapsed(editorHeight > COLLAPSE_HEIGHT)
|
|
49
|
+
}
|
|
50
|
+
}, [code, isCodePaneCollapsed])
|
|
51
|
+
|
|
52
|
+
const shouldShowCollapse = isCodePaneCollapsed !== null && (editorRef.current?.scrollHeight || 0) > COLLAPSE_HEIGHT
|
|
53
|
+
|
|
28
54
|
/**
|
|
29
55
|
* Transforms code to prepend basePath to img src attributes
|
|
30
56
|
*/
|
|
@@ -41,12 +67,27 @@ export function ReactCodeBlock(props: ReactCodeBlockProps) {
|
|
|
41
67
|
navigator.clipboard.writeText(code)
|
|
42
68
|
}, [code])
|
|
43
69
|
|
|
70
|
+
const handleCollapsibleCodePane = useCallback(() => {
|
|
71
|
+
const newCollapsedState = !isCodePaneCollapsed
|
|
72
|
+
|
|
73
|
+
if (!isCodePaneCollapsed && newCollapsedState && initialPosition !== null) {
|
|
74
|
+
requestAnimationFrame(() => {
|
|
75
|
+
window.scrollTo({
|
|
76
|
+
top: initialPosition,
|
|
77
|
+
behavior: 'smooth',
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
setIsCodePaneCollapsed(newCollapsedState)
|
|
83
|
+
}, [isCodePaneCollapsed, initialPosition])
|
|
84
|
+
|
|
44
85
|
const noInline = props['data-filename'] === 'noinline' || false
|
|
45
86
|
|
|
46
87
|
return (
|
|
47
88
|
<>
|
|
48
89
|
<LiveProvider transformCode={transformCodeWithBasePath} code={code} scope={props.jsxScope} noInline={noInline}>
|
|
49
|
-
<div className={clsx(styles.CodeBlock, 'custom-component')}>
|
|
90
|
+
<div ref={rootRef} className={clsx(styles.CodeBlock, 'custom-component')}>
|
|
50
91
|
{shouldShowPreview && (
|
|
51
92
|
<div>
|
|
52
93
|
<div className={styles.colorModeMenu}>
|
|
@@ -80,8 +121,28 @@ export function ReactCodeBlock(props: ReactCodeBlockProps) {
|
|
|
80
121
|
Reset
|
|
81
122
|
</Button>
|
|
82
123
|
</div>
|
|
83
|
-
<div className={styles.
|
|
84
|
-
<
|
|
124
|
+
<div className={styles.EditorWrapper}>
|
|
125
|
+
<div
|
|
126
|
+
className={clsx(styles.Editor, isCodePaneCollapsed && styles['Editor--is-collapsed'])}
|
|
127
|
+
ref={editorRef}
|
|
128
|
+
id={`${uniqueId}-code-editor-content`}
|
|
129
|
+
>
|
|
130
|
+
<LiveEditor theme={colorMode === 'light' ? lightTheme : darkTheme} onChange={setCode} />
|
|
131
|
+
</div>
|
|
132
|
+
{shouldShowCollapse && (
|
|
133
|
+
<button
|
|
134
|
+
className={clsx(styles.collapseButton, isCodePaneCollapsed && styles['collapseButton--collapsed'])}
|
|
135
|
+
onClick={handleCollapsibleCodePane}
|
|
136
|
+
aria-expanded={!isCodePaneCollapsed}
|
|
137
|
+
aria-controls={`${uniqueId}-code-editor-content`}
|
|
138
|
+
aria-label={isCodePaneCollapsed ? 'Show full code block' : 'Collapse code block'}
|
|
139
|
+
>
|
|
140
|
+
<Text size="100" className={styles.collapseLabel}>
|
|
141
|
+
{isCodePaneCollapsed ? 'Show full code' : 'Collapse code'}
|
|
142
|
+
</Text>
|
|
143
|
+
<Text variant="muted">{isCodePaneCollapsed ? <UnfoldIcon /> : <FoldIcon />}</Text>
|
|
144
|
+
</button>
|
|
145
|
+
)}
|
|
85
146
|
</div>
|
|
86
147
|
{shouldShowPreview && (
|
|
87
148
|
<div className={styles.Error}>
|
|
@@ -82,29 +82,7 @@ export function Sidebar({pageMap}: SidebarProps) {
|
|
|
82
82
|
<NextLink href={item.route}>{subNavName}</NextLink>
|
|
83
83
|
</NavList.GroupHeading>
|
|
84
84
|
{item.children
|
|
85
|
-
.sort((a, b) =>
|
|
86
|
-
// make sure index page is first
|
|
87
|
-
if ((a as MdxFile).name === 'index') return -1
|
|
88
|
-
if ((b as MdxFile).name === 'index') return 1
|
|
89
|
-
|
|
90
|
-
// Check for menu-position property in frontmatter
|
|
91
|
-
const aPos = (a as MdxFile).frontMatter?.['menu-position']
|
|
92
|
-
const bPos = (b as MdxFile).frontMatter?.['menu-position']
|
|
93
|
-
|
|
94
|
-
// If both have menu-position, sort by menu-position
|
|
95
|
-
if (typeof aPos === 'number' && typeof bPos === 'number') {
|
|
96
|
-
return aPos - bPos
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// If only one has menu-position, it comes first
|
|
100
|
-
if (typeof aPos === 'number') return -1
|
|
101
|
-
if (typeof bPos === 'number') return 1
|
|
102
|
-
|
|
103
|
-
// Neither has menu-position, sort alphabetically by title or name
|
|
104
|
-
const aTitle = (a as MdxFile).frontMatter?.title || (a as MdxFile).name
|
|
105
|
-
const bTitle = (b as MdxFile).frontMatter?.title || (b as MdxFile).name
|
|
106
|
-
return aTitle.localeCompare(bTitle)
|
|
107
|
-
})
|
|
85
|
+
.sort((a, b) => ((a as MdxFile).name === 'index' ? -1 : (b as MdxFile).name === 'index' ? 1 : 0)) // puts index page first
|
|
108
86
|
// only show index page if it has show-tabs
|
|
109
87
|
.filter(child => (child as MdxFile).name !== 'index' || hasShowTabs(child as ExtendedPageItem))
|
|
110
88
|
.map(child => {
|
package/css/prose.module.css
CHANGED
|
@@ -211,6 +211,11 @@
|
|
|
211
211
|
background-color: var(--base-color-scale-indigo-0);
|
|
212
212
|
}
|
|
213
213
|
|
|
214
|
+
/* Inline code inside headings should inherit font-size */
|
|
215
|
+
.Prose :is(h1, h2, h3, h4, h5, h6):has(code) code:not(:global(.custom-component) code) {
|
|
216
|
+
font-size: inherit;
|
|
217
|
+
}
|
|
218
|
+
|
|
214
219
|
[data-color-mode='dark'] .Prose code:not(:global(.custom-component) code) {
|
|
215
220
|
background-color: var(--base-color-scale-indigo-9);
|
|
216
221
|
}
|
package/package.json
CHANGED