1
1
import { MenuItem , Menu , MenuButton , MenuItems } from "@headlessui/react"
2
- import cn from "clsx"
2
+ import clsx from "clsx"
3
3
// eslint-disable-next-line no-restricted-imports -- since we don't need newWindow prop
4
4
import NextLink from "next/link"
5
5
import { Button } from "nextra/components"
@@ -19,13 +19,7 @@ export interface NavBarProps {
19
19
}
20
20
21
21
const classes = {
22
- link : cn (
23
- "_text-sm contrast-more:_text-gray-700 contrast-more:dark:_text-gray-100" ,
24
- ) ,
25
- active : cn ( "_font-medium _subpixel-antialiased" ) ,
26
- inactive : cn (
27
- "_text-gray-600 hover:_text-gray-800 dark:_text-gray-400 dark:hover:_text-gray-200" ,
28
- ) ,
22
+ link : "typography-menu flex items-center text-neu-900 px-3 py-1 nextra-focus [text-box:trim-both_cap_alphabetic] leading-none" ,
29
23
}
30
24
31
25
function NavbarMenu ( {
@@ -42,33 +36,32 @@ function NavbarMenu({
42
36
< Menu >
43
37
< MenuButton
44
38
className = { ( { focus } ) =>
45
- cn (
39
+ clsx (
46
40
classes . link ,
47
- classes . inactive ,
48
- "max-md:_hidden _items-center _whitespace-nowrap _flex _gap-1.5 _ring-inset" ,
41
+ "flex items-center gap-1.5 whitespace-nowrap max-md:hidden" ,
49
42
focus && "nextra-focusable" ,
50
43
)
51
44
}
52
45
>
53
46
{ children }
54
- { "->" }
55
47
</ MenuButton >
56
48
< MenuItems
57
49
transition
50
+ portal = { false }
51
+ modal = { false }
58
52
className = { ( { open } ) =>
59
- cn (
60
- "motion-reduce:_transition-none" ,
61
- "nextra-focus" ,
62
- open ? "_opacity-100" : "_opacity-0" ,
63
- "nextra-scrollbar _transition-opacity" ,
64
- "_border _border-black/5 dark:_border-white/20" ,
65
- "_backdrop-blur-lg bg-[rgb(var(--nextra-bg),.8)]" , // todo: full screen overlay
66
- "_z-20 _rounded-md _py-1 _text-sm" ,
53
+ clsx (
54
+ "motion-reduce:transition-none" ,
55
+ "focus-visible:outline-none" ,
56
+ open ? "opacity-100" : "opacity-0" ,
57
+ "nextra-scrollbar transition-opacity" ,
58
+ "bg-[rgb(var(--nextra-bg),.8)] backdrop-blur-lg" , // todo: full screen overlay
59
+ "z-20 rounded-md py-1 text-sm" ,
67
60
// headlessui adds max-height as style, use !important to override
68
- "!_max -h-[min(calc(100vh-5rem),256px)]" ,
61
+ "!max -h-[min(calc(100vh-5rem),256px)]" ,
69
62
)
70
63
}
71
- anchor = { { to : "top end " , gap : 10 , padding : 16 } }
64
+ anchor = { { to : "top start " , gap : 21 , padding : 16 } }
72
65
>
73
66
{ Object . entries ( menu . items || { } ) . map ( ( [ key , item ] ) => (
74
67
< MenuItem
@@ -97,26 +90,32 @@ export function Navbar({ items }: NavBarProps): ReactElement {
97
90
const { menu, setMenu } = useMenu ( )
98
91
99
92
return (
100
- < div className = "nextra-nav-container _top-0 _z-20 _w-full _bg-transparent print:_hidden fixed" >
101
- < div className = "nextra-nav-container-blur" />
102
- < nav className = "mx-auto flex h-[var(--nextra-navbar-height)] max-w-[90rem] items-center justify-end gap-4 pl-[max(env(safe-area-inset-left),1.5rem)] pr-[max(env(safe-area-inset-right),1.5rem)]" >
93
+ < div
94
+ className = { clsx (
95
+ "nextra-nav-container _top-0 _z-20 _w-full _bg-transparent print:_hidden" ,
96
+ activeRoute === "/" ? "fixed" : "sticky" ,
97
+ ) }
98
+ >
99
+ < BackdropBlur />
100
+ < nav className = "mx-auto flex h-[var(--nextra-navbar-height)] max-w-[90rem] items-center justify-end pl-[max(env(safe-area-inset-left),1.5rem)] pr-[max(env(safe-area-inset-right),1.5rem)]" >
103
101
{ themeConfig . logoLink ? (
104
102
< NextLink
105
103
href = {
106
104
typeof themeConfig . logoLink === "string"
107
105
? themeConfig . logoLink
108
106
: "/"
109
107
}
110
- className = "nextra-focus flex items-center hover:opacity-75 ltr:mr-auto rtl:ml-auto "
108
+ className = "nextra-focus flex items-center hover:opacity-75"
111
109
>
112
110
{ renderComponent ( themeConfig . logo ) }
113
111
</ NextLink >
114
112
) : (
115
- < div className = "flex items-center ltr:mr-auto rtl:ml-auto " >
113
+ < div className = "flex items-center" >
116
114
{ renderComponent ( themeConfig . logo ) }
117
115
</ div >
118
116
) }
119
- < div className = "nextra-scrollbar flex gap-4 overflow-x-auto py-1.5" >
117
+ < div className = "flex-1" />
118
+ < div className = "-mx-2 flex overflow-x-auto px-2 py-1.5 lg:gap-2 xl:absolute xl:left-1/2 xl:-translate-x-1/2" >
120
119
{ items . map ( pageOrMenu => {
121
120
if ( pageOrMenu . display === "hidden" ) return null
122
121
@@ -145,12 +144,10 @@ export function Navbar({ items }: NavBarProps): ReactElement {
145
144
< Anchor
146
145
href = { href }
147
146
key = { href }
148
- className = { cn (
147
+ className = { clsx (
149
148
classes . link ,
150
- "max-md:_hidden _whitespace-nowrap _ring-inset" ,
151
- ! isActive || page . newWindow
152
- ? classes . inactive
153
- : classes . active ,
149
+ "whitespace-nowrap max-md:hidden" ,
150
+ isActive && ! page . newWindow && "font-medium" ,
154
151
) }
155
152
target = { page . newWindow ? "_blank" : undefined }
156
153
aria-current = { ! page . newWindow && isActive }
@@ -183,16 +180,63 @@ export function Navbar({ items }: NavBarProps): ReactElement {
183
180
< Button
184
181
aria-label = "Menu"
185
182
className = { ( { active } ) =>
186
- cn (
187
- "nextra-hamburger _rounded md:_hidden " ,
188
- active && "_bg-gray -400/20" ,
183
+ clsx (
184
+ "nextra-hamburger p-2 text-pri-base md:hidden " ,
185
+ active && "bg-neu -400/20" ,
189
186
)
190
187
}
191
188
onClick = { ( ) => setMenu ( ! menu ) }
192
189
>
193
- { menu ? < CloseIcon /> : < MenuIcon /> }
190
+ { menu ? (
191
+ < CloseIcon className = "size-5" />
192
+ ) : (
193
+ < MenuIcon className = "size-5" />
194
+ ) }
194
195
</ Button >
195
196
</ nav >
196
197
</ div >
197
198
)
198
199
}
200
+
201
+ function BackdropBlur ( ) {
202
+ const mask = "linear-gradient(to bottom, #000 0% 50%, transparent 50% 100%)"
203
+ const thickness = "1px"
204
+ return (
205
+ < >
206
+ < div
207
+ // note: we can't use the background trick to reduce flickering, because we have many section
208
+ // background colors and big images, so we'd have to change the --bg var with javascript
209
+ className = "pointer-events-none absolute inset-0 -z-10 h-[200%] backdrop-blur-[12.8px]"
210
+ style = { {
211
+ maskImage : mask ,
212
+ WebkitMaskImage : mask ,
213
+ background :
214
+ "linear-gradient(to bottom,rgb(var(--nextra-bg),.97) 0%, rgb(var(--nextra-bg),.5) 50% 100%)" ,
215
+ } }
216
+ />
217
+ < div
218
+ className = "pointer-events-none absolute inset-0 h-full translate-y-full bg-white/10"
219
+ style = { {
220
+ backdropFilter : "blur(8px) brightness(120%) saturate(113%)" ,
221
+ maskImage : `linear-gradient(to bottom, black 0, black ${ thickness } , transparent ${ thickness } )` ,
222
+ } }
223
+ />
224
+ </ >
225
+ )
226
+ }
227
+
228
+ export function NavbarPlaceholder ( {
229
+ className,
230
+ ...rest
231
+ } : React . HTMLAttributes < HTMLDivElement > ) {
232
+ return (
233
+ < div
234
+ // placeholder: the colors here on `before` must match the ones on Hero `before` strip
235
+ className = { clsx (
236
+ "absolute h-[calc(var(--nextra-navbar-height)+1px)] w-full before:absolute before:top-0 before:h-[calc(var(--nextra-navbar-height)+1px)] before:w-full" ,
237
+ className ,
238
+ ) }
239
+ { ...rest }
240
+ />
241
+ )
242
+ }
0 commit comments