1- import React from "react" ;
1+ import React , { useCallback } from "react" ;
22import { RUNTIME_MODE , type RuntimeMode } from "@/common/types/runtime" ;
33import { Select } from "../Select" ;
44import { RuntimeIconSelector } from "../RuntimeIconSelector" ;
5+ import { Loader2 } from "lucide-react" ;
6+ import { cn } from "@/common/lib/utils" ;
57
68interface CreationControlsProps {
79 branches : string [ ] ;
@@ -17,61 +19,126 @@ interface CreationControlsProps {
1719 /** Called when user changes SSH host */
1820 onSshHostChange : ( host : string ) => void ;
1921 disabled : boolean ;
22+ /** Workspace name state */
23+ workspaceName : string ;
24+ /** Whether name is being generated */
25+ isGeneratingName : boolean ;
26+ /** Whether auto-generation is enabled */
27+ autoGenerateName : boolean ;
28+ /** Name generation error */
29+ nameError : string | null ;
30+ /** Called when auto-generate checkbox changes */
31+ onAutoGenerateChange : ( enabled : boolean ) => void ;
32+ /** Called when user types in the name field */
33+ onNameChange : ( name : string ) => void ;
2034}
2135
2236/**
2337 * Additional controls shown only during workspace creation
2438 * - Trunk branch selector (which branch to fork from) - hidden for Local runtime
2539 * - Runtime mode (Local, Worktree, SSH)
40+ * - Workspace name (auto-generated with manual override)
2641 */
2742export function CreationControls ( props : CreationControlsProps ) {
2843 // Local runtime doesn't need a trunk branch selector (uses project dir as-is)
2944 const showTrunkBranchSelector =
3045 props . branches . length > 0 && props . runtimeMode !== RUNTIME_MODE . LOCAL ;
3146
47+ const { onNameChange } = props ;
48+ const handleNameChange = useCallback (
49+ ( e : React . ChangeEvent < HTMLInputElement > ) => {
50+ onNameChange ( e . target . value ) ;
51+ } ,
52+ [ onNameChange ]
53+ ) ;
54+
3255 return (
33- < div className = "flex flex-wrap items-center gap-x-3 gap-y-2" >
34- { /* Runtime Selector - icon-based with tooltips */ }
35- < RuntimeIconSelector
36- value = { props . runtimeMode }
37- onChange = { props . onRuntimeModeChange }
38- defaultMode = { props . defaultRuntimeMode }
39- onSetDefault = { props . onSetDefaultRuntime }
40- disabled = { props . disabled }
41- />
56+ < div className = "flex flex-col gap-2" >
57+ { /* First row: Runtime, Branch, SSH */ }
58+ < div className = "flex flex-wrap items-center gap-x-3 gap-y-2" >
59+ { /* Runtime Selector - icon-based with tooltips */ }
60+ < RuntimeIconSelector
61+ value = { props . runtimeMode }
62+ onChange = { props . onRuntimeModeChange }
63+ defaultMode = { props . defaultRuntimeMode }
64+ onSetDefault = { props . onSetDefaultRuntime }
65+ disabled = { props . disabled }
66+ />
67+
68+ { /* Trunk Branch Selector - hidden for Local runtime */ }
69+ { showTrunkBranchSelector && (
70+ < div
71+ className = "flex items-center gap-1"
72+ data-component = "TrunkBranchGroup"
73+ data-tutorial = "trunk-branch"
74+ >
75+ < label htmlFor = "trunk-branch" className = "text-muted text-xs" >
76+ From:
77+ </ label >
78+ < Select
79+ id = "trunk-branch"
80+ value = { props . trunkBranch }
81+ options = { props . branches }
82+ onChange = { props . onTrunkBranchChange }
83+ disabled = { props . disabled }
84+ className = "max-w-[120px]"
85+ />
86+ </ div >
87+ ) }
4288
43- { /* Trunk Branch Selector - hidden for Local runtime */ }
44- { showTrunkBranchSelector && (
45- < div
46- className = "flex items-center gap-1"
47- data-component = "TrunkBranchGroup"
48- data-tutorial = "trunk-branch"
49- >
50- < label htmlFor = "trunk-branch" className = "text-muted text-xs" >
51- From:
52- </ label >
53- < Select
54- id = "trunk-branch"
55- value = { props . trunkBranch }
56- options = { props . branches }
57- onChange = { props . onTrunkBranchChange }
89+ { /* SSH Host Input - after From selector */ }
90+ { props . runtimeMode === RUNTIME_MODE . SSH && (
91+ < input
92+ type = "text"
93+ value = { props . sshHost }
94+ onChange = { ( e ) => props . onSshHostChange ( e . target . value ) }
95+ placeholder = "user@host"
5896 disabled = { props . disabled }
59- className = "max-w-[120px] "
97+ className = "bg-separator text-foreground border-border-medium focus:border-accent w-32 rounded border px-1 py-0.5 text-xs focus:outline-none disabled:opacity-50 "
6098 />
99+ ) }
100+ </ div >
101+
102+ { /* Second row: Workspace name with auto-generate checkbox */ }
103+ < div className = "flex items-center gap-2" data-component = "WorkspaceNameGroup" >
104+ < label htmlFor = "workspace-name" className = "text-muted text-xs whitespace-nowrap" >
105+ Name:
106+ </ label >
107+ < div className = "relative max-w-xs flex-1" >
108+ < input
109+ id = "workspace-name"
110+ type = "text"
111+ value = { props . workspaceName }
112+ onChange = { handleNameChange }
113+ placeholder = { props . isGeneratingName ? "Generating..." : "workspace-name" }
114+ disabled = { props . disabled || props . autoGenerateName }
115+ className = { cn (
116+ "bg-separator text-foreground border-border-medium focus:border-accent w-full rounded border px-2 py-0.5 pr-6 text-xs focus:outline-none disabled:opacity-50" ,
117+ props . nameError && "border-red-500"
118+ ) }
119+ />
120+ { /* Loading indicator when generating */ }
121+ { props . isGeneratingName && (
122+ < div className = "absolute top-1/2 right-1 -translate-y-1/2" >
123+ < Loader2 className = "text-muted h-3 w-3 animate-spin" />
124+ </ div >
125+ ) }
61126 </ div >
62- ) }
127+ { /* Auto-generate checkbox */ }
128+ < label className = "text-muted flex items-center gap-1 text-[10px] whitespace-nowrap" >
129+ < input
130+ type = "checkbox"
131+ checked = { props . autoGenerateName }
132+ onChange = { ( e ) => props . onAutoGenerateChange ( e . target . checked ) }
133+ disabled = { props . disabled }
134+ className = "h-3 w-3"
135+ />
136+ auto
137+ </ label >
138+ </ div >
63139
64- { /* SSH Host Input - after From selector */ }
65- { props . runtimeMode === RUNTIME_MODE . SSH && (
66- < input
67- type = "text"
68- value = { props . sshHost }
69- onChange = { ( e ) => props . onSshHostChange ( e . target . value ) }
70- placeholder = "user@host"
71- disabled = { props . disabled }
72- className = "bg-separator text-foreground border-border-medium focus:border-accent w-32 rounded border px-1 py-0.5 text-xs focus:outline-none disabled:opacity-50"
73- />
74- ) }
140+ { /* Error display */ }
141+ { props . nameError && < div className = "text-xs text-red-500" > { props . nameError } </ div > }
75142 </ div >
76143 ) ;
77144}
0 commit comments