import { CheckIcon, GitBranchIcon, Loader2Icon } from "lucide-react"; import { useMemo, useRef, useState } from "react"; import { Input } from "../../ui/input"; import { cn } from "../lib/utils"; import type { GitHubRepositoryBranch } from "../types"; export function RepositoryRefInput(props: { value: string; defaultRef: string; label: string; disabled?: boolean | undefined; compact?: boolean | undefined; onChange: (value: string) => void; loadBranches?: (() => Promise) | undefined; }) { const [focused, setFocused] = useState(false); const [loaded, setLoaded] = useState(false); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [branches, setBranches] = useState([]); const containerRef = useRef(null); const query = props.value.trim().toLowerCase(); const matches = useMemo( () => query ? branches.filter((branch) => branch.name.toLowerCase().includes(query)) : branches, [branches, query], ); async function load(): Promise { if (!props.loadBranches || loaded || loading || props.disabled) return; setLoading(true); setError(null); try { setBranches(await props.loadBranches()); setLoaded(true); } catch (caught) { setError(caught instanceof Error ? caught.message : String(caught)); } finally { setLoading(false); } } const showMenu = focused && Boolean(props.loadBranches) && (loading || error || loaded); return (
{ setFocused(true); void load(); }} onBlurCapture={(event) => { if ( event.relatedTarget instanceof Node && containerRef.current?.contains(event.relatedTarget) ) { return; } setFocused(false); }} > props.onChange(event.target.value)} onClick={(event) => event.stopPropagation()} disabled={props.disabled} placeholder={props.defaultRef} aria-label={props.label} className={cn(props.compact ? "h-7" : "h-8", "pl-7 text-xs")} /> {showMenu ? (
{loading ? (
Loading branches…
) : error ? (
Couldn't load branches. Your typed ref is still valid.
) : matches.length === 0 ? (
No matching branches. The typed ref will be kept.
) : ( matches.map((branch) => ( )) )}
) : null}
); }