SPB Git forge

spb/ai-atlas

Public
41commits 1branches 0releases
4.6 MBsize
maindefault branch
12 days agolast push
HTML 77.2% TypeScript 10.5% Python 9.6% JavaScript 2.5%
3.8 KB · 85 lines tsx
Raw Blame History
1'use client';2import { Activity, Boxes, Home, MoreHorizontal, Search } from 'lucide-react';3import Link from 'next/link';4import { usePathname } from 'next/navigation';5import { useEffect, useState } from 'react';6import { Sheet } from '@/components/ui/sheet';7import { cn } from '@/lib/cn';8import { moreGroups, routes } from '@/lib/site';9import { useOpenSearch } from './search-context';1011const ICONS = { '/': Home, '/models': Boxes, '/changes': Activity } as const;1213/**14 * Bottom tab bar (< lg): Home · Models · Changes · Search · More. "More" opens a bottom sheet with the full site grouped15 * (Explore / Intelligence / Temporal / About). Safe-area aware; <body> reserves `pb-[calc(var(--tabbar-h)+safe-area)]`.16 */17export function MobileTabBar() {18  const pathname = usePathname();19  const openSearch = useOpenSearch();20  const [more, setMore] = useState(false);21  useEffect(() => setMore(false), [pathname]);22  const isActive = (href: string) => (href === '/' ? pathname === '/' : pathname === href || pathname.startsWith(href + '/'));23  const cls = (active: boolean) => cn('flex h-full w-full flex-col items-center justify-center gap-0.5 text-[10.5px]', active ? 'text-accent' : 'text-ink-2');24  const tabs = [25    { href: '/', label: 'Home' },26    { href: '/models', label: 'Models' },27    { href: '/changes', label: 'Changes' },28  ] as const;29  return (30    <>31      <Sheet open={more} onClose={() => setMore(false)} side="bottom" eyebrow="AI Atlas" title="Everything on the site" id="mobile-more" className="lg:hidden">32        <div className="grid gap-5 pb-2 sm:grid-cols-2">33          {moreGroups.map((g) => (34            <div key={g.label}>35              <p className="eyebrow mb-1">{g.label}</p>36              <ul className="grid grid-cols-2 gap-x-3 sm:grid-cols-1">37                {g.items.map((n) => (38                  <li key={n.href + n.label}>39                    <Link href={n.href} className={cn('flex min-h-11 items-center py-2 text-[15px]', isActive(n.href) ? 'text-ink font-medium' : 'text-ink-2 hover:text-ink')}>40                      {n.label}41                    </Link>42                  </li>43                ))}44              </ul>45            </div>46          ))}47        </div>48        <p className="border-t border-rule pt-3 text-xs text-ink-3">49          <Link href={routes.explore()} className="link">50            All entity types with live counts →51          </Link>52        </p>53      </Sheet>54      <nav aria-label="Primary (mobile)" className="safe-bottom fixed inset-x-0 bottom-0 z-[46] border-t border-rule bg-canvas/95 backdrop-blur-md lg:hidden">55        <ul className="grid h-[var(--tabbar-h)] grid-cols-5">56          {tabs.map((t) => {57            const Icon = ICONS[t.href];58            const active = isActive(t.href);59            return (60              <li key={t.href} className="min-w-0">61                <Link href={t.href} aria-current={active ? 'page' : undefined} className={cls(active)}>62                  <Icon size={21} aria-hidden strokeWidth={active ? 2.25 : 1.75} />63                  <span className="truncate">{t.label}</span>64                </Link>65              </li>66            );67          })}68          <li className="min-w-0">69            <button type="button" onClick={openSearch} className={cls(false)} aria-label="Search">70              <Search size={21} aria-hidden strokeWidth={1.75} />71              <span className="truncate">Search</span>72            </button>73          </li>74          <li className="min-w-0">75            <button type="button" onClick={() => setMore((m) => !m)} aria-expanded={more} aria-controls="mobile-more" className={cls(more)} data-mobile-more>76              <MoreHorizontal size={21} aria-hidden strokeWidth={1.75} />77              <span className="truncate">More</span>78            </button>79          </li>80        </ul>81      </nav>82    </>83  );84}85