● Zion Boggan
repos/Drophawk/frontend/components/layout/AppShell.tsx
zionboggan.com ↗
78 lines · tsx
History for this file →
1
"use client";
2
 
3
import { useEffect } from "react";
4
import Link from "next/link";
5
import { usePathname } from "next/navigation";
6
import { Sidebar } from "./Sidebar";
7
import { Topbar } from "./Topbar";
8
import { CommandPalette } from "./CommandPalette";
9
import { NAV } from "./nav";
10
import { useDropsStream } from "@/hooks/useDropsStream";
11
import { useUiStore } from "@/stores/ui";
12
import { cn } from "@/lib/utils";
13
 
14
const TITLES: Record<string, string> = {
15
  "/": "Dashboard",
16
  "/drops": "Live Drops",
17
  "/explore": "Explore",
18
  "/watchlist": "Watchlist",
19
  "/sources": "Sources",
20
  "/settings": "Settings",
21
};
22
 
23
export function AppShell({ children }: { children: React.ReactNode }) {
24
  const pathname = usePathname();
25
  const setCommandOpen = useUiStore((s) => s.setCommandOpen);
26
 
27
  useDropsStream();
28
 
29
  useEffect(() => {
30
    const onKey = (e: KeyboardEvent) => {
31
      if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") {
32
        e.preventDefault();
33
        setCommandOpen(true);
34
      }
35
    };
36
    window.addEventListener("keydown", onKey);
37
    return () => window.removeEventListener("keydown", onKey);
38
  }, [setCommandOpen]);
39
 
40
  const title = TITLES[pathname] ?? "DropHawk";
41
  const mobileNav = NAV.filter((n) => n.ready).slice(0, 5);
42
 
43
  return (
44
    <div className="flex min-h-dvh">
45
      <Sidebar />
46
      <div className="flex min-w-0 flex-1 flex-col pb-16 md:pb-0">
47
        <Topbar title={title} />
48
        <main className="flex-1 px-4 py-5 md:px-6 md:py-6">{children}</main>
49
      </div>
50
 
51
      {}
52
      <nav
53
        className="glass fixed inset-x-0 bottom-0 z-[var(--z-sticky)] flex h-16 items-stretch border-t border-[var(--color-border-subtle)] md:hidden"
54
        aria-label="Primary"
55
      >
56
        {mobileNav.map((item) => {
57
          const active = item.href === "/" ? pathname === "/" : pathname.startsWith(item.href);
58
          return (
59
            <Link
60
              key={item.href}
61
              href={item.href}
62
              aria-current={active ? "page" : undefined}
63
              className={cn(
64
                "flex flex-1 flex-col items-center justify-center gap-1 text-[10px] font-medium",
65
                active ? "text-[var(--color-accent-teal)]" : "text-[var(--color-fg-tertiary)]",
66
              )}
67
            >
68
              <item.icon className="h-5 w-5" aria-hidden />
69
              {item.label}
70
            </Link>
71
          );
72
        })}
73
      </nav>
74
 
75
      <CommandPalette />
76
    </div>
77
  );
78
}