diff --git a/examples/credit-card-form/main.go b/examples/credit-card-form/main.go new file mode 100644 index 0000000000..e40de7d215 --- /dev/null +++ b/examples/credit-card-form/main.go @@ -0,0 +1,149 @@ +package main + +import ( + "fmt" + "log" + + "github.com/charmbracelet/bubbles/textinput" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" +) + +func main() { + p := tea.NewProgram(initialModel()) + + if err := p.Start(); err != nil { + log.Fatal(err) + } +} + +type tickMsg struct{} +type errMsg error + +const ( + ccn = iota + exp + cvv +) + +const ( + hotPink = lipgloss.Color("#FF06B7") + darkGray = lipgloss.Color("#767676") +) + +var ( + inputStyle = lipgloss.NewStyle().Foreground(hotPink) + continueStyle = lipgloss.NewStyle().Foreground(darkGray) +) + +type model struct { + inputs []textinput.Model + focused int + err error +} + +func initialModel() model { + var inputs []textinput.Model = make([]textinput.Model, 3) + inputs[ccn] = textinput.New() + inputs[ccn].Placeholder = "4505 **** **** 1234" + inputs[ccn].Focus() + inputs[ccn].CharLimit = 20 + inputs[ccn].Width = 30 + inputs[ccn].Prompt = "" + + inputs[exp] = textinput.New() + inputs[exp].Placeholder = "MM/YY " + inputs[exp].CharLimit = 5 + inputs[exp].Width = 5 + inputs[exp].Prompt = "" + + inputs[cvv] = textinput.New() + inputs[cvv].Placeholder = "XXX" + inputs[cvv].CharLimit = 3 + inputs[cvv].Width = 5 + inputs[cvv].Prompt = "" + + return model{ + inputs: inputs, + focused: 0, + err: nil, + } +} + +func (m model) Init() tea.Cmd { + return textinput.Blink +} + +func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + var ( + cmds []tea.Cmd = make([]tea.Cmd, len(m.inputs)) + ) + + switch msg := msg.(type) { + case tea.KeyMsg: + switch msg.Type { + case tea.KeyEnter: + if m.focused == len(m.inputs)-1 { + return m, tea.Quit + } else { + m.nextInput() + } + case tea.KeyCtrlC, tea.KeyEsc: + return m, tea.Quit + case tea.KeyShiftTab, tea.KeyCtrlP: + m.prevInput() + case tea.KeyTab, tea.KeyCtrlN: + m.nextInput() + } + for i := range m.inputs { + m.inputs[i].Blur() + } + m.inputs[m.focused].Focus() + + // We handle errors just like any other message + case errMsg: + m.err = msg + return m, nil + } + + for i := range m.inputs { + m.inputs[i], cmds[i] = m.inputs[i].Update(msg) + } + return m, tea.Batch(cmds...) +} + +func (m model) View() string { + return fmt.Sprintf( + ` Total: $21.50: + + %s + %s + + %s %s + %s %s + + %s +`, + inputStyle.Width(30).Render("Card Number"), + m.inputs[ccn].View(), + inputStyle.Width(6).Render("EXP"), + inputStyle.Width(6).Render("CVV"), + m.inputs[exp].View(), + m.inputs[cvv].View(), + continueStyle.Render("Continue ->"), + ) + "\n" +} + +// nextInput focuses the next input field +func (m *model) nextInput() { + m.focused = (m.focused + 1) % len(m.inputs) +} + +// prevInput focuses the previous input field +func (m *model) prevInput() { + m.focused-- + // Wrap around + if m.focused < 0 { + m.focused = len(m.inputs) - 1 + } +}