Skip to content

g1t/apps/web/app/components/base-branch.tsx

93 lines3,415 bytesCodeBlame

Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.

Teams and CODEOWNERS, labels and milestones, dependency updates, the security suite, and a clearer top bar1import { GitBranch, Pencil } from "lucide-react";
2import { useState } from "react";
3import { useFetcher } from "react-router";
4
5import { ErrorText } from "./ui";
6import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "./ui/combobox";
7import { Popover, PopoverContent, PopoverTrigger } from "./ui/popover";
8
9/**
10 * The branch a pull request merges into, and for someone who may change
11 * it, a menu of the repository's branches. The names are fetched when the
12 * menu opens; choosing one posts `action=base` with `base`.
13 */
14export function BaseBranch({
15 base,
16 head,
17 canChange,
18 branchesUrl,
19}: {
20 base: string;
21 /** Its own branch, which it cannot merge into. */
22 head: string | null;
23 canChange: boolean;
24 /** Where the branch names are, as JSON. */
25 branchesUrl: string;
26}) {
27 const names = useFetcher<{ branches: string[] }>();
28 const save = useFetcher<{ error?: string } | null>();
29 const [open, setOpen] = useState(false);
30 const shown = save.state !== "idle" ? String(save.formData?.get("base") ?? base) : base;
31 const choose = (branch: string) => {
32 setOpen(false);
33 if (branch === base) return;
34 const form = new FormData();
35 form.set("action", "base");
36 form.set("base", branch);
37 save.submit(form, { method: "post" });
38 };
39 const name = <span className="font-mono text-fg">{shown}</span>;
40 if (!canChange) return name;
41 return (
42 <>
43 <Popover
44 open={open}
45 onOpenChange={(next) => {
46 setOpen(next);
47 if (next && names.state === "idle" && !names.data) names.load(branchesUrl);
48 }}
49 >
50 <PopoverTrigger asChild>
51 <button
52 type="button"
53 title="Change the branch it merges into"
54 className="group inline-flex items-center gap-1 rounded-md border border-transparent px-1 font-mono text-fg transition-colors hover:border-line hover:bg-raised"
55 >
56 {shown}
57 <Pencil size={12} className="text-faint group-hover:text-fg" />
58 </button>
59 </PopoverTrigger>
60 <PopoverContent align="start" className="w-72 p-0">
61 <Command>
62 <CommandInput placeholder="Find a branch to merge into" />
63 <CommandList>
64 {names.data ? (
65 <CommandEmpty>No branch by that name.</CommandEmpty>
66 ) : (
67 <p className="px-3 py-4 text-sm text-faint">Reading branches…</p>
68 )}
69 <CommandGroup heading="Merge into">
70 {(names.data?.branches ?? [])
71 .filter((branch) => branch !== head)
72 .map((branch) => (
73 <CommandItem key={branch} value={branch} onSelect={() => choose(branch)}>
74 <GitBranch />
75 <span className="truncate font-mono text-[0.8125rem]">{branch}</span>
76 </CommandItem>
77 ))}
78 </CommandGroup>
79 </CommandList>
80 </Command>
81 <p className="border-t border-line px-3 py-2 text-xs text-faint">
82 Its checks, mergeability and whether it is up to date are worked out against the new base.
83 </p>
84 </PopoverContent>
85 </Popover>
86 {save.data?.error && (
87 <span className="basis-full">
88 <ErrorText>{save.data.error}</ErrorText>
89 </span>
90 )}
91 </>
92 );
93}