Skip to content

Commit f9132e7

Browse files
authored
Merge pull request #29 from hasparus/new-landing--navbar
new landing — navbar
2 parents 6c8e45e + 4161131 commit f9132e7

File tree

9 files changed

+332
-81
lines changed

9 files changed

+332
-81
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@
4848
"next-query-params": "^5.0.1",
4949
"next-sitemap": "^4.2.3",
5050
"next-with-less": "^3.0.1",
51-
"nextra": "^3",
52-
"nextra-theme-docs": "^3",
51+
"nextra": "3.3.1",
52+
"nextra-theme-docs": "3.3.1",
5353
"numbro": "2.5.0",
5454
"p-limit": "^4.0.0",
5555
"parser-front-matter": "1.6.4",

pnpm-lock.yaml

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/index-page/hero.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import logoBlurred from "./hero/logo-blurred.png"
77

88
export function Hero() {
99
return (
10-
<div className="relative bg-neu-0">
10+
<div className="relative bg-neu-50 dark:bg-neu-0">
1111
<div className="gql-conf-container flex flex-col-reverse lg:grid lg:grid-cols-2">
1212
<div className="flex max-w-4xl flex-col justify-center gap-4 p-4 lg:min-h-[800px] xl:gap-8 xl:py-24 xl:pl-24 xl:pr-10">
1313
<h1 className="typography-h1 max-w-3xl text-neu-900">
@@ -44,7 +44,7 @@ function HeroStripes() {
4444
<div className="pointer-events-none relative overflow-hidden max-lg:h-[210px]">
4545
<ImageLoaded
4646
image={logoBlurred}
47-
className="relative h-full bg-gradient-to-b from-pri-base to-pri-lighter opacity-0 transition-opacity duration-[1.5s] [mask-position:center_12%] [mask-size:110%] data-[loaded=true]:opacity-100 dark:to-pri-base lg:[mask-position:7%_7%] lg:[mask-size:200%]"
47+
className="relative h-full bg-gradient-to-b from-pri-base to-pri-lighter opacity-0 transition-opacity duration-[1.5s] [mask-position:center_12%] [mask-size:110%] data-[loaded=true]:opacity-100 dark:to-pri-base sm:[mask-size:auto_300%] lg:[mask-position:7%_7%] lg:[mask-size:200%]"
4848
style={{
4949
maskImage: `url(${logoBlurred.src})`,
5050
maskRepeat: "no-repeat",

src/components/navbar/navbar.tsx

Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
import { MenuItem, Menu, MenuButton, MenuItems } from "@headlessui/react"
2+
import clsx from "clsx"
3+
// eslint-disable-next-line no-restricted-imports -- since we don't need newWindow prop
4+
import NextLink from "next/link"
5+
import { Button } from "nextra/components"
6+
import { useFSRoute } from "nextra/hooks"
7+
import type * as normalizePages from "nextra/normalize-pages"
8+
import { Fragment, useState, type ReactElement, type ReactNode } from "react"
9+
import { useMenu, useThemeConfig } from "nextra-theme-docs"
10+
import { Anchor } from "@/app/conf/_design-system/anchor"
11+
import { renderComponent } from "@/components/utils"
12+
13+
import MenuIcon from "@/app/conf/_design-system/pixelarticons/menu.svg?svgr"
14+
import CloseIcon from "@/app/conf/_design-system/pixelarticons/close.svg?svgr"
15+
16+
type Item = normalizePages.PageItem | normalizePages.MenuItem
17+
export interface NavBarProps {
18+
items: Item[]
19+
}
20+
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 hover:underline underline-offset-2",
23+
}
24+
25+
function NavbarMenu({
26+
menu,
27+
children,
28+
onSubmenuOpen,
29+
}: {
30+
menu: normalizePages.MenuItem
31+
children: ReactNode
32+
onSubmenuOpen: (open: boolean) => void
33+
}): ReactElement {
34+
const routes = Object.fromEntries(
35+
(menu.children || []).map(route => [route.name, route]),
36+
)
37+
return (
38+
<Menu>
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>
56+
)
57+
}}
58+
</MenuButton>
59+
<MenuItems
60+
transition
61+
modal={false}
62+
className={({ open }) =>
63+
// eslint-disable-next-line tailwindcss/no-custom-classname
64+
clsx(
65+
"gql-navbar-menu-items",
66+
"motion-reduce:transition-none",
67+
"focus-visible:outline-none",
68+
open ? "opacity-100" : "opacity-0",
69+
"nextra-scrollbar overflow-visible transition-opacity",
70+
"z-20 rounded-md py-1 text-sm",
71+
// headlessui adds max-height as style, use !important to override
72+
"!max-h-[min(calc(100vh-5rem),256px)]",
73+
)
74+
}
75+
anchor={{ to: "top start", gap: 21, padding: 16, offset: -8 }}
76+
>
77+
{Object.entries(menu.items || {}).map(([key, item]) => (
78+
<MenuItem key={key}>
79+
<Anchor
80+
href={item.href || routes[key]?.route}
81+
className="block py-1.5 pl-2 pr-9"
82+
target={item.newWindow ? "_blank" : undefined}
83+
>
84+
<span className="typography-menu px-3 py-1 underline-offset-2 [[data-active]>&]:underline">
85+
{item.title}
86+
</span>
87+
</Anchor>
88+
</MenuItem>
89+
))}
90+
</MenuItems>
91+
</Menu>
92+
)
93+
}
94+
95+
export function Navbar({ items }: NavBarProps): ReactElement {
96+
const themeConfig = useThemeConfig()
97+
98+
const activeRoute = useFSRoute()
99+
const { menu, setMenu } = useMenu()
100+
const [submenuOpen, setSubmenuOpen] = useState(false)
101+
102+
return (
103+
<div
104+
className={clsx(
105+
"nextra-nav-container top-0 z-20 w-full bg-transparent print:hidden",
106+
activeRoute === "/" ? "fixed" : "sticky",
107+
)}
108+
>
109+
<BackdropBlur />
110+
<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)]">
111+
{themeConfig.logoLink ? (
112+
<NextLink
113+
href={
114+
typeof themeConfig.logoLink === "string"
115+
? themeConfig.logoLink
116+
: "/"
117+
}
118+
className="nextra-focus flex items-center hover:opacity-75"
119+
>
120+
{renderComponent(themeConfig.logo)}
121+
</NextLink>
122+
) : (
123+
<div className="flex items-center">
124+
{renderComponent(themeConfig.logo)}
125+
</div>
126+
)}
127+
<div className="flex-1" />
128+
<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">
129+
{items.map(pageOrMenu => {
130+
if (pageOrMenu.display === "hidden") return null
131+
132+
if (pageOrMenu.type === "menu") {
133+
const menu = pageOrMenu as normalizePages.MenuItem
134+
return (
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+
>
149+
{menu.title}
150+
</NavbarMenu>
151+
)
152+
}
153+
const page = pageOrMenu as normalizePages.PageItem
154+
let href = page.href || page.route || "#"
155+
156+
// If it's a directory
157+
if (page.children) {
158+
href =
159+
(page.withIndexPage ? page.route : page.firstChildRoute) || href
160+
}
161+
162+
const isActive =
163+
page.route === activeRoute ||
164+
activeRoute.startsWith(page.route + "/")
165+
166+
return (
167+
<Anchor
168+
href={href}
169+
key={href}
170+
className={clsx(
171+
classes.link,
172+
"whitespace-nowrap max-md:hidden",
173+
isActive && !page.newWindow && "underline",
174+
)}
175+
target={page.newWindow ? "_blank" : undefined}
176+
aria-current={!page.newWindow && isActive}
177+
>
178+
{page.title}
179+
</Anchor>
180+
)
181+
})}
182+
</div>
183+
184+
{process.env.NEXTRA_SEARCH &&
185+
renderComponent(themeConfig.search.component, {
186+
className: "max-md:_hidden",
187+
})}
188+
189+
{themeConfig.project.link ? (
190+
<Anchor href={themeConfig.project.link}>
191+
{renderComponent(themeConfig.project.icon)}
192+
</Anchor>
193+
) : null}
194+
195+
{themeConfig.chat.link ? (
196+
<Anchor href={themeConfig.chat.link}>
197+
{renderComponent(themeConfig.chat.icon)}
198+
</Anchor>
199+
) : null}
200+
201+
{renderComponent(themeConfig.navbar.extraContent)}
202+
203+
<Button
204+
aria-label="Menu"
205+
className={({ active }) =>
206+
clsx(
207+
"nextra-hamburger p-2 text-pri-base md:hidden",
208+
active && "bg-neu-400/20",
209+
)
210+
}
211+
onClick={() => setMenu(!menu)}
212+
>
213+
{menu ? (
214+
<CloseIcon className="size-5" />
215+
) : (
216+
<MenuIcon className="size-5" />
217+
)}
218+
</Button>
219+
</nav>
220+
<SubmenuBackdrop className={submenuOpen ? "opacity-100" : "opacity-0"} />
221+
</div>
222+
)
223+
}
224+
225+
function BackdropBlur() {
226+
const mask = "linear-gradient(to bottom, #000 0% 50%, transparent 50% 100%)"
227+
const thickness = "1px"
228+
return (
229+
<>
230+
<div
231+
// note: we can't use the background trick to reduce flickering, because we have many section
232+
// background colors and big images, so we'd have to change the --bg var with javascript
233+
className="pointer-events-none absolute inset-0 -z-10 h-[200%] backdrop-blur-[12.8px]"
234+
style={{
235+
maskImage: mask,
236+
WebkitMaskImage: mask,
237+
background:
238+
"linear-gradient(to bottom,rgb(var(--nextra-bg),.97) 0%, rgb(var(--nextra-bg),.5) 50% 100%)",
239+
}}
240+
/>
241+
<div
242+
className="pointer-events-none absolute inset-0 h-full translate-y-full bg-white/10"
243+
style={{
244+
backdropFilter: "blur(8px) brightness(120%) saturate(113%)",
245+
maskImage: `linear-gradient(to bottom, black 0, black ${thickness}, transparent ${thickness})`,
246+
}}
247+
/>
248+
</>
249+
)
250+
}
251+
252+
export function NavbarPlaceholder({
253+
className,
254+
...rest
255+
}: React.HTMLAttributes<HTMLDivElement>) {
256+
return (
257+
<div
258+
// placeholder: the colors here on `before` must match the ones on Hero `before` strip
259+
className={clsx(
260+
"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",
261+
className,
262+
)}
263+
{...rest}
264+
/>
265+
)
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+
}

