Skip to content

Commit 3e613c0

Browse files
committed
fix merge conflict
2 parents 730df43 + c7bf4a2 commit 3e613c0

5 files changed

Lines changed: 77 additions & 3 deletions

File tree

.changeset/real-pumas-boil.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@primer/doctocat-nextjs': patch
3+
---
4+
5+
Add `menu-position` frontmatter support for custom sidebar navigation ordering

packages/site/content/content-example/simple.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ related:
77
href: 'https://primer.style/foundations/typography'
88
- title: 'Monaspace'
99
href: 'https://monaspace.githubnext.com/'
10+
menu-position: 1
1011
---
1112

1213
## Arva qua ferarum victa

packages/theme/components/layout/code-block/ReactCodeBlock.tsx

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
'use client'
2-
import React, {PropsWithChildren, useCallback, useState, useRef, useEffect, useId} from 'react'
2+
import React, {type PropsWithChildren, useCallback, useState, useRef, useEffect, useId} from 'react'
33
import clsx from 'clsx'
44
import {LiveProvider, LiveEditor, LiveError, LivePreview} from 'react-live'
55
import {useColorMode} from '../../context/color-modes/useColorMode'
@@ -21,6 +21,15 @@ type ReactCodeBlockProps = {
2121
jsxScope: Record<string, unknown>
2222
} & PropsWithChildren<HTMLElement>
2323

24+
const getFocusableElements = () => {
25+
const focusableElementsQuery = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
26+
27+
return Array.from(document.querySelectorAll<HTMLElement>(focusableElementsQuery)).filter(el => {
28+
const style = window.getComputedStyle(el)
29+
return style.display !== 'none' && style.visibility !== 'hidden' && !el.hasAttribute('disabled')
30+
})
31+
}
32+
2433
export function ReactCodeBlock(props: ReactCodeBlockProps) {
2534
const uniqueId = useId()
2635
const {colorMode, setColorMode} = useColorMode()
@@ -31,6 +40,7 @@ export function ReactCodeBlock(props: ReactCodeBlockProps) {
3140
const [isCodePaneCollapsed, setIsCodePaneCollapsed] = useState<boolean | null>(null)
3241
const [initialPosition, setInitialPosition] = useState<number | null>(null)
3342
const editorRef = useRef<HTMLDivElement>(null)
43+
const resetButtonRef = useRef<HTMLButtonElement>(null)
3444
const shouldShowPreview = ['tsx', 'jsx'].includes(props['data-language'])
3545

3646
// scroll back to the initial y pos on collapse state change
@@ -84,6 +94,41 @@ export function ReactCodeBlock(props: ReactCodeBlockProps) {
8494

8595
const noInline = props['data-filename'] === 'noinline' || false
8696

97+
useEffect(() => {
98+
const editor = editorRef.current
99+
100+
if (!editor) return
101+
102+
const onKeyDown = (e: KeyboardEvent) => {
103+
if (e.key !== 'Tab') {
104+
return
105+
}
106+
107+
if (e.shiftKey) {
108+
e.preventDefault()
109+
// We know that the previous focusable element is always the reset button
110+
resetButtonRef.current?.focus()
111+
return
112+
}
113+
114+
const focusableElements = getFocusableElements()
115+
116+
const currentIndex = focusableElements.findIndex(el => el === resetButtonRef.current)
117+
118+
if (currentIndex !== -1) {
119+
e.preventDefault()
120+
const nextIndex = currentIndex + 1
121+
focusableElements[nextIndex]?.focus()
122+
}
123+
}
124+
125+
editor.addEventListener('keydown', onKeyDown)
126+
127+
return () => {
128+
editor.removeEventListener('keydown', onKeyDown)
129+
}
130+
}, [])
131+
87132
return (
88133
<>
89134
<LiveProvider transformCode={transformCodeWithBasePath} code={code} scope={props.jsxScope} noInline={noInline}>
@@ -117,7 +162,7 @@ export function ReactCodeBlock(props: ReactCodeBlockProps) {
117162
<Button size="small" leadingVisual={CopyIcon} onClick={handleCopy}>
118163
Copy
119164
</Button>
120-
<Button size="small" leadingVisual={UndoIcon} onClick={handleReset}>
165+
<Button size="small" leadingVisual={UndoIcon} onClick={handleReset} ref={resetButtonRef}>
121166
Reset
122167
</Button>
123168
</div>

packages/theme/components/layout/sidebar/Sidebar.tsx

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,29 @@ export function Sidebar({pageMap}: SidebarProps) {
8282
<NextLink href={item.route}>{subNavName}</NextLink>
8383
</NavList.GroupHeading>
8484
{item.children
85-
.sort((a, b) => ((a as MdxFile).name === 'index' ? -1 : (b as MdxFile).name === 'index' ? 1 : 0)) // puts index page first
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+
})
86108
// only show index page if it has show-tabs
87109
.filter(child => (child as MdxFile).name !== 'index' || hasShowTabs(child as ExtendedPageItem))
88110
.map(child => {

packages/theme/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export type FrontMatter = {
3131
description?: string
3232
filePath?: string
3333
keywords?: string[]
34+
menu_position?: number
3435
related?: {
3536
title: string
3637
href: string

0 commit comments

Comments
 (0)