@@ -5,7 +5,7 @@ import NextLink from "next/link"
5
5
import { Button } from "nextra/components"
6
6
import { useFSRoute } from "nextra/hooks"
7
7
import type * as normalizePages from "nextra/normalize-pages"
8
- import type { ReactElement , ReactNode } from "react"
8
+ import { Fragment , useState , type ReactElement , type ReactNode } from "react"
9
9
import { useMenu , useThemeConfig } from "nextra-theme-docs"
10
10
import { Anchor } from "@/app/conf/_design-system/anchor"
11
11
import { renderComponent } from "@/components/utils"
@@ -19,62 +19,71 @@ export interface NavBarProps {
19
19
}
20
20
21
21
const classes = {
22
- link : "typography-menu flex items-center text-neu-900 px-3 py-1 nextra-focus [text-box:trim-both_cap_alphabetic] leading-none" ,
22
+ link : "typography-menu flex items-center text-neu-900 px-3 py-1 nextra-focus [text-box:trim-both_cap_alphabetic] leading-none hover:underline underline-offset-2 " ,
23
23
}
24
24
25
25
function NavbarMenu ( {
26
26
menu,
27
27
children,
28
+ onSubmenuOpen,
28
29
} : {
29
30
menu : normalizePages . MenuItem
30
31
children : ReactNode
32
+ onSubmenuOpen : ( open : boolean ) => void
31
33
} ) : ReactElement {
32
34
const routes = Object . fromEntries (
33
35
( menu . children || [ ] ) . map ( route => [ route . name , route ] ) ,
34
36
)
35
37
return (
36
38
< Menu >
37
- < MenuButton
38
- className = { ( { focus } ) =>
39
- clsx (
40
- classes . link ,
41
- "flex items-center gap-1.5 whitespace-nowrap max-md:hidden" ,
42
- focus && "nextra-focusable" ,
39
+ < MenuButton as = { Fragment } >
40
+ { ( { focus, open } ) => {
41
+ // I'm sorry, I know this is so cursed.
42
+ // I need to migrate out of HeadlessUI to something with change handlers.
43
+ onSubmenuOpen ( open )
44
+
45
+ return (
46
+ < button
47
+ onClick = { ( ) => onSubmenuOpen ( open ) }
48
+ className = { clsx (
49
+ classes . link ,
50
+ "flex items-center gap-1.5 whitespace-nowrap max-md:hidden" ,
51
+ focus && "nextra-focusable" ,
52
+ ) }
53
+ >
54
+ { children }
55
+ </ button >
43
56
)
44
- }
45
- >
46
- { children }
57
+ } }
47
58
</ MenuButton >
48
59
< MenuItems
49
60
transition
50
- portal = { false }
51
61
modal = { false }
52
62
className = { ( { open } ) =>
63
+ // eslint-disable-next-line tailwindcss/no-custom-classname
53
64
clsx (
65
+ "gql-navbar-menu-items" ,
54
66
"motion-reduce:transition-none" ,
55
67
"focus-visible:outline-none" ,
56
68
open ? "opacity-100" : "opacity-0" ,
57
- "nextra-scrollbar transition-opacity" ,
58
- "bg-[rgb(var(--nextra-bg),.8)] backdrop-blur-lg" , // todo: full screen overlay
69
+ "nextra-scrollbar overflow-visible transition-opacity" ,
59
70
"z-20 rounded-md py-1 text-sm" ,
60
71
// headlessui adds max-height as style, use !important to override
61
72
"!max-h-[min(calc(100vh-5rem),256px)]" ,
62
73
)
63
74
}
64
- anchor = { { to : "top start" , gap : 21 , padding : 16 } }
75
+ anchor = { { to : "top start" , gap : 21 , padding : 16 , offset : - 8 } }
65
76
>
66
77
{ Object . entries ( menu . items || { } ) . map ( ( [ key , item ] ) => (
67
- < MenuItem
68
- key = { key }
69
- as = { Anchor }
70
- href = { item . href || routes [ key ] ?. route }
71
- >
78
+ < MenuItem key = { key } >
72
79
< Anchor
73
80
href = { item . href || routes [ key ] ?. route }
74
- className = "data-focus:text-gray-900 block py-1.5 pl-3 pr-9 transition-colors rtl:pl-9 rtl:pr-3 "
81
+ className = "block py-1.5 pl-2 pr-9"
75
82
target = { item . newWindow ? "_blank" : undefined }
76
83
>
77
- { item . title }
84
+ < span className = "typography-menu px-3 py-1 underline-offset-2 [[data-active]>&]:underline" >
85
+ { item . title }
86
+ </ span >
78
87
</ Anchor >
79
88
</ MenuItem >
80
89
) ) }
@@ -88,11 +97,12 @@ export function Navbar({ items }: NavBarProps): ReactElement {
88
97
89
98
const activeRoute = useFSRoute ( )
90
99
const { menu, setMenu } = useMenu ( )
100
+ const [ submenuOpen , setSubmenuOpen ] = useState ( false )
91
101
92
102
return (
93
103
< div
94
104
className = { clsx (
95
- "nextra-nav-container _top -0 _z -20 _w -full _bg -transparent print:_hidden " ,
105
+ "nextra-nav-container top -0 z -20 w -full bg -transparent print:hidden " ,
96
106
activeRoute === "/" ? "fixed" : "sticky" ,
97
107
) }
98
108
>
@@ -122,7 +132,20 @@ export function Navbar({ items }: NavBarProps): ReactElement {
122
132
if ( pageOrMenu . type === "menu" ) {
123
133
const menu = pageOrMenu as normalizePages . MenuItem
124
134
return (
125
- < NavbarMenu key = { menu . title } menu = { menu } >
135
+ < NavbarMenu
136
+ key = { menu . title }
137
+ menu = { menu }
138
+ onSubmenuOpen = { open => {
139
+ if ( typeof window !== "undefined" ) {
140
+ if ( open ) {
141
+ document . body . style . overflow = "hidden"
142
+ } else {
143
+ document . body . style . overflow = "auto"
144
+ }
145
+ }
146
+ setSubmenuOpen ( open )
147
+ } }
148
+ >
126
149
{ menu . title }
127
150
</ NavbarMenu >
128
151
)
@@ -147,7 +170,7 @@ export function Navbar({ items }: NavBarProps): ReactElement {
147
170
className = { clsx (
148
171
classes . link ,
149
172
"whitespace-nowrap max-md:hidden" ,
150
- isActive && ! page . newWindow && "font-medium " ,
173
+ isActive && ! page . newWindow && "underline " ,
151
174
) }
152
175
target = { page . newWindow ? "_blank" : undefined }
153
176
aria-current = { ! page . newWindow && isActive }
@@ -194,6 +217,7 @@ export function Navbar({ items }: NavBarProps): ReactElement {
194
217
) }
195
218
</ Button >
196
219
</ nav >
220
+ < SubmenuBackdrop className = { submenuOpen ? "opacity-100" : "opacity-0" } />
197
221
</ div >
198
222
)
199
223
}
@@ -240,3 +264,14 @@ export function NavbarPlaceholder({
240
264
/>
241
265
)
242
266
}
267
+
268
+ function SubmenuBackdrop ( { className } : { className : string } ) {
269
+ return (
270
+ < div
271
+ className = { clsx (
272
+ "fixed inset-0 top-[calc(var(--nextra-navbar-height)+1px)] bg-[rgb(var(--nextra-bg),.4)] backdrop-blur-[6.4px] transition-opacity" ,
273
+ className ,
274
+ ) }
275
+ />
276
+ )
277
+ }
0 commit comments