src/components/utils.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export function renderComponent<T>(
2+
ComponentOrNode: React.FC<T> | React.ReactNode,
3+
props?: T,
4+
) {
5+
if (!ComponentOrNode) return null
6+
if (typeof ComponentOrNode !== "function") return ComponentOrNode
7+
return <ComponentOrNode {...props!} />
8+
}

src/globals.css

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,6 @@
33
@import "tailwindcss/utilities";
44
@import "tailwindcss/components";
55

6-
/* #region nextra tweaks (preferably removed later, replaced with components) */
7-
.nextra-nav-container > nav > div:nth-child(2) {
8-
margin-right: auto;
9-
}
10-
/* #endregion nextra tweaks */
11-
126
:root {
137
--foreground-rgb: 0, 0, 0;
148
--background-start-rgb: 214, 219, 220;
@@ -517,10 +511,15 @@ div[id^="headlessui-menu-items"] {
517511
@apply px-4 py-8 lg:px-12 xl:gap-x-24 xl:px-24 3xl:px-[240px];
518512
}
519513

514+
.gql-navbar-strip,
520515
.gql-conf-navbar-strip {
521516
@apply relative [contain:paint] before:sticky before:top-0 before:z-[9] before:-mt-[var(--navbar-h)] before:block before:h-[var(--navbar-h)] before:w-full before:content-[''];
522517
}
523518

519+
.gql-navbar-strip {
520+
--navbar-h: var(--nextra-navbar-height);
521+
}
522+
524523
:root {
525524
--navbar-h: 70px;
526525
}

0 commit comments

Comments
 (0)