Template
Add mobile navigation menu to navbar
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5.1
parent
b4d7778be2
commit
bea4c4a1b9
@@ -0,0 +1,135 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect, useRef, useState } from 'react';
|
||||||
|
import { RiCloseLine, RiMenuLine } from '@remixicon/react';
|
||||||
|
|
||||||
|
import { Logo } from '@/components/site/logo';
|
||||||
|
import {
|
||||||
|
Sheet,
|
||||||
|
SheetClose,
|
||||||
|
SheetContent,
|
||||||
|
SheetDescription,
|
||||||
|
SheetTitle,
|
||||||
|
SheetTrigger,
|
||||||
|
} from '@/components/ui/sheet';
|
||||||
|
|
||||||
|
export type NavigationItem = {
|
||||||
|
label: string;
|
||||||
|
href: string;
|
||||||
|
description: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function MobileNavigation({ items }: { items: NavigationItem[] }) {
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const navigating = useRef(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const desktop = window.matchMedia('(min-width: 768px)');
|
||||||
|
const closeOnDesktop = () => {
|
||||||
|
if (desktop.matches) setOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
desktop.addEventListener('change', closeOnDesktop);
|
||||||
|
return () => desktop.removeEventListener('change', closeOnDesktop);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Sheet open={open} onOpenChange={setOpen}>
|
||||||
|
<SheetTrigger asChild>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
aria-label="Open navigation menu"
|
||||||
|
className="inline-flex size-10 items-center justify-center rounded-md text-white/70 transition-colors hover:bg-white/5 hover:text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/30 md:hidden"
|
||||||
|
>
|
||||||
|
<RiMenuLine className="size-5" aria-hidden="true" />
|
||||||
|
</button>
|
||||||
|
</SheetTrigger>
|
||||||
|
|
||||||
|
<SheetContent
|
||||||
|
side="top"
|
||||||
|
showCloseButton={false}
|
||||||
|
overlayClassName="z-[60] bg-black/60 duration-300 supports-backdrop-filter:backdrop-blur-md md:hidden"
|
||||||
|
className="z-[61] max-h-dvh w-full gap-0 overflow-hidden rounded-none border-white/10 bg-black text-white outline-none duration-300 data-[side=top]:data-open:slide-in-from-top data-[side=top]:data-closed:slide-out-to-top md:hidden"
|
||||||
|
onCloseAutoFocus={(event) => {
|
||||||
|
// Let the anchor move focus to its section without the trigger
|
||||||
|
// interrupting the page's smooth scroll when the sheet closes.
|
||||||
|
if (navigating.current) event.preventDefault();
|
||||||
|
navigating.current = false;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex h-16 shrink-0 items-center justify-between border-b border-white/10 px-6">
|
||||||
|
<Logo />
|
||||||
|
<SheetClose asChild>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
aria-label="Close navigation menu"
|
||||||
|
className="inline-flex size-10 items-center justify-center rounded-md text-white/60 transition-colors hover:bg-white/5 hover:text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/30"
|
||||||
|
>
|
||||||
|
<RiCloseLine className="size-5" aria-hidden="true" />
|
||||||
|
</button>
|
||||||
|
</SheetClose>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="min-h-0 overflow-y-auto overscroll-contain p-3">
|
||||||
|
<SheetTitle className="px-3 pb-2 pt-3 text-[10px] font-medium uppercase tracking-[0.2em] text-white/40">
|
||||||
|
Explore Vertex
|
||||||
|
</SheetTitle>
|
||||||
|
<SheetDescription className="sr-only">
|
||||||
|
Explore our products, solutions, resources, documentation, and pricing.
|
||||||
|
</SheetDescription>
|
||||||
|
<nav aria-label="Mobile navigation">
|
||||||
|
<ul className="space-y-1">
|
||||||
|
{items.map(({ label, href, description }) => (
|
||||||
|
<li key={label}>
|
||||||
|
<SheetClose asChild>
|
||||||
|
<a
|
||||||
|
href={href}
|
||||||
|
onClick={() => {
|
||||||
|
navigating.current = true;
|
||||||
|
}}
|
||||||
|
className="flex items-center rounded-xl px-3 py-3 transition-colors hover:bg-white/5 focus-visible:bg-white/5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-white/30"
|
||||||
|
>
|
||||||
|
<span className="min-w-0">
|
||||||
|
<span className="block text-xl font-medium tracking-tight">
|
||||||
|
{label}
|
||||||
|
</span>
|
||||||
|
<span className="mt-0.5 block text-xs leading-5 text-white/45">
|
||||||
|
{description}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</a>
|
||||||
|
</SheetClose>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="shrink-0 space-y-2 border-t border-white/10 bg-white/[0.02] p-5">
|
||||||
|
<SheetClose asChild>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
onClick={() => {
|
||||||
|
navigating.current = true;
|
||||||
|
}}
|
||||||
|
className="flex h-12 items-center justify-center rounded-full bg-white px-4 text-sm font-medium text-black transition-colors hover:bg-white/90 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/30 focus-visible:ring-offset-2 focus-visible:ring-offset-black"
|
||||||
|
>
|
||||||
|
Sign Up
|
||||||
|
</a>
|
||||||
|
</SheetClose>
|
||||||
|
<SheetClose asChild>
|
||||||
|
<a
|
||||||
|
href="#"
|
||||||
|
onClick={() => {
|
||||||
|
navigating.current = true;
|
||||||
|
}}
|
||||||
|
className="flex h-12 items-center justify-center rounded-full border border-white/10 px-4 text-sm font-medium text-white/70 transition-colors hover:bg-white/5 hover:text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/30"
|
||||||
|
>
|
||||||
|
Log In
|
||||||
|
</a>
|
||||||
|
</SheetClose>
|
||||||
|
</div>
|
||||||
|
</SheetContent>
|
||||||
|
</Sheet>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,7 +1,14 @@
|
|||||||
import { Logo } from '@/components/site/logo';
|
import { Logo } from '@/components/site/logo';
|
||||||
|
import { MobileNavigation } from '@/components/site/mobile-navigation';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
|
|
||||||
const links = ['Products', 'Solutions', 'Resources', 'Docs', 'Pricing'];
|
const navigationItems = [
|
||||||
|
{ label: 'Products', href: '#', description: 'Everything you need to ship' },
|
||||||
|
{ label: 'Solutions', href: '#', description: 'Built for teams of every size' },
|
||||||
|
{ label: 'Resources', href: '#', description: 'Guides, templates, and changelog' },
|
||||||
|
{ label: 'Docs', href: '#', description: 'Your first deploy starts here' },
|
||||||
|
{ label: 'Pricing', href: '#', description: 'Start free. Scale at your pace.' },
|
||||||
|
];
|
||||||
|
|
||||||
export function Navbar() {
|
export function Navbar() {
|
||||||
return (
|
return (
|
||||||
@@ -10,13 +17,13 @@ export function Navbar() {
|
|||||||
<div className="flex items-center gap-8">
|
<div className="flex items-center gap-8">
|
||||||
<Logo />
|
<Logo />
|
||||||
<ul className="hidden items-center gap-6 md:flex">
|
<ul className="hidden items-center gap-6 md:flex">
|
||||||
{links.map((link) => (
|
{navigationItems.map(({ label, href }) => (
|
||||||
<li key={link}>
|
<li key={label}>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href={href}
|
||||||
className="text-sm text-white/60 transition-colors hover:text-white"
|
className="text-sm text-white/60 transition-colors hover:text-white"
|
||||||
>
|
>
|
||||||
{link}
|
{label}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
@@ -34,10 +41,11 @@ export function Navbar() {
|
|||||||
<Button
|
<Button
|
||||||
asChild
|
asChild
|
||||||
size="lg"
|
size="lg"
|
||||||
className="rounded-full bg-white text-black hover:bg-white/90"
|
className="hidden rounded-full bg-white text-black hover:bg-white/90 sm:inline-flex"
|
||||||
>
|
>
|
||||||
<a href="#">Sign Up</a>
|
<a href="#">Sign Up</a>
|
||||||
</Button>
|
</Button>
|
||||||
|
<MobileNavigation items={navigationItems} />
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
@@ -50,14 +50,16 @@ function SheetContent({
|
|||||||
children,
|
children,
|
||||||
side = "right",
|
side = "right",
|
||||||
showCloseButton = true,
|
showCloseButton = true,
|
||||||
|
overlayClassName,
|
||||||
...props
|
...props
|
||||||
}: React.ComponentProps<typeof SheetPrimitive.Content> & {
|
}: React.ComponentProps<typeof SheetPrimitive.Content> & {
|
||||||
side?: "top" | "right" | "bottom" | "left"
|
side?: "top" | "right" | "bottom" | "left"
|
||||||
showCloseButton?: boolean
|
showCloseButton?: boolean
|
||||||
|
overlayClassName?: string
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<SheetPortal>
|
<SheetPortal>
|
||||||
<SheetOverlay />
|
<SheetOverlay className={overlayClassName} />
|
||||||
<SheetPrimitive.Content
|
<SheetPrimitive.Content
|
||||||
data-slot="sheet-content"
|
data-slot="sheet-content"
|
||||||
data-side={side}
|
data-side={side}
|
||||||
|
|||||||
Reference in New Issue
Block a user