@@ -74,6 +74,8 @@ export function ConfigPanel() {
7474 const features = useFeatures ( )
7575 const [ dropdownOpen , setDropdownOpen ] = useState ( false )
7676 const [ deployDialogOpen , setDeployDialogOpen ] = useState ( false )
77+ const [ cliCopied , setCliCopied ] = useState ( false )
78+ const cliCommand = useCliCommand ( )
7779 const [ deployDialogProvider , setDeployDialogProvider ] = useState <
7880 'cloudflare' | 'netlify' | 'railway' | null
7981 > ( null )
@@ -94,31 +96,56 @@ export function ConfigPanel() {
9496 < div className = "h-full flex flex-col bg-white dark:bg-gray-900 border-r border-gray-200 dark:border-gray-800" >
9597 { /* Header - Project Name + Build Button */ }
9698 < div className = "shrink-0 p-4 border-b border-gray-200 dark:border-gray-800" >
97- < div className = "flex items-center gap-2" >
99+ < div className = "flex flex-wrap items-center gap-2" >
98100 < input
99101 type = "text"
100102 value = { projectName }
101103 onChange = { ( e ) => setProjectName ( e . target . value ) }
102- className = "min-w-0 flex-1 h-8 bg-gray-50 dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded-md px-2 text-gray-900 dark:text-white font-mono text-xs focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-cyan-500 focus:border-transparent"
104+ className = "min-w-[140px] flex-1 h-8 bg-gray-50 dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded-md px-2 text-gray-900 dark:text-white font-mono text-xs focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-cyan-500 focus:border-transparent"
103105 placeholder = "my-tanstack-app"
104106 />
105- < BuildProjectDropdown
106- onOpenChange = { setDropdownOpen }
107- onCreateRepo = { ( ) => openDeployDialog ( null ) }
108- />
109- { deployProvider && (
110- < button
111- onClick = { ( ) => openDeployDialog ( deployProvider . provider ) }
112- className = "shrink-0 h-8 px-2.5 text-xs text-white rounded-md transition-opacity hover:opacity-90 whitespace-nowrap flex items-center gap-1"
113- style = { { backgroundColor : deployProvider . color } }
114- >
115- < Rocket className = "w-3.5 h-3.5" />
116- < span className = "font-medium" > Deploy</ span >
117- < span className = "opacity-80 text-[10px]" >
118- to { deployProvider . name }
119- </ span >
120- </ button >
121- ) }
107+ < div className = "flex flex-wrap items-center gap-2" >
108+ < div className = "relative" >
109+ < button
110+ onClick = { async ( ) => {
111+ await navigator . clipboard . writeText ( cliCommand )
112+ setCliCopied ( true )
113+ setTimeout ( ( ) => setCliCopied ( false ) , 2000 )
114+ } }
115+ className = "shrink-0 h-8 px-2 flex items-center justify-center gap-1.5 rounded-md border border-gray-300 dark:border-gray-700 bg-white dark:bg-gray-800 text-gray-600 dark:text-gray-400 hover:border-gray-400 dark:hover:border-gray-600 hover:text-gray-900 dark:hover:text-white transition-colors text-xs font-medium"
116+ title = "Copy CLI command"
117+ >
118+ { cliCopied ? (
119+ < Check className = "w-3.5 h-3.5 text-green-500" />
120+ ) : (
121+ < Copy className = "w-3.5 h-3.5" />
122+ ) }
123+ CLI
124+ </ button >
125+ { cliCopied && (
126+ < div className = "absolute top-full left-1/2 -translate-x-1/2 mt-1 px-2 py-1 text-xs font-medium text-white bg-gray-900 dark:bg-white dark:text-gray-900 rounded shadow-lg whitespace-nowrap z-10" >
127+ Copied!
128+ </ div >
129+ ) }
130+ </ div >
131+ < BuildProjectDropdown
132+ onOpenChange = { setDropdownOpen }
133+ onCreateRepo = { ( ) => openDeployDialog ( null ) }
134+ />
135+ { deployProvider && (
136+ < button
137+ onClick = { ( ) => openDeployDialog ( deployProvider . provider ) }
138+ className = "shrink-0 h-8 px-2.5 text-xs text-white rounded-md transition-opacity hover:opacity-90 whitespace-nowrap flex items-center gap-1"
139+ style = { { backgroundColor : deployProvider . color } }
140+ >
141+ < Rocket className = "w-3.5 h-3.5" />
142+ < span className = "font-medium" > Deploy</ span >
143+ < span className = "opacity-80 text-[10px]" >
144+ to { deployProvider . name }
145+ </ span >
146+ </ button >
147+ ) }
148+ </ div >
122149 </ div >
123150 </ div >
124151
@@ -150,6 +177,11 @@ export function ConfigPanel() {
150177 < StylesPicker />
151178 </ div >
152179
180+ { /* Package Manager */ }
181+ < div className = "p-4 pb-0" >
182+ < PackageManagerPicker />
183+ </ div >
184+
153185 { /* Feature Picker */ }
154186 < FeaturePicker />
155187
@@ -184,32 +216,14 @@ function IntegrationSearch() {
184216 )
185217}
186218
187- const PACKAGE_MANAGERS = [ 'pnpm' , 'npm' , 'yarn' , 'bun' ] as const
188-
189219function CliOptionsInline ( ) {
190- const packageManager = useBuilderStore ( ( s ) => s . packageManager )
191- const setPackageManager = useBuilderStore ( ( s ) => s . setPackageManager )
192220 const skipInstall = useBuilderStore ( ( s ) => s . skipInstall )
193221 const setSkipInstall = useBuilderStore ( ( s ) => s . setSkipInstall )
194222 const skipGit = useBuilderStore ( ( s ) => s . skipGit )
195223 const setSkipGit = useBuilderStore ( ( s ) => s . setSkipGit )
196224
197225 return (
198226 < div className = "flex flex-wrap items-center gap-x-4 gap-y-2 text-xs text-gray-600 dark:text-gray-400" >
199- < select
200- value = { packageManager }
201- onChange = { ( e ) =>
202- setPackageManager ( e . target . value as ( typeof PACKAGE_MANAGERS ) [ number ] )
203- }
204- className = "bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded px-2 py-1 text-xs focus:outline-none focus:ring-1 focus:ring-blue-500 dark:focus:ring-cyan-500"
205- >
206- { PACKAGE_MANAGERS . map ( ( pm ) => (
207- < option key = { pm } value = { pm } >
208- { pm }
209- </ option >
210- ) ) }
211- </ select >
212-
213227 < label className = "flex items-center gap-1.5 cursor-pointer" >
214228 < input
215229 type = "checkbox"
@@ -368,6 +382,43 @@ function StylesPicker() {
368382 )
369383}
370384
385+ const PACKAGE_MANAGERS = [ 'pnpm' , 'npm' , 'yarn' , 'bun' ] as const
386+
387+ function PackageManagerPicker ( ) {
388+ const packageManager = useBuilderStore ( ( s ) => s . packageManager )
389+ const setPackageManager = useBuilderStore ( ( s ) => s . setPackageManager )
390+
391+ return (
392+ < div >
393+ < h3 className = "text-base font-semibold text-gray-700 dark:text-gray-300 mb-1" >
394+ Package Manager
395+ </ h3 >
396+ < p className = "text-xs text-gray-500 dark:text-gray-400 mb-3" >
397+ CLI tool for dependencies
398+ </ p >
399+ < div className = "flex gap-1" >
400+ { PACKAGE_MANAGERS . map ( ( pm ) => {
401+ const isSelected = packageManager === pm
402+ return (
403+ < button
404+ key = { pm }
405+ onClick = { ( ) => setPackageManager ( pm ) }
406+ className = { twMerge (
407+ 'flex-1 px-2 py-1.5 text-xs font-medium rounded-md border-2 transition-all' ,
408+ isSelected
409+ ? 'bg-blue-50 dark:bg-cyan-950 border-blue-500 dark:border-cyan-500 text-blue-700 dark:text-cyan-300'
410+ : 'bg-white dark:bg-gray-800/50 border-gray-200 dark:border-gray-700 text-gray-600 dark:text-gray-400 hover:border-gray-300 dark:hover:border-gray-600' ,
411+ ) }
412+ >
413+ { pm }
414+ </ button >
415+ )
416+ } ) }
417+ </ div >
418+ </ div >
419+ )
420+ }
421+
371422function SelectedFeatureOptions ( { features } : { features : Array < string > } ) {
372423 const availableFeatures = useBuilderStore ( ( s ) => s . availableFeatures )
373424
0 commit comments