Refine mobile navigation, header controls, and branding

This commit is contained in:
Rami Bitar
2026-09-16 14:56:27 -04:00
parent 1eedca17d8
commit 9e6dbfd721
5 changed files with 93 additions and 92 deletions
+34 -40
View File
@@ -1,19 +1,17 @@
"use client" "use client"
import { useEffect, useRef, useState } from "react" import { useEffect, useRef, useState } from "react"
import { RiArrowRightLine, RiCloseLine, RiMenu3Line } from "@remixicon/react" import { RiCloseLine, RiMenuLine } from "@remixicon/react"
import { Dialog as DialogPrimitive } from "radix-ui"
import { Brand } from "@/components/shared/brand" import { Brand } from "@/components/shared/brand"
import { import {
Dialog, Sheet,
DialogClose, SheetClose,
DialogDescription, SheetContent,
DialogOverlay, SheetDescription,
DialogPortal, SheetTitle,
DialogTitle, SheetTrigger,
DialogTrigger, } from "@/components/ui/sheet"
} from "@/components/ui/dialog"
type NavigationItem = { type NavigationItem = {
label: string label: string
@@ -36,67 +34,65 @@ export function MobileNavigation({ items }: { items: NavigationItem[] }) {
}, []) }, [])
return ( return (
<Dialog open={open} onOpenChange={setOpen}> <Sheet open={open} onOpenChange={setOpen}>
<DialogTrigger asChild> <SheetTrigger asChild>
<button <button
type="button" type="button"
aria-label="Open navigation menu" aria-label="Open navigation menu"
className="inline-flex size-11 items-center justify-center rounded-sm border bg-background/80 text-foreground transition-colors hover:border-blue-500/40 hover:bg-blue-600/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring md:hidden" className="inline-flex size-11 items-center justify-center rounded-sm bg-background/80 text-foreground transition-colors hover:bg-blue-600/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring md:hidden"
> >
<RiMenu3Line className="size-5" aria-hidden="true" /> <RiMenuLine className="size-5" aria-hidden="true" />
</button> </button>
</DialogTrigger> </SheetTrigger>
<DialogPortal> <SheetContent
<DialogOverlay className="z-[60] bg-black/45 duration-200 supports-backdrop-filter:backdrop-blur-md md:hidden" /> side="top"
<DialogPrimitive.Content showCloseButton={false}
className="fixed inset-x-3 top-3 z-[61] mx-auto flex max-h-[calc(100dvh-1.5rem)] max-w-lg flex-col overflow-hidden rounded-2xl border bg-background text-foreground shadow-[0_24px_100px_rgba(0,0,0,.35)] outline-none duration-200 data-open:animate-in data-open:fade-in-0 data-open:slide-in-from-top-4 data-closed:animate-out data-closed:fade-out-0 data-closed:slide-out-to-top-4 md:hidden" overlayClassName="z-[60] bg-black/45 duration-300 supports-backdrop-filter:backdrop-blur-md md:hidden"
className="z-[61] max-h-dvh w-full gap-0 overflow-hidden rounded-none bg-background text-foreground 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) => { onCloseAutoFocus={(event) => {
// Let the anchor move focus to its section without the trigger // Let the anchor move focus to its section without the trigger
// interrupting the page's smooth scroll when the dialog closes. // interrupting the page's smooth scroll when the sheet closes.
if (navigating.current) event.preventDefault() if (navigating.current) event.preventDefault()
navigating.current = false navigating.current = false
}} }}
> >
<div className="flex shrink-0 items-center justify-between border-b bg-linear-to-r from-blue-600/10 via-blue-600/5 to-transparent px-5 py-3"> <div className="flex shrink-0 items-center justify-between border-b bg-linear-to-r from-blue-600/10 via-blue-600/5 to-transparent px-5 py-3">
<Brand /> <Brand />
<DialogClose asChild> <SheetClose asChild>
<button <button
type="button" type="button"
aria-label="Close navigation menu" aria-label="Close navigation menu"
className="inline-flex size-11 items-center justify-center rounded-full border bg-background/70 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring" className="inline-flex size-11 items-center justify-center rounded-sm bg-background/70 text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
> >
<RiCloseLine className="size-5" aria-hidden="true" /> <RiCloseLine className="size-5" aria-hidden="true" />
</button> </button>
</DialogClose> </SheetClose>
</div> </div>
<div className="min-h-0 overflow-y-auto overscroll-contain p-3"> <div className="min-h-0 overflow-y-auto overscroll-contain p-3">
<DialogTitle className="px-3 pb-2 pt-3 font-mono text-[10px] font-medium tracking-[0.18em] text-muted-foreground uppercase"> <SheetTitle className="px-3 pb-2 pt-3 font-mono text-[10px] font-medium tracking-[0.18em] text-muted-foreground uppercase">
Explore Vertex Explore Vertex
</DialogTitle> </SheetTitle>
<DialogDescription className="sr-only"> <SheetDescription className="sr-only">
Explore our products, models, features, pricing, and developer documentation. Explore our products, models, features, pricing, and developer documentation.
</DialogDescription> </SheetDescription>
<nav aria-label="Mobile navigation"> <nav aria-label="Mobile navigation">
<ul className="space-y-1"> <ul className="space-y-1">
{items.map(({ label, href, description }) => ( {items.map(({ label, href, description }) => (
<li key={label}> <li key={label}>
<DialogClose asChild> <SheetClose asChild>
<a <a
href={href} href={href}
onClick={() => { navigating.current = true }} onClick={() => { navigating.current = true }}
className="group flex items-center justify-between gap-4 rounded-xl px-3 py-3 transition-colors hover:bg-blue-600/8 focus-visible:bg-blue-600/8 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring" className="flex items-center rounded-xl px-3 py-3 transition-colors hover:bg-blue-600/8 focus-visible:bg-blue-600/8 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring"
> >
<span className="min-w-0"> <span className="min-w-0">
<span className="block text-xl font-medium tracking-tight">{label}</span> <span className="block text-xl font-medium tracking-tight">{label}</span>
<span className="mt-0.5 block text-xs leading-5 text-muted-foreground">{description}</span> <span className="mt-0.5 block text-xs leading-5 text-muted-foreground">{description}</span>
</span> </span>
<span className="flex size-8 shrink-0 items-center justify-center rounded-full border text-muted-foreground transition-colors group-hover:border-blue-500/30 group-hover:bg-blue-600/10 group-hover:text-blue-600 dark:group-hover:text-blue-400">
<RiArrowRightLine className="size-4" aria-hidden="true" />
</span>
</a> </a>
</DialogClose> </SheetClose>
</li> </li>
))} ))}
</ul> </ul>
@@ -105,19 +101,17 @@ export function MobileNavigation({ items }: { items: NavigationItem[] }) {
<div className="shrink-0 border-t bg-muted/35 p-5"> <div className="shrink-0 border-t bg-muted/35 p-5">
<p className="mb-3 text-xs text-muted-foreground">One API. Every AI model.</p> <p className="mb-3 text-xs text-muted-foreground">One API. Every AI model.</p>
<DialogClose asChild> <SheetClose asChild>
<a <a
href="#pricing" href="#pricing"
onClick={() => { navigating.current = true }} onClick={() => { navigating.current = true }}
className="flex h-12 items-center justify-between rounded-lg bg-blue-600 px-4 text-sm font-medium text-white shadow-[0_6px_24px_rgba(23,61,166,.2)] transition-colors hover:bg-blue-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background" className="flex h-12 items-center justify-center rounded-lg bg-blue-600 px-4 text-sm font-medium text-white shadow-[0_6px_24px_rgba(23,61,166,.2)] transition-colors hover:bg-blue-500 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
> >
Get started Get started
<RiArrowRightLine className="size-4" aria-hidden="true" />
</a> </a>
</DialogClose> </SheetClose>
</div> </div>
</DialogPrimitive.Content> </SheetContent>
</DialogPortal> </Sheet>
</Dialog>
) )
} }
+7 -3
View File
@@ -4,6 +4,8 @@ import { RiMoonLine, RiSunLine } from "@remixicon/react"
import { useTheme } from "next-themes" import { useTheme } from "next-themes"
import { useSyncExternalStore } from "react" import { useSyncExternalStore } from "react"
import { Button } from "@/components/ui/button"
const emptySubscribe = () => () => {} const emptySubscribe = () => () => {}
export function ThemeToggle() { export function ThemeToggle() {
@@ -13,10 +15,12 @@ export function ThemeToggle() {
const isDark = mounted && resolvedTheme === "dark" const isDark = mounted && resolvedTheme === "dark"
return ( return (
<button <Button
type="button" type="button"
variant="ghost"
size="icon-lg"
aria-label={mounted ? `Switch to ${isDark ? "light" : "dark"} theme` : "Toggle theme"} aria-label={mounted ? `Switch to ${isDark ? "light" : "dark"} theme` : "Toggle theme"}
className="inline-flex size-9 items-center justify-center rounded-sm border bg-background/80 text-muted-foreground transition-colors hover:border-foreground/20 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-500/60" className="text-muted-foreground"
onClick={() => setTheme(isDark ? "light" : "dark")} onClick={() => setTheme(isDark ? "light" : "dark")}
> >
{mounted ? ( {mounted ? (
@@ -24,6 +28,6 @@ export function ThemeToggle() {
) : ( ) : (
<span className="size-4" aria-hidden="true" /> <span className="size-4" aria-hidden="true" />
)} )}
</button> </Button>
) )
} }
+1 -1
View File
@@ -9,7 +9,7 @@ export function HeroSection() {
<div className="relative z-10 mx-auto max-w-4xl"> <div className="relative z-10 mx-auto max-w-4xl">
<a <a
href="#features" href="#features"
className="mb-7 inline-flex items-center justify-center rounded-full border border-blue-600/15 bg-gradient-to-br from-white/80 via-blue-600/10 to-blue-600/20 px-5 py-2 text-xs font-medium text-foreground shadow-[inset_0_1px_0_rgba(255,255,255,.6),0_4px_20px_rgba(23,61,166,.1)] backdrop-blur-xl transition-colors hover:border-blue-600/35 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background dark:border-white/15 dark:from-white/10 dark:via-blue-600/15 dark:to-blue-600/30 dark:shadow-[inset_0_1px_0_rgba(255,255,255,.12),0_4px_20px_rgba(23,61,166,.15)] dark:hover:border-white/30" className="mb-7 inline-flex items-center justify-center rounded-full border border-blue-600/25 bg-clip-padding bg-gradient-to-br from-white/80 via-blue-600/10 to-blue-600/20 px-5 py-2 text-xs font-medium text-foreground shadow-[inset_0_1px_0_rgba(255,255,255,.6),0_4px_20px_rgba(23,61,166,.1)] backdrop-blur-xl transition-colors hover:border-blue-600/35 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background dark:border-white/15 dark:from-white/10 dark:via-blue-600/15 dark:to-blue-600/30 dark:shadow-[inset_0_1px_0_rgba(255,255,255,.12),0_4px_20px_rgba(23,61,166,.15)] dark:hover:border-white/30"
> >
The unified AI gateway The unified AI gateway
</a> </a>
+5 -2
View File
@@ -5,6 +5,7 @@ import { Dialog as SheetPrimitive } from "radix-ui"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { DialogPortal } from "@/components/ui/dialog"
import { RiCloseLine } from "@remixicon/react" import { RiCloseLine } from "@remixicon/react"
function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) { function Sheet({ ...props }: React.ComponentProps<typeof SheetPrimitive.Root>) {
@@ -26,7 +27,7 @@ function SheetClose({
function SheetPortal({ function SheetPortal({
...props ...props
}: React.ComponentProps<typeof SheetPrimitive.Portal>) { }: React.ComponentProps<typeof SheetPrimitive.Portal>) {
return <SheetPrimitive.Portal data-slot="sheet-portal" {...props} /> return <DialogPortal data-slot="sheet-portal" {...props} />
} }
function SheetOverlay({ function SheetOverlay({
@@ -50,14 +51,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}
+1 -1
View File
@@ -2,7 +2,7 @@
<g transform="rotate(45 12 12)"> <g transform="rotate(45 12 12)">
<path d="M13.236 2.377 2.751 20.493H0L11.863 0l1.373 2.377Z"/> <path d="M13.236 2.377 2.751 20.493H0L11.863 0l1.373 2.377Z"/>
<path d="M16.790 8.533 7.184 20.493H4.130L15.511 6.320l1.279 2.212Z"/> <path d="M16.790 8.533 7.184 20.493H4.130L15.511 6.320l1.279 2.212Z"/>
<path d="M20.278 14.571 12.435 20.491H8.482L19.079 12.494l1.2 2.077Z"/>
</g> </g>
<path d="M4.070494 16.081410 L16.928016 16.338560 L16.341956 18.526839 L6.315392 18.326308 Z"/>
<path d="M7.444111 19.455027 L15.518911 21.600000 L14.266277 26.277193 Z"/> <path d="M7.444111 19.455027 L15.518911 21.600000 L14.266277 26.277193 Z"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 447 B

After

Width:  |  Height:  |  Size: 469 B