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}