import { Popover } from "@cloudflare/kumo/components/popover"; import { Tabs } from "@cloudflare/kumo/components/tabs"; import { compactInputClass } from "~/ui/Field"; import { useNavigate } from "@tanstack/react-router"; import { useEffect, useId, useRef, useState } from "react"; import { fuzzyFilter } from "~/lib/fuzzy"; import { cn } from "~/utils/cn"; const MAX_RESULTS = 60; const SHORT_SHA = 12; type Kind = "branches" | "tags"; type Props = { owner: string; repo: string; current: string; branches: string[]; tags: string[]; path: string; mode: "tree" | "blob" | "commits" | "blame"; }; /** * Branch and tag picker: a styled trigger that opens a fuzzy-filterable list, with a branches/tags * toggle so hundreds of tags never crowd the branch list. Keyboard: type to filter, arrows, Enter, Escape. */ export function RefSwitcher({ owner, repo, current, branches, tags, path, mode }: Props) { const navigate = useNavigate(); const [open, setOpen] = useState(false); const [kind, setKind] = useState(tags.includes(current) && !branches.includes(current) ? "tags" : "branches"); const [query, setQuery] = useState(""); const [active, setActive] = useState(0); const inputRef = useRef(null); const listId = useId(); const refs = kind === "branches" ? branches : tags; const results = fuzzyFilter(query, refs, MAX_RESULTS).map((match) => match.path); const known = branches.includes(current) || tags.includes(current); function openChange(next: boolean) { if (next) { setQuery(""); setActive(0); } setOpen(next); } useEffect(() => { setActive(0); }, [query, kind]); function go(target: string) { setOpen(false); const splat = path ? `${target}/${path}` : target; if (mode === "commits") navigate({ to: "/$owner/$repo/commits/$", params: { owner, repo, _splat: splat } }); else if (mode === "blame") navigate({ to: "/$owner/$repo/blame/$", params: { owner, repo, _splat: splat } }); else if (mode === "blob") navigate({ to: "/$owner/$repo/blob/$", params: { owner, repo, _splat: splat } }); else navigate({ to: "/$owner/$repo/tree/$", params: { owner, repo, _splat: splat } }); } function onKeyDown(event: React.KeyboardEvent) { if (event.key === "ArrowDown") { event.preventDefault(); setActive((index) => Math.min(index + 1, results.length - 1)); } else if (event.key === "ArrowUp") { event.preventDefault(); setActive((index) => Math.max(index - 1, 0)); } else if (event.key === "Enter") { event.preventDefault(); const target = results[active]; if (target) go(target); } else if (event.key === "Tab" && !event.shiftKey && results.length > 0) { event.preventDefault(); setKind((value) => (value === "branches" ? "tags" : "branches")); } } return ( } > {tags.includes(current) && !branches.includes(current) ? "⌂" : "⑂"} {known ? current : current.slice(0, SHORT_SHA)} ▾
setQuery(event.target.value)} onKeyDown={onKeyDown} placeholder={kind === "branches" ? "Find a branch…" : "Find a tag…"} className={compactInputClass("h-8 w-full font-mono")} />
setKind(next === "tags" ? "tags" : "branches")} listClassName="w-full" tabs={(["branches", "tags"] as const).map((item) => ({ value: item, className: "flex-1 capitalize", label: ( {item}{" "} {item === "branches" ? branches.length : tags.length} ), }))} />
    {results.length === 0 && (
  • {refs.length === 0 ? `No ${kind}` : `Nothing matches “${query}”`}
  • )} {results.map((item, index) => (
  • ))}
); }