SPB Git forge

spb/rareindex

Public
54commits 1branches 0releases
7.1 MBsize
maindefault branch
10 days agolast push
TypeScript 61.9% HTML 37.2% SQL 0.7%
3.9 KB · 83 lines tsx
Raw Blame History
1'use client';23import Link from 'next/link';4import { useEffect, useState } from 'react';5import { usePathname } from 'next/navigation';6import { ChevronRight, Menu, X } from 'lucide-react';7import type { NavItem } from '@/config/nav';8import { LogoMark } from './logo';910export function MobileNav({ items, groups }: { items: NavItem[]; groups: Array<{ id: NavItem['group']; label: string }> }) {11  const [open, setOpen] = useState(false);12  const pathname = usePathname();13  const [prevPath, setPrevPath] = useState(pathname);14  if (pathname !== prevPath) {15    // close the drawer on navigation (state adjustment during render, no effect needed)16    setPrevPath(pathname);17    setOpen(false);18  }19  useEffect(() => {20    document.body.style.overflow = open ? 'hidden' : '';21    const onKey = (e: KeyboardEvent) => e.key === 'Escape' && setOpen(false);22    window.addEventListener('keydown', onKey);23    return () => {24      document.body.style.overflow = '';25      window.removeEventListener('keydown', onKey);26    };27  }, [open]);28  return (29    <div className="lg:hidden">30      <button type="button" aria-label="Open menu" aria-expanded={open} className="-ml-1 inline-flex h-10 w-10 items-center justify-center rounded-md text-fg hover:bg-inset" onClick={() => setOpen(true)}>31        <Menu className="h-5 w-5" />32      </button>33      {open ? (34        <div className="fixed inset-0 z-[65] flex" role="dialog" aria-modal="true" aria-label="Menu">35          <div className="absolute inset-0 bg-black/45 backdrop-blur-[2px]" onClick={() => setOpen(false)} />36          <div className="sheet-in relative flex h-[100dvh] w-[88vw] max-w-sm flex-col bg-elevated shadow-pop">37            <div className="flex h-[var(--ri-header-h)] items-center justify-between border-b border-border px-4" style={{ marginTop: 'env(safe-area-inset-top)' }}>38              <span className="flex items-center gap-2">39                <LogoMark className="h-6 w-6" />40                <span className="text-[15px] font-semibold tracking-tight">41                  Rare<span className="text-muted">Index</span>42                </span>43              </span>44              <button type="button" aria-label="Close menu" className="inline-flex h-10 w-10 items-center justify-center rounded-md text-muted hover:bg-inset" onClick={() => setOpen(false)}>45                <X className="h-5 w-5" />46              </button>47            </div>48            <nav className="flex-1 overflow-y-auto overscroll-contain px-2 py-3 safe-bottom">49              {groups.map((g) => (50                <div key={g.id} className="mb-3">51                  <p className="t-label px-2 pb-1">{g.label}</p>52                  {items53                    .filter((n) => n.group === g.id)54                    .map((n) => {55                      const active = pathname === n.href;56                      return (57                        <Link key={n.href} href={n.href} className={`flex min-h-[44px] items-center justify-between gap-3 rounded-md px-2 py-2 ${active ? 'bg-inset' : 'hover:bg-sunken'}`}>58                          <span className="min-w-0">59                            <span className={`block text-[15px] ${active ? 'font-medium text-fg' : 'text-fg'}`}>{n.label}</span>60                            <span className="block truncate text-[11px] text-subtle">{n.description}</span>61                          </span>62                          <ChevronRight className="h-4 w-4 shrink-0 text-subtle" />63                        </Link>64                      );65                    })}66                </div>67              ))}68              <div className="mt-2 grid grid-cols-2 gap-2 px-1">69                <Link href="/login" className="btn h-11">70                  Sign in71                </Link>72                <Link href="/signup" className="btn-primary h-11">73                  Create account74                </Link>75              </div>76            </nav>77          </div>78        </div>79      ) : null}80    </div>81  );82}83