● Zion Boggan
repos/Drophawk/frontend/components/layout/CommandPalette.tsx
zionboggan.com ↗
73 lines · tsx
History for this file →
1
"use client";
2
 
3
import { useRouter } from "next/navigation";
4
import { Command } from "cmdk";
5
import { motion, AnimatePresence } from "framer-motion";
6
import { NAV } from "./nav";
7
import { useUiStore } from "@/stores/ui";
8
import { springs } from "@/lib/motion";
9
 
10
export function CommandPalette() {
11
  const router = useRouter();
12
  const open = useUiStore((s) => s.commandOpen);
13
  const setOpen = useUiStore((s) => s.setCommandOpen);
14
 
15
  const go = (href: string) => {
16
    setOpen(false);
17
    router.push(href);
18
  };
19
 
20
  return (
21
    <AnimatePresence>
22
      {open ? (
23
        <div className="fixed inset-0 z-[var(--z-modal)] flex items-start justify-center p-4 pt-[14vh]">
24
          <motion.div
25
            className="absolute inset-0 bg-black/55 backdrop-blur-sm"
26
            initial={{ opacity: 0 }}
27
            animate={{ opacity: 1 }}
28
            exit={{ opacity: 0 }}
29
            onClick={() => setOpen(false)}
30
            aria-hidden
31
          />
32
          <motion.div
33
            initial={{ opacity: 0, scale: 0.97, y: -8 }}
34
            animate={{ opacity: 1, scale: 1, y: 0, transition: springs.sheet }}
35
            exit={{ opacity: 0, scale: 0.97, y: -8, transition: { duration: 0.15 } }}
36
            className="ring-frame relative w-full max-w-lg overflow-hidden rounded-[var(--radius-lg)] border border-[var(--color-border-default)] bg-[var(--color-bg-overlay)] shadow-[var(--shadow-pop)]"
37
          >
38
            <Command label="Command palette" className="w-full">
39
              <div className="border-b border-[var(--color-border-subtle)] px-4">
40
                <Command.Input
41
                  autoFocus
42
                  placeholder="Jump to a page or search…"
43
                  className="h-12 w-full bg-transparent text-sm text-[var(--color-fg-primary)] placeholder:text-[var(--color-fg-tertiary)] focus:outline-none"
44
                />
45
              </div>
46
              <Command.List className="max-h-[320px] overflow-y-auto p-2">
47
                <Command.Empty className="px-3 py-6 text-center text-sm text-[var(--color-fg-tertiary)]">
48
                  No results.
49
                </Command.Empty>
50
                <Command.Group
51
                  heading="Navigate"
52
                  className="px-1 text-[10px] font-medium uppercase tracking-wider text-[var(--color-fg-tertiary)] [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5"
53
                >
54
                  {NAV.filter((n) => n.ready).map((item) => (
55
                    <Command.Item
56
                      key={item.href}
57
                      value={item.label}
58
                      onSelect={() => go(item.href)}
59
                      className="flex cursor-pointer items-center gap-3 rounded-[var(--radius)] px-3 py-2 text-sm text-[var(--color-fg-secondary)] aria-selected:bg-[var(--color-accent-teal-soft)] aria-selected:text-[var(--color-fg-primary)]"
60
                    >
61
                      <item.icon className="h-4 w-4" aria-hidden />
62
                      {item.label}
63
                    </Command.Item>
64
                  ))}
65
                </Command.Group>
66
              </Command.List>
67
            </Command>
68
          </motion.div>
69
        </div>
70
      ) : null}
71
    </AnimatePresence>
72
  );
73
}