From 00d1859f41a045c68b3d848f82b40d8d6c122c90 Mon Sep 17 00:00:00 2001 From: kbot Date: Mon, 6 Jul 2026 20:37:20 -0500 Subject: [PATCH] feat(portfolio): add cmdk command palette --- components/portfolio/CommandPalette.tsx | 207 ++++++++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100644 components/portfolio/CommandPalette.tsx diff --git a/components/portfolio/CommandPalette.tsx b/components/portfolio/CommandPalette.tsx new file mode 100644 index 0000000..87a046d --- /dev/null +++ b/components/portfolio/CommandPalette.tsx @@ -0,0 +1,207 @@ +"use client"; + +import { Command } from "cmdk"; +import { useRouter } from "next/navigation"; +import { useEffect, useRef, useState } from "react"; +import { contact } from "@/lib/portfolio"; + +type PaletteAction = + | { kind: "scroll"; targetId: "work" | "capabilities" | "operator" | "contact" } + | { kind: "route"; href: "/blog/" } + | { kind: "copy-email" } + | { kind: "external"; href: string }; + +const commands: { + label: string; + hint: string; + keywords: string[]; + action: PaletteAction; +}[] = [ + { + label: "Go to Work", + hint: "#work", + keywords: ["projects", "dossiers", "selected work"], + action: { kind: "scroll", targetId: "work" }, + }, + { + label: "Go to Capabilities", + hint: "#capabilities", + keywords: ["skills", "systems", "kernel"], + action: { kind: "scroll", targetId: "capabilities" }, + }, + { + label: "Go to Operator", + hint: "#operator", + keywords: ["profile", "identity", "honors"], + action: { kind: "scroll", targetId: "operator" }, + }, + { + label: "Go to Contact", + hint: "#contact", + keywords: ["email", "ssh", "signal"], + action: { kind: "scroll", targetId: "contact" }, + }, + { + label: "Open Field Notes", + hint: "/blog/", + keywords: ["blog", "notes", "archive"], + action: { kind: "route", href: "/blog/" }, + }, + { + label: "Copy Email", + hint: "copy", + keywords: [contact.email, "mail", "contact"], + action: { kind: "copy-email" }, + }, + { + label: "Open Git profile", + hint: "git", + keywords: ["source", "forge", "code"], + action: { kind: "external", href: contact.git }, + }, + { + label: "Open LinkedIn", + hint: "in", + keywords: ["profile", "social", "linkedin"], + action: { kind: "external", href: contact.linkedIn }, + }, +]; + +export function CommandPalette() { + const router = useRouter(); + const [open, setOpen] = useState(false); + const [pendingAction, setPendingAction] = useState(null); + const lastFocusedElement = useRef(null); + const wasOpen = useRef(false); + + useEffect(() => { + function rememberFocus() { + lastFocusedElement.current = + document.activeElement instanceof HTMLElement ? document.activeElement : null; + } + + function onKeyDown(event: KeyboardEvent) { + if (event.key.toLowerCase() !== "k" || (!event.metaKey && !event.ctrlKey)) { + return; + } + + event.preventDefault(); + rememberFocus(); + setOpen((current) => !current); + } + + function onPaletteEvent() { + rememberFocus(); + setOpen(true); + } + + window.addEventListener("keydown", onKeyDown); + window.addEventListener("blacksite:palette", onPaletteEvent); + + return () => { + window.removeEventListener("keydown", onKeyDown); + window.removeEventListener("blacksite:palette", onPaletteEvent); + }; + }, []); + + useEffect(() => { + if (wasOpen.current && !open) { + lastFocusedElement.current?.focus(); + } + + wasOpen.current = open; + }, [open]); + + useEffect(() => { + if (!pendingAction) { + return; + } + + let cancelled = false; + + async function runAction(action: PaletteAction) { + if (action.kind === "scroll") { + document.getElementById(action.targetId)?.scrollIntoView({ block: "start" }); + } + + if (action.kind === "route") { + router.push(action.href); + } + + if (action.kind === "copy-email") { + try { + await navigator.clipboard.writeText(contact.email); + } catch { + // Clipboard failures should not strand the command dialog open. + } + } + + if (action.kind === "external") { + window.open(action.href, "_blank", "noopener"); + } + + if (!cancelled) { + setOpen(false); + setPendingAction(null); + } + } + + void runAction(pendingAction); + + return () => { + cancelled = true; + }; + }, [pendingAction, router]); + + return ( + +
+

COMMAND PALETTE

+ +
+ + + + No matching command. + + + + {commands.map((command) => ( + setPendingAction(command.action)} + className="group flex cursor-pointer select-none items-center justify-between gap-4 rounded-md border border-transparent px-3 py-3 font-mono text-sm text-blacksite-text outline-none transition-colors data-[selected=true]:border-signal-cyan/60 data-[selected=true]:bg-signal-cyan/12 data-[selected=true]:text-signal-cyan data-[disabled=true]:cursor-not-allowed data-[disabled=true]:opacity-50" + > + {command.label} + + {command.hint} + + + ))} + + + +
+ ↑↓ select + Enter run · Esc close +
+
+ ); +}