Skip to content
Source
Sign inSign up
Open History to see the last commit

WorktreeBaseBranchPicker.tsx

131 lines4.2 KB

1import type { EnvironmentId } from "@t3tools/contracts";
2import { ChevronDownIcon, GitBranchIcon } from "lucide-react";
3import { useState } from "react";
4​
5import { usePaginatedBranches } from "../state/queries";
6import { useEnvironmentQuery } from "../state/query";
7import { vcsEnvironment } from "../state/vcs";
8import { BranchPicker, BranchPickerRefItem } from "./BranchPicker";
9import {
10 groupLabelsForPickerItems,
11 orderRefNamesByGroup,
12 resolveBranchTriggerLabel,
13 sanitizeNewRefName,
14} from "./BranchToolbar.logic";
15import { MiddleTruncate } from "./ui/middle-truncate";
16import { Button } from "./ui/button";
17import { ComboboxTrigger } from "./ui/combobox";
18​
19export function WorktreeBaseBranchPicker({
20 environmentId,
21 cwd,
22 value,
23 onValueChange,
24 startFromOrigin,
25 onStartFromOriginChange,
26 disabled = false,
27 id,
28}: {
29 environmentId: EnvironmentId;
30 cwd: string | null;
31 value: string;
32 onValueChange: (branch: string) => void;
33 startFromOrigin: boolean;
34 onStartFromOriginChange: (checked: boolean) => void;
35 disabled?: boolean;
36 id?: string;
37}) {
38 const [open, setOpen] = useState(false);
39 const [query, setQuery] = useState("");
40 const branches = usePaginatedBranches({
41 environmentId,
42 cwd,
43 query: sanitizeNewRefName(query.trim()),
44 });
45 const selectedRefQuery = useEnvironmentQuery(
46 cwd && value
47 ? vcsEnvironment.listRefs({
48 environmentId,
49 input: { cwd, query: value, limit: 10 },
50 })
51 : null,
52 );
53 const selectedRef =
54 branches.refs.find((branch) => branch.name === value) ??
55 selectedRefQuery.data?.refs.find((branch) => branch.name === value);
56 const label = resolveBranchTriggerLabel({
57 activeWorktreePath: null,
58 effectiveEnvMode: "worktree",
59 resolvedActiveBranch: value || null,
60 resolvedActiveBranchIsRemote: selectedRef ? selectedRef.isRemote === true : null,
61 startFromOrigin,
62 });
63 const branchByName = new Map(branches.refs.map((branch) => [branch.name, branch]));
64 const items = orderRefNamesByGroup(branches.refs);
65 const groupLabels = groupLabelsForPickerItems(items, (name) =>
66 branchByName.get(name)?.isRemote ? "Remote" : "Local",
67 );
68 const hasNextPage = branches.data?.nextCursor != null;
69 const statusText =
70 branches.error ??
71 (branches.isPending && branches.data === null
72 ? "Loading refs..."
73 : branches.isFetchingNextPage
74 ? "Loading more refs..."
75 : hasNextPage
76 ? `Showing ${branches.refs.length} of ${branches.data?.totalCount} refs`
77 : null);
78 const handleOpenChange = (next: boolean) => {
79 setOpen(next);
80 if (!next) setQuery("");
81 };
82 return (
83 <BranchPicker
84 items={items}
85 filteredItems={items}
86 value={value || null}
87 query={query}
88 onQueryChange={setQuery}
89 open={open && !disabled}
90 onOpenChange={handleOpenChange}
91 onSelectItem={(name) => {
92 onValueChange(name);
93 handleOpenChange(false);
94 }}
95 hasNextPage={hasNextPage}
96 isFetchingNextPage={branches.isFetchingNextPage}
97 onLoadNext={branches.loadNext}
98 statusText={statusText}
99 originControl={{ checked: startFromOrigin, onCheckedChange: onStartFromOriginChange }}
100 popupProps={{ align: "start", side: "bottom", className: "flex w-90 flex-col" }}
101 getItemType={(name) => (groupLabels.has(name) ? "ref-with-label" : "ref")}
102 renderItem={(name, index) => {
103 const branch = branchByName.get(name);
104 return branch ? (
105 <BranchPickerRefItem
106 branch={branch}
107 projectCwd={cwd}
108 index={index}
109 selected={branch.name === value}
110 groupLabel={groupLabels.get(name) ?? null}
111 onClick={() => {
112 onValueChange(branch.name);
113 handleOpenChange(false);
114 }}
115 />
116 ) : null;
117 }}
118 >
119 <ComboboxTrigger
120 id={id}
121 disabled={disabled || !cwd}
122 render={<Button variant="secondary" size="sm" />}
123 className="w-full justify-between "
124 >
125 <GitBranchIcon className="size-3.5 shrink-0 text-muted-foreground" />
126 <MiddleTruncate value={label} className="flex-1 text-left" />
127 <ChevronDownIcon className="size-3.5 shrink-0 text-muted-foreground" />
128 </ComboboxTrigger>
129 </BranchPicker>
130 );