Updated TUI field sizing

This commit is contained in:
2026-05-15 00:40:01 +00:00
parent a8f69cd7cc
commit 9105cba380
2 changed files with 52 additions and 2 deletions
+27 -2
View File
@@ -63,10 +63,27 @@ type form struct {
cursor int cursor int
busy bool busy bool
err string err string
width int // current terminal width; inputs resize to fill it
submit func(values []string) tea.Cmd submit func(values []string) tea.Cmd
} }
// defaultFieldWidth is the fallback input width used before the first
// WindowSizeMsg has arrived. Once we know the terminal size, inputs
// grow to fill the available horizontal space.
const defaultFieldWidth = 40
// fieldWidthFor derives the per-input visible width from the terminal
// width. It subtracts the modal's border+padding (6) and the form's
// label indent (2), then a couple of chars of safety margin.
func fieldWidthFor(termWidth int) int {
w := termWidth - 12
if w < defaultFieldWidth {
return defaultFieldWidth
}
return w
}
func newForm(title string, fields []formField, submit func([]string) tea.Cmd) *form { func newForm(title string, fields []formField, submit func([]string) tea.Cmd) *form {
for i := range fields { for i := range fields {
fields[i].input.Prompt = "" fields[i].input.Prompt = ""
@@ -89,7 +106,7 @@ func textField(label, hint string, required bool) formField {
// contents and can tweak instead of retyping everything. // contents and can tweak instead of retyping everything.
func textFieldWithValue(label, hint, value string, required bool) formField { func textFieldWithValue(label, hint, value string, required bool) formField {
ti := textinput.New() ti := textinput.New()
ti.Width = 40 ti.Width = defaultFieldWidth
ti.Placeholder = hint ti.Placeholder = hint
if value != "" { if value != "" {
ti.SetValue(value) ti.SetValue(value)
@@ -106,7 +123,7 @@ func passwordField(label, hint string) formField {
// the actual value leaking on-screen. // the actual value leaking on-screen.
func passwordFieldWithValue(label, hint, value string) formField { func passwordFieldWithValue(label, hint, value string) formField {
ti := textinput.New() ti := textinput.New()
ti.Width = 40 ti.Width = defaultFieldWidth
ti.Placeholder = hint ti.Placeholder = hint
ti.EchoMode = textinput.EchoPassword ti.EchoMode = textinput.EchoPassword
ti.EchoCharacter = '•' ti.EchoCharacter = '•'
@@ -148,6 +165,14 @@ func (f *form) View() string {
func (f *form) Update(msg tea.Msg) (modal, tea.Cmd) { func (f *form) Update(msg tea.Msg) (modal, tea.Cmd) {
switch msg := msg.(type) { switch msg := msg.(type) {
case tea.WindowSizeMsg:
f.width = msg.Width
w := fieldWidthFor(msg.Width)
for i := range f.fields {
f.fields[i].input.Width = w
}
return f, nil
case formSubmitErr: case formSubmitErr:
f.busy = false f.busy = false
f.err = string(msg) f.err = string(msg)
+25
View File
@@ -132,6 +132,9 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case tea.WindowSizeMsg: case tea.WindowSizeMsg:
m.width, m.height = msg.Width, msg.Height m.width, m.height = msg.Width, msg.Height
m.resizeTabs() m.resizeTabs()
if m.modal != nil {
m.modal, _ = m.modal.Update(msg)
}
return m, nil return m, nil
case tickMsg: case tickMsg:
@@ -175,8 +178,15 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// Modal grabs all input while open. // Modal grabs all input while open.
if m.modal != nil { if m.modal != nil {
prev := m.modal
newModal, cmd := m.modal.Update(msg) newModal, cmd := m.modal.Update(msg)
m.modal = newModal m.modal = newModal
// If the modal handed off to a different modal (e.g. picker →
// form), seed the new one with the current terminal size so its
// text inputs can size themselves on first paint.
if newModal != nil && newModal != prev {
m.seedModalSize()
}
return m, cmd return m, cmd
} }
@@ -223,6 +233,7 @@ func (m model) handleKey(km tea.KeyMsg) (tea.Model, tea.Cmd) {
return m, tea.Batch(loadStatusCmd(), loadConfigCmd()) return m, tea.Batch(loadStatusCmd(), loadConfigCmd())
case "a": case "a":
m.modal = m.openAddPicker() m.modal = m.openAddPicker()
m.seedModalSize()
return m, nil return m, nil
case "d": case "d":
return m.openRemoveConfirm() return m.openRemoveConfirm()
@@ -501,9 +512,20 @@ func (m model) openRemoveConfirm() (tea.Model, tea.Cmd) {
return m, nil return m, nil
} }
m.modal = newConfirm(prompt, run) m.modal = newConfirm(prompt, run)
m.seedModalSize()
return m, nil return m, nil
} }
// seedModalSize forwards the current terminal dimensions to the modal
// so its inputs can size themselves on first paint. Called whenever a
// new modal is installed.
func (m *model) seedModalSize() {
if m.modal == nil || m.width == 0 {
return
}
m.modal, _ = m.modal.Update(tea.WindowSizeMsg{Width: m.width, Height: m.height})
}
// openEditForm dispatches to the right pre-filled edit form based on the // openEditForm dispatches to the right pre-filled edit form based on the
// active tab and the row under the cursor. Looks up the full record in // active tab and the row under the cursor. Looks up the full record in
// m.peersFull / m.checksFull / m.alerts (populated by loadConfigCmd) so // m.peersFull / m.checksFull / m.alerts (populated by loadConfigCmd) so
@@ -519,6 +541,7 @@ func (m model) openEditForm() (tea.Model, tea.Cmd) {
for i := range m.peersFull { for i := range m.peersFull {
if m.peersFull[i].NodeID == id { if m.peersFull[i].NodeID == id {
m.modal = newEditNodeForm(m.peersFull[i]) m.modal = newEditNodeForm(m.peersFull[i])
m.seedModalSize()
return m, nil return m, nil
} }
} }
@@ -534,6 +557,7 @@ func (m model) openEditForm() (tea.Model, tea.Cmd) {
for i := range m.checksFull { for i := range m.checksFull {
if m.checksFull[i].ID == id { if m.checksFull[i].ID == id {
m.modal = newEditCheckForm(m.checksFull[i]) m.modal = newEditCheckForm(m.checksFull[i])
m.seedModalSize()
return m, nil return m, nil
} }
} }
@@ -559,6 +583,7 @@ func (m model) openEditForm() (tea.Model, tea.Cmd) {
m.setFlash("unsupported alert type", flashError) m.setFlash("unsupported alert type", flashError)
return m, nil return m, nil
} }
m.seedModalSize()
return m, nil return m, nil
} }
m.setFlash("alert not found in local cluster.yaml", flashError) m.setFlash("alert not found in local cluster.yaml", flashError)