Skip to content

Commit 0b4d43b

Browse files
authoredAug 31, 2024··
[v3] Avoid the sidebar collapse having unintended animations when sidebar.autoCollapse is set to true (#3157)
* more * more * more * more * aa * fix lint
1 parent c4c4635 commit 0b4d43b

File tree

4 files changed

+59
-37
lines changed

4 files changed

+59
-37
lines changed
 

Diff for: ‎.changeset/fair-hounds-watch.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'nextra-theme-docs': patch
3+
---
4+
5+
Avoid the sidebar collapse having unintended animations when `sidebar.autoCollapse` is set to `true`.

Diff for: ‎examples/swr-site/theme.config.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ const config: DocsThemeConfig = {
191191
},
192192
toc: {
193193
extraContent: (
194-
<img alt="placeholder cat" src="https://placekitten.com/g/300/200" />
194+
<img alt="placeholder cat" src="https://placecats.com/300/200" />
195195
),
196196
float: true
197197
}

Diff for: ‎packages/nextra-theme-docs/src/components/search.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ export function Search({
216216
const handleChange = useCallback(
217217
(e: ChangeEvent<HTMLInputElement> | CompositionEvent<HTMLInputElement>) => {
218218
if (!compositionStateRef.current.compositioning) {
219-
const { value } = e.target as HTMLInputElement
219+
const { value } = e.currentTarget
220220
onChangeProp(value)
221221
setShow(Boolean(value))
222222
compositionStateRef.current.emitted = true

Diff for: ‎packages/nextra-theme-docs/src/components/sidebar.tsx

+52-35
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ import type { Heading } from 'nextra'
33
import { useFSRoute, useMounted } from 'nextra/hooks'
44
import { ArrowRightIcon, ExpandIcon } from 'nextra/icons'
55
import type { Item, MenuItem, PageItem } from 'nextra/normalize-pages'
6-
import type { ReactElement } from 'react'
6+
import type { FocusEventHandler, ReactElement } from 'react'
77
import {
88
createContext,
99
memo,
10+
useCallback,
1011
useContext,
1112
useEffect,
1213
useMemo,
@@ -22,11 +23,9 @@ import { LocaleSwitch } from './locale-switch'
2223

2324
const TreeState: Record<string, boolean> = Object.create(null)
2425

25-
const FocusedItemContext = createContext<null | string>(null)
26+
const FocusedItemContext = createContext('')
2627
FocusedItemContext.displayName = 'FocusedItem'
27-
const OnFocusItemContext = createContext<null | ((item: string | null) => any)>(
28-
null
29-
)
28+
const OnFocusItemContext = createContext<(route: string) => void>(() => {})
3029
OnFocusItemContext.displayName = 'OnFocusItem'
3130
const FolderLevelContext = createContext(0)
3231
FolderLevelContext.displayName = 'FolderLevel'
@@ -66,16 +65,17 @@ const classes = {
6665
type FolderProps = {
6766
item: PageItem | MenuItem | Item
6867
anchors: Heading[]
68+
onFocus: FocusEventHandler
6969
}
7070

71-
function FolderImpl({ item, anchors }: FolderProps): ReactElement {
71+
function FolderImpl({ item, anchors, onFocus }: FolderProps): ReactElement {
7272
const routeOriginal = useFSRoute()
7373
const [route] = routeOriginal.split('#')
7474
const active = [route, route + '/'].includes(item.route + '/')
7575
const activeRouteInside = active || route.startsWith(item.route + '/')
7676

7777
const focusedRoute = useContext(FocusedItemContext)
78-
const focusedRouteInside = !!focusedRoute?.startsWith(item.route + '/')
78+
const focusedRouteInside = focusedRoute.startsWith(item.route + '/')
7979
const level = useContext(FolderLevelContext)
8080

8181
const { setMenu } = useMenu()
@@ -95,18 +95,20 @@ function FolderImpl({ item, anchors }: FolderProps): ReactElement {
9595
const rerender = useState({})[1]
9696

9797
useEffect(() => {
98-
const updateTreeState = () => {
98+
function updateTreeState() {
9999
if (activeRouteInside || focusedRouteInside) {
100100
TreeState[item.route] = true
101101
}
102102
}
103-
const updateAndPruneTreeState = () => {
103+
104+
function updateAndPruneTreeState() {
104105
if (activeRouteInside && focusedRouteInside) {
105106
TreeState[item.route] = true
106107
} else {
107108
delete TreeState[item.route]
108109
}
109110
}
111+
110112
if (themeConfig.sidebar.autoCollapse) {
111113
updateAndPruneTreeState()
112114
} else {
@@ -145,6 +147,7 @@ function FolderImpl({ item, anchors }: FolderProps): ReactElement {
145147
<li className={cn({ open, active })}>
146148
<ComponentToUse
147149
href={isLink ? item.route : undefined}
150+
data-href={isLink ? undefined : item.route}
148151
className={cn(
149152
'_items-center _justify-between _gap-2',
150153
!isLink && '_text-left _w-full',
@@ -173,6 +176,7 @@ function FolderImpl({ item, anchors }: FolderProps): ReactElement {
173176
TreeState[item.route] = !open
174177
rerender({})
175178
}}
179+
onFocus={onFocus}
176180
>
177181
{item.title}
178182
<ArrowRightIcon
@@ -183,15 +187,15 @@ function FolderImpl({ item, anchors }: FolderProps): ReactElement {
183187
)}
184188
/>
185189
</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) && (
188192
<Menu
189193
className={cn(classes.border, 'ltr:_ml-3 rtl:_mr-3')}
190194
directories={item.children}
191195
base={item.route}
192196
anchors={anchors}
193197
/>
194-
) : null}
198+
)}
195199
</Collapse>
196200
</li>
197201
)
@@ -218,13 +222,14 @@ function Separator({ title }: { title: string }): ReactElement {
218222

219223
function File({
220224
item,
221-
anchors
225+
anchors,
226+
onFocus
222227
}: {
223228
item: PageItem | Item
224229
anchors: Heading[]
230+
onFocus: FocusEventHandler
225231
}): ReactElement {
226232
const route = useFSRoute()
227-
const onFocus = useContext(OnFocusItemContext)
228233

229234
// It is possible that the item doesn't have any route - for example an external link.
230235
const active = item.route && [route, route + '/'].includes(item.route + '/')
@@ -244,12 +249,7 @@ function File({
244249
onClick={() => {
245250
setMenu(false)
246251
}}
247-
onFocus={() => {
248-
onFocus?.(item.route)
249-
}}
250-
onBlur={() => {
251-
onFocus?.(null)
252-
}}
252+
onFocus={onFocus}
253253
>
254254
{item.title}
255255
</Anchor>
@@ -292,18 +292,39 @@ function Menu({
292292
className,
293293
onlyCurrentDocs
294294
}: 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+
295308
return (
296309
<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 =
299314
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+
})}
307328
</ul>
308329
)
309330
}
@@ -324,7 +345,7 @@ export function Sidebar({
324345
includePlaceholder
325346
}: SideBarProps): ReactElement {
326347
const { menu, setMenu } = useMenu()
327-
const [focused, setFocused] = useState<null | string>(null)
348+
const [focused, setFocused] = useState('')
328349
const [showSidebar, setSidebar] = useState(true)
329350
const [showToggleAnimation, setToggleAnimation] = useState(false)
330351

@@ -400,11 +421,7 @@ export function Sidebar({
400421
</div>
401422
)}
402423
<FocusedItemContext.Provider value={focused}>
403-
<OnFocusItemContext.Provider
404-
value={item => {
405-
setFocused(item)
406-
}}
407-
>
424+
<OnFocusItemContext.Provider value={setFocused}>
408425
<div
409426
className={cn(
410427
'_overflow-y-auto',

0 commit comments

Comments
 (0)
Please sign in to comment.