Gitfed
bastien-mrq/gitfed / cmd / gitfed-tui / form.go
package main

import (
	"github.com/charmbracelet/bubbles/textinput"
	tea "github.com/charmbracelet/bubbletea"
	"github.com/charmbracelet/lipgloss"
)

// form is a tiny multi-field text input flow: tab/down/up move focus,
// enter on the last field submits, esc cancels.
type form struct {
	title    string
	labels   []string
	inputs   []textinput.Model
	focus    int
	onSubmit func(values []string) tea.Msg
	onCancel func() tea.Msg
}

func newForm(title string, labels []string, placeholders []string, onSubmit func([]string) tea.Msg, onCancel func() tea.Msg) *form {
	inputs := make([]textinput.Model, len(labels))
	for i := range inputs {
		ti := textinput.New()
		if i < len(placeholders) {
			ti.Placeholder = placeholders[i]
		}
		ti.CharLimit = 256
		ti.Width = 60
		if i == 0 {
			ti.Focus()
		}
		inputs[i] = ti
	}
	return &form{title: title, labels: labels, inputs: inputs, onSubmit: onSubmit, onCancel: onCancel}
}

func (f *form) Update(msg tea.Msg) (*form, tea.Cmd) {
	if keyMsg, ok := msg.(tea.KeyMsg); ok {
		switch keyMsg.String() {
		case "esc":
			return f, func() tea.Msg { return f.onCancel() }
		case "tab", "down":
			f.inputs[f.focus].Blur()
			f.focus = (f.focus + 1) % len(f.inputs)
			f.inputs[f.focus].Focus()
			return f, nil
		case "shift+tab", "up":
			f.inputs[f.focus].Blur()
			f.focus = (f.focus - 1 + len(f.inputs)) % len(f.inputs)
			f.inputs[f.focus].Focus()
			return f, nil
		case "enter":
			if f.focus == len(f.inputs)-1 {
				values := make([]string, len(f.inputs))
				for i, in := range f.inputs {
					values[i] = in.Value()
				}
				return f, func() tea.Msg { return f.onSubmit(values) }
			}
			f.inputs[f.focus].Blur()
			f.focus++
			f.inputs[f.focus].Focus()
			return f, nil
		}
	}
	var cmd tea.Cmd
	f.inputs[f.focus], cmd = f.inputs[f.focus].Update(msg)
	return f, cmd
}

var (
	formTitleStyle = lipgloss.NewStyle().Bold(true).MarginBottom(1)
	formLabelStyle = lipgloss.NewStyle().Width(14)
	formHintStyle  = lipgloss.NewStyle().Faint(true).MarginTop(1)
)

func (f *form) View() string {
	s := formTitleStyle.Render(f.title) + "\n"
	for i, in := range f.inputs {
		s += formLabelStyle.Render(f.labels[i]+":") + in.View() + "\n"
	}
	s += formHintStyle.Render("tab/↑↓ move · enter next/submit · esc cancel")
	return s
}