1- import React , { useCallback , useEffect , useMemo , useRef , useState } from "react" ;
2- import { Check , ChevronRight , Globe , Loader2 , Wand2 } from "lucide-react" ;
1+ import React , { useCallback , useMemo , useRef , useState } from "react" ;
2+ import { Check , ChevronRight , Globe , Loader2 , Plus , Wand2 } from "lucide-react" ;
33import { cn } from "@/common/lib/utils" ;
44import { Popover , PopoverContent , PopoverAnchor } from "./ui/popover" ;
55import { Tooltip , TooltipContent , TooltipTrigger } from "./ui/tooltip" ;
@@ -103,27 +103,29 @@ export function BranchNameInput(props: BranchNameInputProps) {
103103 filteredLocalBranches . length > 0 ||
104104 remoteGroups . some ( ( g ) => getFilteredRemoteBranches ( g . remote ) . length > 0 ) ;
105105
106+ const hasAnyBranches =
107+ localBranches . length > 0 || remoteGroups . some ( ( g ) => g . branches . length > 0 ) ;
108+
106109 // Check if input exactly matches an existing branch
107110 const exactLocalMatch = localBranches . find ( ( b ) => b . toLowerCase ( ) === searchLower ) ;
108111 const exactRemoteMatch = remoteGroups . find ( ( g ) =>
109112 g . branches . some ( ( b ) => b . toLowerCase ( ) === searchLower )
110113 ) ;
114+ const hasExactMatch = exactLocalMatch ?? exactRemoteMatch ;
111115
112- // Open popover when there's input and matching branches
113- useEffect ( ( ) => {
114- if ( value . length > 0 && hasMatchingBranches && ! disabled ) {
115- setIsOpen ( true ) ;
116- } else if ( value . length === 0 || ! hasMatchingBranches ) {
117- setIsOpen ( false ) ;
118- }
119- } , [ value , hasMatchingBranches , disabled ] ) ;
116+ // Show "Create new branch" option when there's input that doesn't exactly match
117+ const showCreateOption = value . length > 0 && ! hasExactMatch ;
120118
121- // Handle input focus - disable auto-generate so user can edit
119+ // Handle input focus - show dropdown and disable auto-generate
122120 const handleFocus = useCallback ( ( ) => {
123121 if ( autoGenerate ) {
124122 onAutoGenerateChange ( false ) ;
125123 }
126- } , [ autoGenerate , onAutoGenerateChange ] ) ;
124+ // Show dropdown if branches are available (even when input is empty)
125+ if ( branchesLoaded && hasAnyBranches && ! disabled ) {
126+ setIsOpen ( true ) ;
127+ }
128+ } , [ autoGenerate , onAutoGenerateChange , branchesLoaded , hasAnyBranches , disabled ] ) ;
127129
128130 // Handle input change
129131 const handleChange = useCallback (
@@ -160,17 +162,21 @@ export function BranchNameInput(props: BranchNameInputProps) {
160162 [ onChange , onSelectExistingBranch ]
161163 ) ;
162164
163- // Handle input blur - check if we should auto-select an exact match
165+ // Handle selecting "Create new branch" option
166+ const handleSelectCreateNew = useCallback ( ( ) => {
167+ // Keep the current value, clear any existing branch selection
168+ onSelectExistingBranch ( null ) ;
169+ setIsOpen ( false ) ;
170+ inputRef . current ?. blur ( ) ;
171+ } , [ onSelectExistingBranch ] ) ;
172+
173+ // Handle input blur - close dropdown
164174 const handleBlur = useCallback ( ( ) => {
165175 // Small delay to allow click events on dropdown items to fire first
166176 setTimeout ( ( ) => {
167- // If input exactly matches a local branch, auto-select it
168- if ( exactLocalMatch && ! selectedExistingBranch ) {
169- onSelectExistingBranch ( { kind : "local" , branch : exactLocalMatch } ) ;
170- }
171177 setIsOpen ( false ) ;
172178 } , 150 ) ;
173- } , [ exactLocalMatch , selectedExistingBranch , onSelectExistingBranch ] ) ;
179+ } , [ ] ) ;
174180
175181 // Handle keyboard navigation
176182 const handleKeyDown = useCallback (
@@ -187,18 +193,20 @@ export function BranchNameInput(props: BranchNameInputProps) {
187193 if ( branch ) {
188194 handleSelectRemoteBranch ( exactRemoteMatch . remote , branch ) ;
189195 }
190- } else {
191- // No match - close popover and use as new branch name
192- setIsOpen ( false ) ;
196+ } else if ( value . length > 0 ) {
197+ // No match - use as new branch name
198+ handleSelectCreateNew ( ) ;
193199 }
194200 }
195201 } ,
196202 [
197203 exactLocalMatch ,
198204 exactRemoteMatch ,
199205 searchLower ,
206+ value . length ,
200207 handleSelectLocalBranch ,
201208 handleSelectRemoteBranch ,
209+ handleSelectCreateNew ,
202210 ]
203211 ) ;
204212
@@ -315,6 +323,23 @@ export function BranchNameInput(props: BranchNameInputProps) {
315323 </ div >
316324 ) }
317325
326+ { /* Create new branch option - shown when input doesn't match existing */ }
327+ { branchesLoaded && showCreateOption && (
328+ < >
329+ < button
330+ type = "button"
331+ onClick = { handleSelectCreateNew }
332+ className = "text-accent hover:bg-hover flex w-full items-center gap-1.5 rounded-sm px-2 py-1.5 font-mono text-[11px]"
333+ >
334+ < Plus className = "h-3 w-3 shrink-0" />
335+ < span className = "truncate" >
336+ Create new branch < span className = "font-semibold" > { value } </ span >
337+ </ span >
338+ </ button >
339+ { hasMatchingBranches && < div className = "bg-border my-1 h-px" /> }
340+ </ >
341+ ) }
342+
318343 { /* Remote branches as expandable groups */ }
319344 { branchesLoaded && remoteGroups . length > 0 && (
320345 < >
@@ -399,10 +424,10 @@ export function BranchNameInput(props: BranchNameInputProps) {
399424 </ >
400425 ) }
401426
402- { /* No matches - show hint */ }
403- { branchesLoaded && ! hasMatchingBranches && value . length > 0 && (
427+ { /* Empty state when no input yet */ }
428+ { branchesLoaded && value . length === 0 && ! hasMatchingBranches && (
404429 < div className = "text-muted px-2 py-2 text-center text-[11px]" >
405- Press Enter to create new branch
430+ Type to search or create a branch
406431 </ div >
407432 ) }
408433 </ div >
0 commit comments