spb/countryatlas
Public
TypeScript 57%
Python 38.6%
JavaScript 3.6%
CSS 0.6%
1'use client';2import { ChevronDown } from 'lucide-react';3import Link from 'next/link';4import { usePathname } from 'next/navigation';5import { useEffect, useId, useRef, useState } from 'react';6import { t } from '@/i18n';7import { cn } from '@/lib/cn';89interface Item {10 href: string;11 label: string;12 group: 'explore' | 'reference';13}1415/** "More ▾" disclosure in the desktop header: two columns (Explore · Reference), keyboard + outside-click closing. */16export function MoreMenu({ items }: { items: Item[] }) {17 const [open, setOpen] = useState(false);18 const id = useId();19 const ref = useRef<HTMLDivElement>(null);20 const pathname = usePathname();21 const active = items.some((it) => pathname === it.href || pathname.startsWith(`${it.href}/`));2223 useEffect(() => {24 setOpen(false);25 }, [pathname]);26 useEffect(() => {27 if (!open) return;28 const onDoc = (e: PointerEvent) => {29 if (!ref.current?.contains(e.target as Node)) setOpen(false);30 };31 const onKey = (e: KeyboardEvent) => {32 if (e.key === 'Escape') setOpen(false);33 };34 document.addEventListener('pointerdown', onDoc);35 document.addEventListener('keydown', onKey);36 return () => {37 document.removeEventListener('pointerdown', onDoc);38 document.removeEventListener('keydown', onKey);39 };40 }, [open]);4142 const groups: Array<{ key: Item['group']; label: string }> = [43 { key: 'explore', label: t('site.footer.explore') },44 { key: 'reference', label: t('site.footer.reference') },45 ];46 return (47 <div ref={ref} className="relative">48 <button type="button" onClick={() => setOpen((o) => !o)} aria-expanded={open} aria-controls={id} aria-haspopup="menu" className={cn('inline-flex h-9 items-center gap-1 rounded-sm px-2.5 text-sm', active ? 'font-medium text-ink' : 'text-ink-2 hover:bg-surface-2 hover:text-ink')}>49 {t('nav.more')}50 <ChevronDown size={14} aria-hidden className={cn('transition-transform', open && 'rotate-180')} />51 </button>52 {open ? (53 <div id={id} role="menu" aria-label={t('nav.moreMenu')} className="animate-fade absolute left-0 top-full z-40 mt-1 grid w-[26rem] grid-cols-2 gap-x-6 rounded-sm border border-rule bg-surface p-3 shadow-pop">54 {groups.map((g) => (55 <div key={g.key}>56 <div className="eyebrow mb-1 px-2">{g.label}</div>57 <ul>58 {items59 .filter((it) => it.group === g.key)60 .map((it) => {61 const cur = pathname === it.href || pathname.startsWith(`${it.href}/`);62 return (63 <li key={it.href}>64 <Link href={it.href} role="menuitem" aria-current={cur ? 'page' : undefined} className={cn('flex h-9 items-center rounded-sm px-2 text-sm', cur ? 'font-medium text-ink' : 'text-ink-2 hover:bg-surface-2 hover:text-ink')}>65 {it.label}66 </Link>67 </li>68 );69 })}70 </ul>71 </div>72 ))}73 </div>74 ) : null}75 </div>76 );77}78