"use client"; import { useRouter } from "next/navigation"; import { useEffect, useState, useSyncExternalStore } from "react"; import { CommandPalette, SearchField, type CommandPaletteItem } from "@unlocalhosted/metalui"; import { CompassIcon, GearIcon, HomeIcon, KeyIcon, PlusIcon, SearchIcon, ShieldLockIcon } from "./icons"; import { RepoAvatar, type RepoImageRef } from "./ui"; const noSubscribe = () => () => {}; const isApple = () => /Mac|iPhone|iPad/.test(navigator.platform); export type PaletteRepo = RepoImageRef; /** * The header's search well (MetalUI's SearchField) and the ⌘K palette it opens: jump to a repository, * go to a place, or search every repository for the words typed. ⌘K / Ctrl+K or "/" opens it anywhere. */ export function SearchBox({ repos = [], signedIn = false }: { repos?: PaletteRepo[]; signedIn?: boolean }) { const router = useRouter(); const apple = useSyncExternalStore(noSubscribe, isApple, () => true); const [open, setOpen] = useState(false); const [query, setQuery] = useState(""); useEffect(() => { const onKey = (e: KeyboardEvent) => { const target = e.target as HTMLElement; const typing = target.isContentEditable || /^(INPUT|TEXTAREA|SELECT)$/.test(target.tagName); const shortcut = (e.key === "k" && (e.metaKey || e.ctrlKey)) || (e.key === "/" && !typing); if (!shortcut) return; e.preventDefault(); setOpen(true); }; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, []); const q = query.trim(); const items: CommandPaletteItem[] = [ ...(q ? [{ id: `search:${q}`, section: "SEARCH", label: `Search repositories for “${q}”`, icon: , hint: "EXPLORE" }] : []), ...repos.map((r) => ({ id: `repo:${r.owner}/${r.name}`, section: "REPOSITORIES", label: `${r.owner}/${r.name}`, icon: , })), { id: "go:/", section: "PLACES", label: "Overview", icon: }, { id: "go:/explore", section: "PLACES", label: "Explore", icon: , keywords: ["browse", "discover"] }, ...(signedIn ? [ { id: "go:/new", section: "ACTIONS", label: "New Repository", icon: , keywords: ["create"] }, { id: "go:/settings/profile", section: "ACTIONS", label: "Settings", icon: , keywords: ["profile", "account"] }, { id: "go:/settings/keys", section: "ACTIONS", label: "SSH Keys", icon: }, { id: "go:/settings/tokens", section: "ACTIONS", label: "API Tokens", icon: }, ] : []), ]; return ( <> } shortcut={apple ? "⌘K" : "Ctrl K"} aria-keyshortcuts={apple ? "Meta+K" : "Control+K"} tone="light" onClick={() => setOpen(true)} className="hidden md:flex" /> { setOpen(next); if (!next) setQuery(""); }} query={query} onQueryChange={setQuery} items={items} icon={} placeholder="Repository, place or words to search" status={signedIn ? "YOUR REPOSITORIES" : "PUBLIC REPOSITORIES"} empty="Nothing matches" aria-label="Search" onRun={(item) => { const [kind, ...rest] = item.id.split(":"); const value = rest.join(":"); if (kind === "search") router.push(`/explore?q=${encodeURIComponent(value)}`); else if (kind === "repo") router.push(`/${value}`); else router.push(value); }} /> ); }