1import { useState } from 'react'
2import { GitBranch, Tag, Check, ChevronsUpDown } from 'lucide-react'
3import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
4import { Button } from '@/components/ui/button'
5import { Input } from '@/components/ui/input'
6import type { GitRef } from '@/lib/gitApi'
7import { cn } from '@/lib/utils'
8
9interface RefSelectorProps {
10 refs: GitRef[]
11 currentRef: string
12 onSelect: (ref: GitRef) => void
13}
14
15// Branch / tag selector dropdown for the code browser. Shown in two groups
16// (branches, tags) with an inline search filter.
17export function RefSelector({ refs, currentRef, onSelect }: RefSelectorProps) {
18 const [open, setOpen] = useState(false)
19 const [filter, setFilter] = useState('')
20
21 const filtered = refs.filter((r) =>
22 r.shortName.toLowerCase().includes(filter.toLowerCase()),
23 )
24 const branches = filtered.filter((r) => r.type === 'branch')
25 const tags = filtered.filter((r) => r.type === 'tag')
26
27 return (
28 <Popover open={open} onOpenChange={setOpen}>
29 <PopoverTrigger asChild>
30 <Button variant="outline" size="sm" className="gap-2 font-mono text-xs">
31 <GitBranch className="size-3.5" />
32 {currentRef}
33 <ChevronsUpDown className="size-3 text-muted-foreground" />
34 </Button>
35 </PopoverTrigger>
36 <PopoverContent align="start" className="w-64 p-2">
37 <p className="mb-2 px-1 text-xs font-semibold text-muted-foreground">Switch branch / tag</p>
38 <Input
39 placeholder="Filter…"
40 className="mb-2 h-7 text-xs"
41 value={filter}
42 onChange={(e) => setFilter(e.target.value)}
43 autoFocus
44 />
45 <div className="max-h-64 overflow-y-auto">
46 {branches.length > 0 && (
47 <div className="mb-1">
48 <p className="px-2 py-1 text-xs text-muted-foreground">Branches</p>
49 {branches.map((ref) => (
50 <RefItem
51 key={ref.name}
52 ref_={ref}
53 active={ref.shortName === currentRef}
54 onSelect={() => { onSelect(ref); setOpen(false); setFilter('') }}
55 />
56 ))}
57 </div>
58 )}
59 {tags.length > 0 && (
60 <div>
61 <p className="px-2 py-1 text-xs text-muted-foreground">Tags</p>
62 {tags.map((ref) => (
63 <RefItem
64 key={ref.name}
65 ref_={ref}
66 active={ref.shortName === currentRef}
67 onSelect={() => { onSelect(ref); setOpen(false); setFilter('') }}
68 />
69 ))}
70 </div>
71 )}
72 {filtered.length === 0 && (
73 <p className="px-2 py-2 text-xs text-muted-foreground">No results</p>
74 )}
75 </div>
76 </PopoverContent>
77 </Popover>
78 )
79}
80
81function RefItem({
82 ref_,
83 active,
84 onSelect,
85}: {
86 ref_: GitRef
87 active: boolean
88 onSelect: () => void
89}) {
90 return (
91 <button
92 onClick={onSelect}
93 className={cn(
94 'flex w-full items-center gap-2 rounded px-2 py-1.5 text-left text-xs hover:bg-muted',
95 active && 'font-medium',
96 )}
97 >
98 {ref_.type === 'branch' ? (
99 <GitBranch className="size-3 shrink-0 text-muted-foreground" />
100 ) : (
101 <Tag className="size-3 shrink-0 text-muted-foreground" />
102 )}
103 <span className="flex-1 truncate font-mono">{ref_.shortName}</span>
104 {active && <Check className="size-3 text-muted-foreground" />}
105 </button>
106 )
107}