1import { Settings2 } from 'lucide-react'
2import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
3import { LabelBadge } from './LabelBadge'
4import { useAuth } from '@/lib/auth'
5import {
6 useValidLabelsQuery,
7 useBugChangeLabelsMutation,
8 BugDetailDocument,
9} from '@/__generated__/graphql'
10
11interface LabelEditorProps {
12 bugPrefix: string
13 currentLabels: Array<{ name: string; color: { R: number; G: number; B: number } }>
14 /** Current repo slug, passed as `ref` in refetch query variables. */
15 ref_?: string | null
16}
17
18// Gear-icon popover in the BugDetailPage sidebar for adding/removing labels.
19// Loads all valid labels from the repo and toggles them via bugChangeLabels.
20// Hidden in read-only mode.
21export function LabelEditor({ bugPrefix, currentLabels, ref_ }: LabelEditorProps) {
22 const { user } = useAuth()
23 const { data } = useValidLabelsQuery({ skip: !user, variables: { ref: ref_ } })
24 const [changeLabels] = useBugChangeLabelsMutation({
25 refetchQueries: [{ query: BugDetailDocument, variables: { ref: ref_, prefix: bugPrefix } }],
26 })
27
28 const validLabels = data?.repository?.validLabels.nodes ?? []
29 const currentNames = new Set(currentLabels.map((l) => l.name))
30
31 async function toggleLabel(name: string) {
32 const isSet = currentNames.has(name)
33 await changeLabels({
34 variables: {
35 input: {
36 prefix: bugPrefix,
37 added: isSet ? [] : [name],
38 Removed: isSet ? [name] : [],
39 },
40 },
41 })
42 }
43
44 return (
45 <div>
46 <div className="mb-2 flex items-center justify-between">
47 <h3 className="text-xs font-semibold uppercase tracking-wider text-muted-foreground">
48 Labels
49 </h3>
50 {user && validLabels.length > 0 && (
51 <Popover>
52 <PopoverTrigger asChild>
53 <button className="text-muted-foreground hover:text-foreground">
54 <Settings2 className="size-3.5" />
55 </button>
56 </PopoverTrigger>
57 <PopoverContent align="end" className="w-56 p-2">
58 <p className="mb-2 px-2 text-xs font-medium text-muted-foreground">
59 Apply labels
60 </p>
61 <div className="space-y-1">
62 {validLabels.map((label) => {
63 const active = currentNames.has(label.name)
64 return (
65 <button
66 key={label.name}
67 onClick={() => toggleLabel(label.name)}
68 className="flex w-full items-center gap-2 rounded px-2 py-1.5 text-sm hover:bg-muted"
69 >
70 <span
71 className={`size-2 rounded-full border-2 transition-colors ${
72 active ? 'border-transparent' : 'border-muted-foreground/40 bg-transparent'
73 }`}
74 style={active ? { backgroundColor: `rgb(${label.color.R},${label.color.G},${label.color.B})` } : {}}
75 />
76 <LabelBadge name={label.name} color={label.color} />
77 </button>
78 )
79 })}
80 </div>
81 </PopoverContent>
82 </Popover>
83 )}
84 </div>
85
86 {currentLabels.length === 0 ? (
87 <p className="text-sm text-muted-foreground">None yet</p>
88 ) : (
89 <div className="flex flex-wrap gap-1">
90 {currentLabels.map((label) => (
91 <LabelBadge key={label.name} name={label.name} color={label.color} />
92 ))}
93 </div>
94 )}
95 </div>
96 )
97}