Skip to content

Commit 2ae86a3

Browse files
authored
feat: add placeholder to app name prompt in create command (#370)
* feat: add placeholder to app name prompt in create command Show the randomly generated app name as placeholder text in the input field instead of printing a separate hint line. This provides a cleaner UX where users see the default name inline and can press Enter to accept. * style: display survey input placeholder text in grey
1 parent 4973cd6 commit 2ae86a3

5 files changed

Lines changed: 50 additions & 5 deletions

File tree

cmd/project/create.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,9 @@ func runCreateCommand(clients *shared.ClientFactory, cmd *cobra.Command, args []
142142
if appNameArg == "" {
143143
if clients.IO.IsTTY() {
144144
defaultName := generateRandomAppName()
145-
cmd.Print(style.Secondary(fmt.Sprintf(" Press Enter to use the generated name: %s", defaultName)), "\n")
146-
name, err := clients.IO.InputPrompt(ctx, "Name your app:", iostreams.InputPromptConfig{})
145+
name, err := clients.IO.InputPrompt(ctx, "Name your app:", iostreams.InputPromptConfig{
146+
Placeholder: defaultName,
147+
})
147148
if err != nil {
148149
return err
149150
}

cmd/project/create_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,38 @@ func TestCreateCommand(t *testing.T) {
348348
cm.IO.AssertNotCalled(t, "InputPrompt", mock.Anything, "Name your app:", mock.Anything)
349349
},
350350
},
351+
"name prompt includes placeholder with generated name": {
352+
Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) {
353+
cm.IO.On("IsTTY").Return(true)
354+
cm.IO.On("SelectPrompt", mock.Anything, "Select an app:", mock.Anything, mock.Anything).
355+
Return(
356+
iostreams.SelectPromptResponse{
357+
Prompt: true,
358+
Index: 0,
359+
},
360+
nil,
361+
)
362+
cm.IO.On("SelectPrompt", mock.Anything, "Select a language:", mock.Anything, mock.Anything).
363+
Return(
364+
iostreams.SelectPromptResponse{
365+
Prompt: true,
366+
Index: 0,
367+
},
368+
nil,
369+
)
370+
cm.IO.On("InputPrompt", mock.Anything, "Name your app:", mock.Anything).
371+
Return("my-app", nil)
372+
createClientMock = new(CreateClientMock)
373+
createClientMock.On("Create", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return("", nil)
374+
CreateFunc = createClientMock.Create
375+
},
376+
ExpectedAsserts: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock) {
377+
// Verify that InputPrompt was called with a config that has a non-empty Placeholder
378+
cm.IO.AssertCalled(t, "InputPrompt", mock.Anything, "Name your app:", mock.MatchedBy(func(cfg iostreams.InputPromptConfig) bool {
379+
return cfg.Placeholder != ""
380+
}))
381+
},
382+
},
351383
"user accepts default name from prompt": {
352384
Setup: func(t *testing.T, ctx context.Context, cm *shared.ClientsMock, cf *shared.ClientFactory) {
353385
cm.IO.On("IsTTY").Return(true)

internal/iostreams/charm.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
func buildInputForm(message string, cfg InputPromptConfig, input *string) *huh.Form {
3030
field := huh.NewInput().
3131
Title(message).
32+
Placeholder(cfg.Placeholder).
3233
Value(input)
3334
if cfg.Required {
3435
field.Validate(huh.ValidateMinLength(1))

internal/iostreams/charm_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,15 @@ func TestCharmInput(t *testing.T) {
5353
assert.Contains(t, view, "Huh")
5454
})
5555

56+
t.Run("renders placeholder text", func(t *testing.T) {
57+
var input string
58+
f := buildInputForm("Name?", InputPromptConfig{Placeholder: "my-cool-app"}, &input)
59+
f.Update(f.Init())
60+
61+
view := ansi.Strip(f.View())
62+
assert.Contains(t, view, "my-cool-app")
63+
})
64+
5665
t.Run("stores typed value", func(t *testing.T) {
5766
var input string
5867
f := buildInputForm("Name?", InputPromptConfig{}, &input)

internal/iostreams/survey.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -174,12 +174,13 @@ var InputQuestionTemplate = fmt.Sprintf(`
174174
{{- if and .Help (not .ShowHelp)}}{{ print .Config.HelpInput }} for help {{- if and .Suggest}}, {{end}}{{end -}}
175175
{{- if and .Suggest }}{{color "cyan"}}{{ print .Config.SuggestInput }} for suggestions{{end -}}
176176
]{{color "reset"}} {{end}}
177-
{{- if .Default}}{{color "white"}}({{.Default}}) {{color "reset"}}{{end}}
178-
{{- end}}`, blue())
177+
{{- if .Default}}{{color "%s"}}({{.Default}}) {{color "reset"}}{{end}}
178+
{{- end}}`, blue(), gray())
179179

180180
// InputPromptConfig holds additional config for an Input prompt
181181
type InputPromptConfig struct {
182-
Required bool // Whether the input must be non-empty
182+
Required bool // Whether the input must be non-empty
183+
Placeholder string // Placeholder text shown when input is empty
183184
}
184185

185186
// GetFlags returns all flags for the Input prompt
@@ -208,6 +209,7 @@ func (io *IOStreams) InputPrompt(ctx context.Context, message string, cfg InputP
208209
var input string
209210
err := survey.AskOne(&survey.Input{
210211
Message: message,
212+
Default: cfg.Placeholder,
211213
}, &input, SurveyOptions(cfg)...)
212214

213215
if err != nil {

0 commit comments

Comments
 (0)