Skip to content

Commit 47e177f

Browse files
committed
Fight HeadlessUI
1 parent a49550b commit 47e177f

File tree

2 files changed

+61
-26
lines changed

2 files changed

+61
-26
lines changed

src/components/navbar/navbar.tsx

Lines changed: 60 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import NextLink from "next/link"
55
import { Button } from "nextra/components"
66
import { useFSRoute } from "nextra/hooks"
77
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"
99
import { useMenu, useThemeConfig } from "nextra-theme-docs"
1010
import { Anchor } from "@/app/conf/_design-system/anchor"
1111
import { renderComponent } from "@/components/utils"
@@ -19,62 +19,71 @@ export interface NavBarProps {
1919
}
2020

2121
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",
2323
}
2424

2525
function NavbarMenu({
2626
menu,
2727
children,
28+
onSubmenuOpen,
2829
}: {
2930
menu: normalizePages.MenuItem
3031
children: ReactNode
32+
onSubmenuOpen: (open: boolean) => void
3133
}): ReactElement {
3234
const routes = Object.fromEntries(
3335
(menu.children || []).map(route => [route.name, route]),
3436
)
3537
return (
3638
<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>
4356
)
44-
}
45-
>
46-
{children}
57+
}}
4758
</MenuButton>
4859
<MenuItems
4960
transition
50-
portal={false}
5161
modal={false}
5262
className={({ open }) =>
63+
// eslint-disable-next-line tailwindcss/no-custom-classname
5364
clsx(
65+
"gql-navbar-menu-items",
5466
"motion-reduce:transition-none",
5567
"focus-visible:outline-none",
5668
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",
5970
"z-20 rounded-md py-1 text-sm",
6071
// headlessui adds max-height as style, use !important to override
6172
"!max-h-[min(calc(100vh-5rem),256px)]",
6273
)
6374
}
64-
anchor={{ to: "top start", gap: 21, padding: 16 }}
75+
anchor={{ to: "top start", gap: 21, padding: 16, offset: -8 }}
6576
>
6677
{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}>
7279
<Anchor
7380
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"
7582
target={item.newWindow ? "_blank" : undefined}
7683
>
77-
{item.title}
84+
<span className="typography-menu px-3 py-1 underline-offset-2 [[data-active]>&]:underline">
85+
{item.title}
86+
</span>
7887
</Anchor>
7988
</MenuItem>
8089
))}
@@ -88,11 +97,12 @@ export function Navbar({ items }: NavBarProps): ReactElement {
8897

8998
const activeRoute = useFSRoute()
9099
const { menu, setMenu } = useMenu()
100+
const [submenuOpen, setSubmenuOpen] = useState(false)
91101

92102
return (
93103
<div
94104
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",
96106
activeRoute === "/" ? "fixed" : "sticky",
97107
)}
98108
>
@@ -122,7 +132,20 @@ export function Navbar({ items }: NavBarProps): ReactElement {
122132
if (pageOrMenu.type === "menu") {
123133
const menu = pageOrMenu as normalizePages.MenuItem
124134
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+
>
126149
{menu.title}
127150
</NavbarMenu>
128151
)
@@ -147,7 +170,7 @@ export function Navbar({ items }: NavBarProps): ReactElement {
147170
className={clsx(
148171
classes.link,
149172
"whitespace-nowrap max-md:hidden",
150-
isActive && !page.newWindow && "font-medium",
173+
isActive && !page.newWindow && "underline",
151174
)}
152175
target={page.newWindow ? "_blank" : undefined}
153176
aria-current={!page.newWindow && isActive}
@@ -194,6 +217,7 @@ export function Navbar({ items }: NavBarProps): ReactElement {
194217
)}
195218
</Button>
196219
</nav>
220+
<SubmenuBackdrop className={submenuOpen ? "opacity-100" : "opacity-0"} />
197221
</div>
198222
)
199223
}
@@ -240,3 +264,14 @@ export function NavbarPlaceholder({
240264
/>
241265
)
242266
}
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+
}

src/pages/_meta.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ export default {
8181
conf: {
8282
type: "page",
8383
title: (
84-
<span className="[a:has(>&)]:[a:has(>&)]:border [a:has(>&)]:border [a:has(>&)]:border-current [a:has(>&)]:text-pri-base dark:[a:has(>&)]:text-pri-light">
84+
<span className="[a:has(>&)]:[a:has(>&)]:border [a:has(>&)]:border [a:has(>&)]:border-current [a:has(>&)]:text-pri-base dark:[a:has(>&)]:text-pri-light [a:hover:has(>&)]:border-transparent">
8585
GraphQLConf
8686
<span className="max-xl:hidden"> 2025</span>
8787
</span>

0 commit comments

Comments
 (0)