feat: main flow created
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"charm.land/bubbles/v2/textinput"
|
||||
tea "charm.land/bubbletea/v2"
|
||||
)
|
||||
|
||||
type FieldType int
|
||||
|
||||
const (
|
||||
FieldTypeText FieldType = iota
|
||||
FieldTypeSelect
|
||||
FieldTypePassword
|
||||
FieldTypeNumber
|
||||
)
|
||||
|
||||
type FormField struct {
|
||||
Label string
|
||||
Placeholder string
|
||||
Default string
|
||||
Type FieldType
|
||||
Options []string // for FieldTypeSelect
|
||||
optionIdx int // for FieldTypeSelect
|
||||
CharLimit int
|
||||
input textinput.Model
|
||||
}
|
||||
|
||||
type FormStep struct {
|
||||
Title string
|
||||
Fields []FormField
|
||||
focus int
|
||||
}
|
||||
|
||||
func NewFormStep(title string, fields []FormField) FormStep {
|
||||
f := FormStep{Title: title, Fields: fields}
|
||||
for i := range f.Fields {
|
||||
if f.Fields[i].Type == FieldTypeSelect {
|
||||
for j, opt := range f.Fields[i].Options {
|
||||
// Apply default option
|
||||
if opt == f.Fields[i].Default {
|
||||
f.Fields[i].optionIdx = j
|
||||
break
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
ti := textinput.New()
|
||||
ti.Placeholder = f.Fields[i].Placeholder
|
||||
if f.Fields[i].Default != "" {
|
||||
ti.SetValue(f.Fields[i].Default)
|
||||
}
|
||||
|
||||
if f.Fields[i].CharLimit > 0 {
|
||||
ti.CharLimit = f.Fields[i].CharLimit
|
||||
}
|
||||
|
||||
if f.Fields[i].Type == FieldTypePassword {
|
||||
ti.EchoMode = textinput.EchoPassword
|
||||
}
|
||||
|
||||
if i == 0 {
|
||||
ti.Focus()
|
||||
}
|
||||
|
||||
f.Fields[i].input = ti
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
// Update
|
||||
func (f *FormStep) Update(msg tea.Msg) (done bool, cmd tea.Cmd) {
|
||||
focused := &f.Fields[f.focus]
|
||||
isSelect := focused.Type == FieldTypeSelect
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyPressMsg:
|
||||
switch msg.String() {
|
||||
// Select specific inputs
|
||||
case "left", "h":
|
||||
if isSelect && focused.optionIdx > 0 {
|
||||
focused.optionIdx--
|
||||
}
|
||||
|
||||
case "right", "l":
|
||||
if isSelect && focused.optionIdx < len(focused.Options)-1 {
|
||||
focused.optionIdx++
|
||||
}
|
||||
|
||||
// Generic inputs
|
||||
case "tab", "down":
|
||||
f.blurCurrent()
|
||||
f.focus = (f.focus + 1) % len(f.Fields)
|
||||
f.focusCurrent()
|
||||
|
||||
case "shift+tab", "up":
|
||||
f.blurCurrent()
|
||||
f.focus = (f.focus - 1 + len(f.Fields)) % len(f.Fields)
|
||||
f.focusCurrent()
|
||||
|
||||
case "enter":
|
||||
if f.focus == len(f.Fields)-1 {
|
||||
return true, nil
|
||||
}
|
||||
f.blurCurrent()
|
||||
f.focus++
|
||||
f.focusCurrent()
|
||||
}
|
||||
}
|
||||
|
||||
var c tea.Cmd
|
||||
f.Fields[f.focus].input, c = f.Fields[f.focus].input.Update(msg)
|
||||
return false, c
|
||||
}
|
||||
|
||||
func (f *FormStep) Values() map[string]string {
|
||||
out := make(map[string]string)
|
||||
for _, field := range f.Fields {
|
||||
if field.Type == FieldTypeSelect {
|
||||
out[field.Label] = field.Options[field.optionIdx]
|
||||
} else {
|
||||
out[field.Label] = field.input.Value()
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// View
|
||||
func (f FormStep) View() string {
|
||||
pad := strings.Repeat(" ", padding)
|
||||
var sb strings.Builder
|
||||
|
||||
sb.WriteString(pad + TitleStyle.Render(f.Title) + "")
|
||||
|
||||
for i, field := range f.Fields {
|
||||
sb.WriteString(renderField(field, i == f.focus))
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func renderField(field FormField, focused bool) string {
|
||||
pad := strings.Repeat(" ", padding)
|
||||
|
||||
label := " " + field.Label
|
||||
if focused {
|
||||
label = CursorStyle.Render("> " + field.Label)
|
||||
}
|
||||
|
||||
var value string
|
||||
switch field.Type {
|
||||
case FieldTypeSelect:
|
||||
var opts []string
|
||||
for i, opt := range field.Options {
|
||||
if i == field.optionIdx {
|
||||
opts = append(opts, SelectedStyle.Render("[ "+opt+" ]"))
|
||||
} else {
|
||||
opts = append(opts, DimStyle.Render(" "+opt+" "))
|
||||
}
|
||||
}
|
||||
value = strings.Join(opts, " ")
|
||||
if focused {
|
||||
value += HelpStyle.Render(" ← →")
|
||||
}
|
||||
default:
|
||||
value = field.input.View()
|
||||
}
|
||||
|
||||
return fmt.Sprintf("\n\n%s%s\n%s %s", pad, label, pad, value)
|
||||
}
|
||||
|
||||
// Helpers
|
||||
func (f *FormStep) blurCurrent() {
|
||||
if f.Fields[f.focus].Type != FieldTypeSelect {
|
||||
f.Fields[f.focus].input.Blur()
|
||||
}
|
||||
}
|
||||
|
||||
func (f *FormStep) focusCurrent() {
|
||||
if f.Fields[f.focus].Type != FieldTypeSelect {
|
||||
f.Fields[f.focus].input.Focus()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user