Mobile Menu
Full-screen mobile menu with large numbered links, arrow icons, contact footer, and CTA button.
| Prop | Type | Default |
|---|---|---|
| open | boolean | false |
| onClose | () => void | — |
Company Name.
1-800-000-0000
hello@example.com
import { useState } from "react";
import { MobileMenu } from "@/extensions/layout";
import { IconButton } from "@/ui";
import { MenuIcon } from "@/ui/icons";
const [open, setOpen] = useState(false);
<IconButton
variant="ghost"
color="root"
onClick={() => setOpen(true)}
aria-label="Open menu"
>
<MenuIcon />
</IconButton>
<MobileMenu open={open} onClose={() => setOpen(false)} />import { MobileMenu } from "@/extensions/layout";
<MobileMenu open={open} onClose={() => setOpen(false)} />src/extensions/layout/MobileMenu.tsx
"use client";
import { useEffect } from "react";
import { Link, Button, IconButton, Logo } from "@/ui";
import { ArrowForwardIcon, CloseIcon } from "@/ui/icons";
import { NAV_LINKS, NAV_CTA, CONTACT } from "@/constants";
import { useTheme } from "next-themes";
import { cn } from "@/utils";
interface MobileMenuProps {
open: boolean;
onClose: () => void;
}
export function MobileMenu({ open, onClose }: MobileMenuProps) {
const { resolvedTheme } = useTheme();
useEffect(() => {
if (!open) return;
document.body.style.overflow = "hidden";
return () => {
document.body.style.overflow = "";
};
}, [open]);
if (!open) return null;
return (
<div
className={cn(
"fixed inset-0 z-50 flex flex-col bg-root",
resolvedTheme === "dark" && "dark",
)}
>
<div className="flex items-center justify-between border-b border-divider px-6 py-3 lg:px-8">
<Link href="/" onClick={onClose}>
<Logo />
</Link>
<IconButton
variant="ghost"
color="root"
onClick={onClose}
aria-label="Close menu"
>
<CloseIcon />
</IconButton>
</div>
<nav className="flex flex-1 flex-col justify-center px-8">
<ul className="space-y-1">
{NAV_LINKS.map((link, i) => (
<li key={link.href}>
<Link
href={link.href}
onClick={onClose}
className="group flex items-center justify-between border-b border-divider py-5"
>
<div className="flex items-baseline gap-4">
<span className="text-xs tabular-nums text-root-contrast/30">
{String(i + 1).padStart(2, "0")}
</span>
<span className="text-4xl font-light text-root-contrast transition-colors group-hover:text-primary">
{link.label}
</span>
</div>
<ArrowForwardIcon
size={5}
className="text-root-contrast/30 transition-all group-hover:translate-x-1 group-hover:text-primary"
/>
</Link>
</li>
))}
</ul>
</nav>
<div className="flex items-end justify-between gap-6 border-t border-divider px-8 py-8">
<div className="space-y-1 text-sm text-root-contrast/40">
<p>
<Link
href={CONTACT.phone.href}
className="transition-colors hover:text-root-contrast"
>
{CONTACT.phone.number}
</Link>
</p>
<p>
<Link
href={CONTACT.email.href}
className="transition-colors hover:text-root-contrast"
>
{CONTACT.email.address}
</Link>
</p>
</div>
<Button
href={NAV_CTA.href}
variant="solid"
color="root"
size="sm"
onClick={onClose}
>
{NAV_CTA.label}
</Button>
</div>
</div>
);
}