package ui import ( "fmt" "strconv" "github.com/charmbracelet/lipgloss" "github.com/charmbracelet/huh" log "github.com/sirupsen/logrus" ) type FormBuilder struct { widgets []huh.Field form huh.Form } func NewForm() *FormBuilder { return &FormBuilder{} } func (f *FormBuilder) AddWidget(w huh.Field) { f.widgets = append(f.widgets, w) } func (f *FormBuilder) Group(w ...huh.Field) *huh.Group { group := huh.NewGroup(w...) return group } func (f *FormBuilder) BuildGroups(g ...*huh.Group) *huh.Form { form := huh.NewForm(g...) return form } func (f *FormBuilder) Build() *huh.Form { form := huh.NewForm(huh.NewGroup(f.widgets...)).WithTheme(ThemeCustom()) return form } func (f *FormBuilder) GetString(key string) string { fmt.Println(f.form.GetString(key)) return f.form.GetString(key) } func (f *FormBuilder) GetBool(key string) bool { fmt.Println(f.form.GetString(key)) return f.form.GetBool(key) } func (f *FormBuilder) GetInt(key string) int64 { v := f.form.GetString(key) fmt.Println(v) value, err := strconv.Atoi(v) if err != nil { log.WithError(err).Fatalf(fmt.Sprintf("failed to convert value %s to integer", v)) return 0 } return int64(value) } func MultiSelect(title, key string, options []string) *huh.MultiSelect[string] { s := huh.NewMultiSelect[string](). Title(title). Options(huh.NewOptions[string](options...)...). Key(key) return s } func InteractiveList(title, key string, opts map[string]string) *huh.Select[string] { var pick string var options []huh.Option[string] s := huh.NewSelect[string](). Title(title). Key(key). Value(&pick) if opts != nil { for k, v := range opts { options = append(options, huh.Option[string]{Key: k, Value: v}) } s.Options(options...) } return s } func Input(question string, key string) *huh.Input { var answer string input := huh.NewInput(). Title(fmt.Sprintf("%s", question)). Prompt("> "). Validate(ValidateString). Key(key). Value(&answer) return input } func Confirm(question string, key string) *huh.Confirm { var happy bool confirm := huh.NewConfirm(). Title(question). Affirmative("Yes"). Negative("No"). Value(&happy). Key(key) return confirm } func Text(title, desc string) *huh.Note { t := huh.NewNote(). Title(title). Description(desc) return t } func TextField(title, key string) *huh.Text { t := huh.NewText(). Title(title).Key(key).ShowLineNumbers(true).Editor("vim") return t } func ThemeCustom() *huh.Theme { t := huh.ThemeBase() var ( normalFg = lipgloss.AdaptiveColor{Light: "235", Dark: "252"} // indigo = lipgloss.AdaptiveColor{Light: "#5A56E0", Dark: "#7571F9"} indigo = lipgloss.AdaptiveColor{Light: "32", Dark: "33"} cream = lipgloss.AdaptiveColor{Light: "#FFFDF5", Dark: "#FFFDF5"} // fuchsia = lipgloss.Color("#F780E2") green = lipgloss.AdaptiveColor{Light: "#02BA84", Dark: "#02BF87"} red = lipgloss.AdaptiveColor{Light: "#FF4672", Dark: "#ED567A"} ) t.Focused.Base = t.Focused.Base.BorderForeground(lipgloss.Color("238")) t.Focused.Title = t.Focused.Title.Foreground(White).Bold(true) t.Focused.NoteTitle = t.Focused.NoteTitle.Foreground(White).Bold(true).MarginBottom(1) t.Focused.Directory = t.Focused.Directory.Foreground(indigo) t.Focused.Description = t.Focused.Description.Foreground(lipgloss.AdaptiveColor{Light: "", Dark: "243"}) t.Focused.ErrorIndicator = t.Focused.ErrorIndicator.Foreground(red) t.Focused.ErrorMessage = t.Focused.ErrorMessage.Foreground(red) t.Focused.SelectSelector = t.Focused.SelectSelector.Foreground(green) t.Focused.NextIndicator = t.Focused.NextIndicator.Foreground(indigo) t.Focused.PrevIndicator = t.Focused.PrevIndicator.Foreground(indigo) t.Focused.Option = t.Focused.Option.Foreground(normalFg) t.Focused.MultiSelectSelector = t.Focused.MultiSelectSelector.Foreground(green) t.Focused.SelectedOption = t.Focused.SelectedOption.Foreground(green) t.Focused.SelectedPrefix = lipgloss.NewStyle().Foreground(green).SetString("✓ ") t.Focused.UnselectedPrefix = lipgloss.NewStyle().Foreground(lipgloss.AdaptiveColor{Light: "", Dark: "243"}).SetString("• ") t.Focused.UnselectedOption = t.Focused.UnselectedOption.Foreground(normalFg) t.Focused.FocusedButton = t.Focused.FocusedButton.Foreground(cream).Background(indigo) t.Focused.Next = t.Focused.FocusedButton t.Focused.BlurredButton = t.Focused.BlurredButton.Foreground(normalFg).Background(lipgloss.AdaptiveColor{Light: "252", Dark: "237"}) t.Focused.TextInput.Cursor = t.Focused.TextInput.Cursor.Foreground(Gray) t.Focused.TextInput.Placeholder = t.Focused.TextInput.Placeholder.Foreground(lipgloss.AdaptiveColor{Light: "248", Dark: "238"}) t.Focused.TextInput.Prompt = t.Focused.TextInput.Prompt.Foreground(green) t.Blurred = t.Focused t.Blurred.Base = t.Focused.Base.BorderStyle(lipgloss.HiddenBorder()) t.Blurred.NextIndicator = lipgloss.NewStyle() t.Blurred.PrevIndicator = lipgloss.NewStyle() return t }