1import { ComposerSelectControl } from "./chat/ComposerControl";
2import { ComposerContextLabel } from "./ComposerContextLabel";
3import { FolderGit2Icon, FolderGitIcon, FolderIcon } from "lucide-react";
4import { memo, useMemo, type MouseEvent as ReactMouseEvent } from "react";
5
6import {
7 resolveCurrentWorkspaceLabel,
8 resolveEnvModeLabel,
9 resolveLockedWorkspaceLabel,
10 type EnvMode,
11} from "./BranchToolbar.logic";
12import { useComposerMenuProps } from "./chat/composerEventScope";
13import { PreviousWorktreeItemContent } from "./PreviousWorktreeItemContent";
14import {
15 Select,
16 SelectGroup,
17 SelectGroupLabel,
18 SelectItem,
19 SelectPopup,
20 SelectValue,
21} from "./ui/select";
22import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip";
23
24const PREVIOUS_WORKTREE_SELECT_VALUE = "previous-worktree";
25
26interface BranchToolbarEnvModeSelectorProps {
27 forceNewWorktree?: boolean;
28 envLocked: boolean;
29 effectiveEnvMode: EnvMode;
30 activeWorktreePath: string | null;
31 onEnvModeChange: (mode: EnvMode) => void;
32 previousWorktreeLabel?: string | null;
33 previousWorktreeBranch?: string | null;
34 onUsePreviousWorktree?: () => void;
35}
36
37export const BranchToolbarEnvModeSelector = memo(function BranchToolbarEnvModeSelector({
38 forceNewWorktree = false,
39 envLocked,
40 effectiveEnvMode,
41 activeWorktreePath,
42 onEnvModeChange,
43 previousWorktreeLabel,
44 previousWorktreeBranch = null,
45 onUsePreviousWorktree,
46}: BranchToolbarEnvModeSelectorProps) {
47 const composerFloatingLayerProps = useComposerMenuProps();
48 const showPreviousWorktree = Boolean(previousWorktreeLabel && onUsePreviousWorktree);
49 const envModeItems = useMemo(
50 () => [
51 {
52 value: "local",
53 label: resolveCurrentWorkspaceLabel(activeWorktreePath),
54 },
55 { value: "worktree", label: resolveEnvModeLabel("worktree") },
56 ...(showPreviousWorktree && previousWorktreeLabel
57 ? [{ value: PREVIOUS_WORKTREE_SELECT_VALUE, label: previousWorktreeLabel }]
58 : []),
59 ],
60 [activeWorktreePath, previousWorktreeLabel, showPreviousWorktree],
61 );
62
63 const stopContextMenuMouseDown = (event: ReactMouseEvent) => {
64 if (event.button !== 0 || event.ctrlKey) {
65 event.stopPropagation();
66 }
67 };
68
69 if (envLocked || forceNewWorktree) {
70 const lockedRow = (
71 <span
72 className="inline-flex h-7 min-w-0 items-center gap-1 border border-transparent px-1.75 font-normal text-muted-foreground/70 text-xs sm:h-6"
73 data-composer-context-control
74 >
75 {activeWorktreePath ? (
76 <FolderGitIcon className="size-3" />
77 ) : effectiveEnvMode === "worktree" ? (
78 <FolderGit2Icon className="size-3" />
79 ) : (
80 <FolderIcon className="size-3" />
81 )}
82 <ComposerContextLabel>
83 {forceNewWorktree
84 ? resolveEnvModeLabel("worktree")
85 : resolveLockedWorkspaceLabel(activeWorktreePath, effectiveEnvMode)}
86 </ComposerContextLabel>
87 </span>
88 );
89
90 return (
91 <Tooltip>
92 <TooltipTrigger render={lockedRow} />
93 <TooltipPopup>
94 {forceNewWorktree
95 ? "Each model starts in its own worktree."
96 : resolveLockedWorkspaceLabel(activeWorktreePath, effectiveEnvMode)}
97 </TooltipPopup>
98 </Tooltip>
99 );
100 }
101
102 return (
103 <Select
104 modal={false}
105 value={effectiveEnvMode}
106 onValueChange={(value: string | null) => {
107 if (value === PREVIOUS_WORKTREE_SELECT_VALUE) {
108 onUsePreviousWorktree?.();
109 return;
110 }
111 onEnvModeChange(value as EnvMode);
112 }}
113 items={envModeItems}
114 >
115 <Tooltip>
116 <TooltipTrigger
117 render={
118 <ComposerSelectControl
119 size="xs"
120 className="min-w-0 shrink"
121 aria-label="Workspace"
122 data-composer-shortcut="composer.workspace"
123 data-composer-context-control
124 onMouseDownCapture={stopContextMenuMouseDown}
125 />
126 }
127 >
128 {effectiveEnvMode === "worktree" ? (
129 <FolderGit2Icon className="size-3" />
130 ) : activeWorktreePath ? (
131 <FolderGitIcon className="size-3" />
132 ) : (
133 <FolderIcon className="size-3" />
134 )}
135 <ComposerContextLabel>
136 <SelectValue />
137 </ComposerContextLabel>
138 </TooltipTrigger>
139 <TooltipPopup>
140 {effectiveEnvMode === "worktree"
141 ? resolveEnvModeLabel("worktree")
142 : resolveCurrentWorkspaceLabel(activeWorktreePath)}
143 </TooltipPopup>
144 </Tooltip>
145 <SelectPopup
146 alignItemWithTrigger={false}
147 {...composerFloatingLayerProps}
148 className={showPreviousWorktree ? "w-[min(21rem,calc(100vw-2rem))]" : undefined}
149 >
150 <SelectGroup>
151 <SelectGroupLabel>Workspace</SelectGroupLabel>
152 <SelectItem value="local">
153 <span className="inline-flex items-center gap-1.5">
154 {activeWorktreePath ? (
155 <FolderGitIcon className="size-3" />
156 ) : (
157 <FolderIcon className="size-3" />
158 )}
159 {resolveCurrentWorkspaceLabel(activeWorktreePath)}
160 </span>
161 </SelectItem>
162 <SelectItem value="worktree">
163 <span className="inline-flex items-center gap-1.5">
164 <FolderGit2Icon className="size-3" />
165 {resolveEnvModeLabel("worktree")}
166 </span>
167 </SelectItem>
168 {showPreviousWorktree && previousWorktreeLabel ? (
169 <SelectItem value={PREVIOUS_WORKTREE_SELECT_VALUE}>
170 <PreviousWorktreeItemContent branch={previousWorktreeBranch} />
171 </SelectItem>
172 ) : null}
173 </SelectGroup>
174 </SelectPopup>
175 </Select>
176 );
177});