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