@@ -3,10 +3,11 @@ import type { Heading } from 'nextra'
3
3
import { useFSRoute , useMounted } from 'nextra/hooks'
4
4
import { ArrowRightIcon , ExpandIcon } from 'nextra/icons'
5
5
import type { Item , MenuItem , PageItem } from 'nextra/normalize-pages'
6
- import type { ReactElement } from 'react'
6
+ import type { FocusEventHandler , ReactElement } from 'react'
7
7
import {
8
8
createContext ,
9
9
memo ,
10
+ useCallback ,
10
11
useContext ,
11
12
useEffect ,
12
13
useMemo ,
@@ -22,11 +23,9 @@ import { LocaleSwitch } from './locale-switch'
22
23
23
24
const TreeState : Record < string , boolean > = Object . create ( null )
24
25
25
- const FocusedItemContext = createContext < null | string > ( null )
26
+ const FocusedItemContext = createContext ( '' )
26
27
FocusedItemContext . displayName = 'FocusedItem'
27
- const OnFocusItemContext = createContext < null | ( ( item : string | null ) => any ) > (
28
- null
29
- )
28
+ const OnFocusItemContext = createContext < ( route : string ) => void > ( ( ) => { } )
30
29
OnFocusItemContext . displayName = 'OnFocusItem'
31
30
const FolderLevelContext = createContext ( 0 )
32
31
FolderLevelContext . displayName = 'FolderLevel'
@@ -66,16 +65,17 @@ const classes = {
66
65
type FolderProps = {
67
66
item : PageItem | MenuItem | Item
68
67
anchors : Heading [ ]
68
+ onFocus : FocusEventHandler
69
69
}
70
70
71
- function FolderImpl ( { item, anchors } : FolderProps ) : ReactElement {
71
+ function FolderImpl ( { item, anchors, onFocus } : FolderProps ) : ReactElement {
72
72
const routeOriginal = useFSRoute ( )
73
73
const [ route ] = routeOriginal . split ( '#' )
74
74
const active = [ route , route + '/' ] . includes ( item . route + '/' )
75
75
const activeRouteInside = active || route . startsWith ( item . route + '/' )
76
76
77
77
const focusedRoute = useContext ( FocusedItemContext )
78
- const focusedRouteInside = ! ! focusedRoute ? .startsWith ( item . route + '/' )
78
+ const focusedRouteInside = focusedRoute . startsWith ( item . route + '/' )
79
79
const level = useContext ( FolderLevelContext )
80
80
81
81
const { setMenu } = useMenu ( )
@@ -95,18 +95,20 @@ function FolderImpl({ item, anchors }: FolderProps): ReactElement {
95
95
const rerender = useState ( { } ) [ 1 ]
96
96
97
97
useEffect ( ( ) => {
98
- const updateTreeState = ( ) => {
98
+ function updateTreeState ( ) {
99
99
if ( activeRouteInside || focusedRouteInside ) {
100
100
TreeState [ item . route ] = true
101
101
}
102
102
}
103
- const updateAndPruneTreeState = ( ) => {
103
+
104
+ function updateAndPruneTreeState ( ) {
104
105
if ( activeRouteInside && focusedRouteInside ) {
105
106
TreeState [ item . route ] = true
106
107
} else {
107
108
delete TreeState [ item . route ]
108
109
}
109
110
}
111
+
110
112
if ( themeConfig . sidebar . autoCollapse ) {
111
113
updateAndPruneTreeState ( )
112
114
} else {
@@ -145,6 +147,7 @@ function FolderImpl({ item, anchors }: FolderProps): ReactElement {
145
147
< li className = { cn ( { open, active } ) } >
146
148
< ComponentToUse
147
149
href = { isLink ? item . route : undefined }
150
+ data-href = { isLink ? undefined : item . route }
148
151
className = { cn (
149
152
'_items-center _justify-between _gap-2' ,
150
153
! isLink && '_text-left _w-full' ,
@@ -173,6 +176,7 @@ function FolderImpl({ item, anchors }: FolderProps): ReactElement {
173
176
TreeState [ item . route ] = ! open
174
177
rerender ( { } )
175
178
} }
179
+ onFocus = { onFocus }
176
180
>
177
181
{ item . title }
178
182
< ArrowRightIcon
@@ -183,15 +187,15 @@ function FolderImpl({ item, anchors }: FolderProps): ReactElement {
183
187
) }
184
188
/>
185
189
</ ComponentToUse >
186
- < Collapse className = "ltr:_pr-0 rtl:_pl-0 _pt-1" isOpen = { open } >
187
- { Array . isArray ( item . children ) ? (
190
+ < Collapse className = "_pt-1" isOpen = { open } >
191
+ { Array . isArray ( item . children ) && (
188
192
< Menu
189
193
className = { cn ( classes . border , 'ltr:_ml-3 rtl:_mr-3' ) }
190
194
directories = { item . children }
191
195
base = { item . route }
192
196
anchors = { anchors }
193
197
/>
194
- ) : null }
198
+ ) }
195
199
</ Collapse >
196
200
</ li >
197
201
)
@@ -218,13 +222,14 @@ function Separator({ title }: { title: string }): ReactElement {
218
222
219
223
function File ( {
220
224
item,
221
- anchors
225
+ anchors,
226
+ onFocus
222
227
} : {
223
228
item : PageItem | Item
224
229
anchors : Heading [ ]
230
+ onFocus : FocusEventHandler
225
231
} ) : ReactElement {
226
232
const route = useFSRoute ( )
227
- const onFocus = useContext ( OnFocusItemContext )
228
233
229
234
// It is possible that the item doesn't have any route - for example an external link.
230
235
const active = item . route && [ route , route + '/' ] . includes ( item . route + '/' )
@@ -244,12 +249,7 @@ function File({
244
249
onClick = { ( ) => {
245
250
setMenu ( false )
246
251
} }
247
- onFocus = { ( ) => {
248
- onFocus ?.( item . route )
249
- } }
250
- onBlur = { ( ) => {
251
- onFocus ?.( null )
252
- } }
252
+ onFocus = { onFocus }
253
253
>
254
254
{ item . title }
255
255
</ Anchor >
@@ -292,18 +292,39 @@ function Menu({
292
292
className,
293
293
onlyCurrentDocs
294
294
} : MenuProps ) : ReactElement {
295
+ const onFocus = useContext ( OnFocusItemContext )
296
+
297
+ const handleFocus : FocusEventHandler = useCallback (
298
+ event => {
299
+ const route =
300
+ event . target . getAttribute ( 'href' ) ||
301
+ event . target . getAttribute ( 'data-href' ) ||
302
+ ''
303
+ onFocus ( route )
304
+ } ,
305
+ [ onFocus ]
306
+ )
307
+
295
308
return (
296
309
< ul className = { cn ( classes . list , className ) } >
297
- { directories . map ( item =>
298
- ! onlyCurrentDocs || item . isUnderCurrentDocsTree ? (
310
+ { directories . map ( item => {
311
+ if ( onlyCurrentDocs && ! item . isUnderCurrentDocsTree ) return
312
+
313
+ const ComponentToUse =
299
314
item . type === 'menu' ||
300
- ( item . children && ( item . children . length || ! item . withIndexPage ) ) ? (
301
- < Folder key = { item . name } item = { item } anchors = { anchors } />
302
- ) : (
303
- < File key = { item . name } item = { item } anchors = { anchors } />
304
- )
305
- ) : null
306
- ) }
315
+ ( item . children && ( item . children . length || ! item . withIndexPage ) )
316
+ ? Folder
317
+ : File
318
+
319
+ return (
320
+ < ComponentToUse
321
+ key = { item . name }
322
+ item = { item }
323
+ anchors = { anchors }
324
+ onFocus = { handleFocus }
325
+ />
326
+ )
327
+ } ) }
307
328
</ ul >
308
329
)
309
330
}
@@ -324,7 +345,7 @@ export function Sidebar({
324
345
includePlaceholder
325
346
} : SideBarProps ) : ReactElement {
326
347
const { menu, setMenu } = useMenu ( )
327
- const [ focused , setFocused ] = useState < null | string > ( null )
348
+ const [ focused , setFocused ] = useState ( '' )
328
349
const [ showSidebar , setSidebar ] = useState ( true )
329
350
const [ showToggleAnimation , setToggleAnimation ] = useState ( false )
330
351
@@ -400,11 +421,7 @@ export function Sidebar({
400
421
</ div >
401
422
) }
402
423
< FocusedItemContext . Provider value = { focused } >
403
- < OnFocusItemContext . Provider
404
- value = { item => {
405
- setFocused ( item )
406
- } }
407
- >
424
+ < OnFocusItemContext . Provider value = { setFocused } >
408
425
< div
409
426
className = { cn (
410
427
'_overflow-y-auto' ,
0 commit comments