composer.go

  1package tui
  2
  3import (
  4	"github.com/charmbracelet/bubbles/textarea"
  5	"github.com/charmbracelet/bubbles/textinput"
  6	tea "github.com/charmbracelet/bubbletea"
  7	"github.com/charmbracelet/lipgloss"
  8)
  9
 10// Styles for the UI
 11var (
 12	focusedStyle        = lipgloss.NewStyle().Foreground(lipgloss.Color("205"))
 13	blurredStyle        = lipgloss.NewStyle().Foreground(lipgloss.Color("240"))
 14	cursorStyle         = focusedStyle.Copy()
 15	noStyle             = lipgloss.NewStyle()
 16	helpStyle           = blurredStyle.Copy()
 17	focusedButton       = focusedStyle.Copy().Render("[ Send ]")
 18	blurredButton       = blurredStyle.Copy().Render("[ Send ]")
 19	emailRecipientStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("205")).Bold(true)
 20)
 21
 22// Composer model holds the state of the email composition UI.
 23type Composer struct {
 24	focusIndex int
 25	toInput    textinput.Model
 26	subjectInput textinput.Model
 27	bodyInput  textarea.Model
 28	fromAddr   string
 29}
 30
 31// NewComposer initializes a new composer model.
 32func NewComposer(from string) Composer {
 33	m := Composer{fromAddr: from}
 34
 35	m.toInput = textinput.New()
 36	m.toInput.Cursor.Style = cursorStyle
 37	m.toInput.Placeholder = "To"
 38	m.toInput.Focus()
 39	m.toInput.Prompt = "> "
 40	m.toInput.CharLimit = 256
 41
 42	m.subjectInput = textinput.New()
 43	m.subjectInput.Cursor.Style = cursorStyle
 44	m.subjectInput.Placeholder = "Subject"
 45	m.subjectInput.Prompt = "> "
 46	m.subjectInput.CharLimit = 256
 47
 48	m.bodyInput = textarea.New()
 49	m.bodyInput.Cursor.Style = cursorStyle
 50	m.bodyInput.Placeholder = "Body..."
 51	m.bodyInput.Prompt = "> "
 52	m.bodyInput.SetHeight(10)
 53	m.bodyInput.SetWidth(60)
 54
 55	return m
 56}
 57
 58func (m Composer) Init() tea.Cmd {
 59	return textinput.Blink
 60}
 61
 62func (m Composer) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 63	var cmds []tea.Cmd
 64	var cmd tea.Cmd
 65
 66	switch msg := msg.(type) {
 67	case tea.KeyMsg:
 68		switch msg.Type {
 69		case tea.KeyCtrlC, tea.KeyEsc:
 70			return m, tea.Quit
 71
 72		// Handle Tab and Shift+Tab to cycle focus between inputs.
 73		case tea.KeyTab, tea.KeyShiftTab:
 74			if msg.Type == tea.KeyShiftTab {
 75				m.focusIndex--
 76			} else {
 77				m.focusIndex++
 78			}
 79
 80			// Wrap around
 81			if m.focusIndex > 3 { // 3 is the Send button
 82				m.focusIndex = 0
 83			} else if m.focusIndex < 0 {
 84				m.focusIndex = 3
 85			}
 86			
 87			// Blur all inputs
 88			m.toInput.Blur()
 89			m.subjectInput.Blur()
 90			m.bodyInput.Blur()
 91
 92			// Focus the correct input
 93			switch m.focusIndex {
 94			case 0:
 95				m.toInput.Focus()
 96			case 1:
 97				m.subjectInput.Focus()
 98			case 2:
 99				m.bodyInput.Focus()
100			}
101			return m, tea.Batch(cmds...)
102
103		// Handle Enter key.
104		case tea.KeyEnter:
105			// If on the Send button, send the email.
106			if m.focusIndex == 3 {
107				return m, func() tea.Msg {
108					return SendEmailMsg{
109						To:      m.toInput.Value(),
110						Subject: m.subjectInput.Value(),
111						Body:    m.bodyInput.Value(),
112					}
113				}
114			}
115		}
116	}
117
118	// Update the focused input.
119	switch m.focusIndex {
120	case 0:
121		m.toInput, cmd = m.toInput.Update(msg)
122		cmds = append(cmds, cmd)
123	case 1:
124		m.subjectInput, cmd = m.subjectInput.Update(msg)
125		cmds = append(cmds, cmd)
126	case 2:
127		m.bodyInput, cmd = m.bodyInput.Update(msg)
128		cmds = append(cmds, cmd)
129	}
130
131	return m, tea.Batch(cmds...)
132}
133
134// View renders the UI.
135func (m Composer) View() string {
136	button := &blurredButton
137	if m.focusIndex == 3 {
138		button = &focusedButton
139	}
140
141	return lipgloss.JoinVertical(lipgloss.Left,
142		"Compose New Email",
143		"From: "+emailRecipientStyle.Render(m.fromAddr),
144		m.toInput.View(),
145		m.subjectInput.View(),
146		m.bodyInput.View(),
147		*button,
148		helpStyle.Render("tab: next field • esc: quit"),
149	)
150}