Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions examples/dynamic/dynamic-default/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package main

import (
"fmt"
"strings"

"github.com/charmbracelet/huh"
)

func main() {

var name, id, directory string

form := huh.NewForm(
huh.NewGroup(
huh.NewInput().Title("Project name").Value(&name).Placeholder("<Placeholder>").Validate(huh.ValidateNotEmpty()),
huh.NewInput().Title("Project ID").Value(&id).DefaultFunc(func() string {
return strings.ReplaceAll(strings.ToLower(name), "/", "-")
}, &name),
huh.NewInput().Title("Project directory").Value(&directory).DefaultFunc(func() string {
if id == "" {
return "~/projects/<Project ID>"
}
return "~/projects/" + id
}, &id),
),
)

if err := form.Run(); err != nil {
fmt.Println(err)
}
fmt.Println("Project Name: ", name)
fmt.Println("Project ID: ", id)
fmt.Println("Directory: ", directory)
}
56 changes: 48 additions & 8 deletions field_input.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,11 @@ type Input struct {
key string
id int

title Eval[string]
description Eval[string]
placeholder Eval[string]
suggestions Eval[[]string]
title Eval[string]
description Eval[string]
placeholder Eval[string]
placeholderIsDefault bool
suggestions Eval[[]string]

textinput textinput.Model

Expand Down Expand Up @@ -215,11 +216,27 @@ func (i *Input) Placeholder(str string) *Input {

// PlaceholderFunc sets the placeholder func of the text input.
func (i *Input) PlaceholderFunc(f func() string, bindings any) *Input {
i.placeholderIsDefault = false
i.placeholder.fn = f
i.placeholder.bindings = bindings
return i
}

// Default sets the default value of the text input. This looks the same as a placeholder,
// but it is the field's value unless the user overrides it.
func (i *Input) Default(str string) *Input {
i.Placeholder(str)
i.placeholderIsDefault = true
return i
}

// DefaultFunc sets the default func of the text input.
func (i *Input) DefaultFunc(f func() string, bindings any) *Input {
i.PlaceholderFunc(f, bindings)
i.placeholderIsDefault = true
return i
}

// Inline sets whether the title and input should be on the same line.
func (i *Input) Inline(inline bool) *Input {
i.inline = inline
Expand Down Expand Up @@ -250,7 +267,7 @@ func (i *Input) Focus() tea.Cmd {
// Blur blurs the input field.
func (i *Input) Blur() tea.Cmd {
i.focused = false
i.accessor.Set(i.textinput.Value())
i.accessor.Set(i.valueOrDefault())
i.textinput.Blur()
i.err = i.validate(i.accessor.Get())
return nil
Expand Down Expand Up @@ -346,18 +363,29 @@ func (i *Input) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case key.Matches(msg, i.keymap.Prev):
cmds = append(cmds, PrevField)
case key.Matches(msg, i.keymap.Next, i.keymap.Submit):
value := i.textinput.Value()
value := i.valueOrDefault()
i.err = i.validate(value)
if i.err != nil {
return i, nil
}
cmds = append(cmds, NextField)
case key.Matches(msg,
i.textinput.KeyMap.CharacterForward,
i.textinput.KeyMap.DeleteCharacterForward,
i.textinput.KeyMap.WordForward,
i.textinput.KeyMap.DeleteWordForward,
i.textinput.KeyMap.LineEnd):

if i.placeholderIsDefault && i.textinput.Value() == "" {
i.textinput.SetValue(i.textinput.Placeholder)
i.textinput.CursorStart()
}
}
}

i.textinput, cmd = i.textinput.Update(msg)
cmds = append(cmds, cmd)
i.accessor.Set(i.textinput.Value())
i.accessor.Set(i.valueOrDefault())

return i, tea.Batch(cmds...)
}
Expand All @@ -381,7 +409,11 @@ func (i *Input) View() string {
// NB: since the method is on a pointer receiver these are being mutated.
// Because this runs on every render this shouldn't matter in practice,
// however.
i.textinput.PlaceholderStyle = styles.TextInput.Placeholder
if i.placeholderIsDefault {
i.textinput.PlaceholderStyle = styles.TextInput.DefaultValue
} else {
i.textinput.PlaceholderStyle = styles.TextInput.Placeholder
}
i.textinput.PromptStyle = styles.TextInput.Prompt
i.textinput.Cursor.Style = styles.TextInput.Cursor
i.textinput.Cursor.TextStyle = styles.TextInput.CursorText
Expand Down Expand Up @@ -506,3 +538,11 @@ func (i *Input) GetKey() string { return i.key }
func (i *Input) GetValue() any {
return i.accessor.Get()
}

func (i *Input) valueOrDefault() string {
value := i.textinput.Value()
if i.placeholderIsDefault && value == "" {
value = i.textinput.Placeholder
}
return value
}
17 changes: 12 additions & 5 deletions theme.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,12 @@ type FieldStyles struct {

// TextInputStyles are the styles for text inputs.
type TextInputStyles struct {
Cursor lipgloss.Style
CursorText lipgloss.Style
Placeholder lipgloss.Style
Prompt lipgloss.Style
Text lipgloss.Style
Cursor lipgloss.Style
CursorText lipgloss.Style
Placeholder lipgloss.Style
DefaultValue lipgloss.Style
Prompt lipgloss.Style
Text lipgloss.Style
}

const (
Expand Down Expand Up @@ -108,6 +109,7 @@ func ThemeBase() *Theme {
t.Focused.FocusedButton = button.Foreground(lipgloss.Color("0")).Background(lipgloss.Color("7"))
t.Focused.BlurredButton = button.Foreground(lipgloss.Color("7")).Background(lipgloss.Color("0"))
t.Focused.TextInput.Placeholder = lipgloss.NewStyle().Foreground(lipgloss.Color("8"))
t.Focused.TextInput.DefaultValue = lipgloss.NewStyle().Foreground(lipgloss.Color("7"))

t.Help = help.New().Styles

Expand Down Expand Up @@ -158,6 +160,7 @@ func ThemeCharm() *Theme {

t.Focused.TextInput.Cursor = t.Focused.TextInput.Cursor.Foreground(green)
t.Focused.TextInput.Placeholder = t.Focused.TextInput.Placeholder.Foreground(lipgloss.AdaptiveColor{Light: "248", Dark: "238"})
t.Focused.TextInput.DefaultValue = t.Focused.TextInput.DefaultValue.Foreground(lipgloss.AdaptiveColor{Light: "243", Dark: "248"})
t.Focused.TextInput.Prompt = t.Focused.TextInput.Prompt.Foreground(fuchsia)

t.Blurred = t.Focused
Expand Down Expand Up @@ -209,6 +212,7 @@ func ThemeDracula() *Theme {

t.Focused.TextInput.Cursor = t.Focused.TextInput.Cursor.Foreground(yellow)
t.Focused.TextInput.Placeholder = t.Focused.TextInput.Placeholder.Foreground(comment)
t.Focused.TextInput.DefaultValue = t.Focused.TextInput.DefaultValue.Foreground(yellow)
t.Focused.TextInput.Prompt = t.Focused.TextInput.Prompt.Foreground(yellow)

t.Blurred = t.Focused
Expand Down Expand Up @@ -247,6 +251,7 @@ func ThemeBase16() *Theme {

t.Focused.TextInput.Cursor.Foreground(lipgloss.Color("5"))
t.Focused.TextInput.Placeholder.Foreground(lipgloss.Color("8"))
t.Focused.TextInput.DefaultValue.Foreground(lipgloss.Color("7"))
t.Focused.TextInput.Prompt.Foreground(lipgloss.Color("3"))

t.Blurred = t.Focused
Expand Down Expand Up @@ -278,6 +283,7 @@ func ThemeCatppuccin() *Theme {
text = lipgloss.AdaptiveColor{Light: light.Text().Hex, Dark: dark.Text().Hex}
subtext1 = lipgloss.AdaptiveColor{Light: light.Subtext1().Hex, Dark: dark.Subtext1().Hex}
subtext0 = lipgloss.AdaptiveColor{Light: light.Subtext0().Hex, Dark: dark.Subtext0().Hex}
overlay2 = lipgloss.AdaptiveColor{Light: light.Overlay2().Hex, Dark: dark.Overlay2().Hex}
overlay1 = lipgloss.AdaptiveColor{Light: light.Overlay1().Hex, Dark: dark.Overlay1().Hex}
overlay0 = lipgloss.AdaptiveColor{Light: light.Overlay0().Hex, Dark: dark.Overlay0().Hex}
green = lipgloss.AdaptiveColor{Light: light.Green().Hex, Dark: dark.Green().Hex}
Expand Down Expand Up @@ -309,6 +315,7 @@ func ThemeCatppuccin() *Theme {

t.Focused.TextInput.Cursor = t.Focused.TextInput.Cursor.Foreground(cursor)
t.Focused.TextInput.Placeholder = t.Focused.TextInput.Placeholder.Foreground(overlay0)
t.Focused.TextInput.DefaultValue = t.Focused.TextInput.DefaultValue.Foreground(overlay2)
t.Focused.TextInput.Prompt = t.Focused.TextInput.Prompt.Foreground(pink)

t.Blurred = t.Focused
Expand Down
Loading