feat: centered footer + auth type toggle pills

- Status bar footer centered within terminal width using lipgloss.Width
- Auth Type field replaced with toggle pills (password/key)
  Space/Enter/←/→ cycle between options on the focused field
  Selected type shown with SelectedStyle/TagStyle (green),
  inactive type shown in muted SubtitleStyle
  Help hint 'Space/←/→ to toggle' shown when focused
- Added toggleAuthType() and cycleAuthType() helpers
This commit is contained in:
swanadiva
2026-06-23 15:59:54 +07:00
parent 5b456104e8
commit 2442920616
2 changed files with 68 additions and 9 deletions
+56 -7
View File
@@ -83,7 +83,6 @@ func newHostFormTab(mode formMode, host *models.Host, dataDir string) *HostFormT
inputs[fieldPort].Placeholder = "22" inputs[fieldPort].Placeholder = "22"
inputs[fieldPort].SetValue("22") inputs[fieldPort].SetValue("22")
inputs[fieldUsername].Placeholder = "root" inputs[fieldUsername].Placeholder = "root"
inputs[fieldAuthType].Placeholder = "password / key / both"
inputs[fieldAuthType].SetValue("password") inputs[fieldAuthType].SetValue("password")
inputs[fieldPassword].EchoMode = textinput.EchoPassword inputs[fieldPassword].EchoMode = textinput.EchoPassword
inputs[fieldPassword].Placeholder = "Enter password" inputs[fieldPassword].Placeholder = "Enter password"
@@ -143,11 +142,22 @@ func (t *HostFormTab) Update(msg tea.Msg) (Tab, tea.Cmd) {
return t, func() tea.Msg { return closeFormMsg{} } return t, func() tea.Msg { return closeFormMsg{} }
case "enter": case "enter":
if t.focus == fieldAuthType {
t.toggleAuthType()
return t, nil
}
if t.focus == fieldCount-1 { if t.focus == fieldCount-1 {
return t.submit() return t.submit()
} }
t.nextField() t.nextField()
case " ", "left", "right":
if t.focus == fieldAuthType {
t.toggleAuthType()
return t, nil
}
fallthrough
case "tab", "down": case "tab", "down":
t.nextField() t.nextField()
@@ -158,6 +168,10 @@ func (t *HostFormTab) Update(msg tea.Msg) (Tab, tea.Cmd) {
return t.submit() return t.submit()
default: default:
if t.focus == fieldAuthType {
// ignore typing on auth type field
return t, nil
}
var cmd tea.Cmd var cmd tea.Cmd
t.inputs[t.focus], cmd = t.inputs[t.focus].Update(msg) t.inputs[t.focus], cmd = t.inputs[t.focus].Update(msg)
return t, cmd return t, cmd
@@ -189,6 +203,22 @@ func (t *HostFormTab) prevField() {
t.inputs[t.focus].Prompt = "> " t.inputs[t.focus].Prompt = "> "
} }
func cycleAuthType(current string) string {
switch current {
case "password":
return "key"
case "key":
return "password"
default:
return "password"
}
}
func (t *HostFormTab) toggleAuthType() {
current := t.inputs[fieldAuthType].Value()
t.inputs[fieldAuthType].SetValue(cycleAuthType(current))
}
func (t *HostFormTab) submit() (Tab, tea.Cmd) { func (t *HostFormTab) submit() (Tab, tea.Cmd) {
name := t.inputs[fieldName].Value() name := t.inputs[fieldName].Value()
hostname := t.inputs[fieldHostname].Value() hostname := t.inputs[fieldHostname].Value()
@@ -278,16 +308,35 @@ func (t *HostFormTab) View() string {
b.WriteString(style.Render(label + ":")) b.WriteString(style.Render(label + ":"))
b.WriteString("\n") b.WriteString("\n")
renderedInput := input.View() if i == fieldAuthType {
// Render auth type as toggle pills
// Add parentheses showing auth options current := input.Value()
if i == fieldAuthType && i != t.focus { pills := []string{"password", "key"}
var parts []string
for _, p := range pills {
if p == current {
if i == t.focus {
parts = append(parts, SelectedStyle.Render(" "+p+" "))
} else {
parts = append(parts, TagStyle.Render(" "+p+" "))
}
} else {
parts = append(parts, SubtitleStyle.Render(" "+p+" "))
}
}
b.WriteString(" ") b.WriteString(" ")
b.WriteString(strings.Join(parts, " "))
b.WriteString("\n")
if i == t.focus {
b.WriteString(InfoStyle.Render(" Space/←/→ to toggle"))
}
b.WriteString("\n")
} else { } else {
renderedInput := input.View()
b.WriteString(" ") b.WriteString(" ")
b.WriteString(renderedInput)
b.WriteString("\n\n")
} }
b.WriteString(renderedInput)
b.WriteString("\n\n")
} }
// Help text // Help text
+12 -2
View File
@@ -4,6 +4,7 @@ import (
"strings" "strings"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
) )
// Tab represents a single tab in the TUI // Tab represents a single tab in the TUI
@@ -194,8 +195,17 @@ func renderTabBar(tm *TabManager) string {
return TabBarStyle.Render(bar) return TabBarStyle.Render(bar)
} }
// renderStatusBar renders the bottom status bar // renderStatusBar renders the bottom status bar, centered
func renderStatusBar(tm *TabManager) string { func renderStatusBar(tm *TabManager) string {
hints := "Ctrl+Tab:switch Ctrl+Q:close ↑↓:nav Enter:SSH Ctrl+N:add Ctrl+F:SFTP Ctrl+K:keys Ctrl+P:snippets q:quit" hints := "Ctrl+Tab:switch Ctrl+Q:close ↑↓:nav Enter:SSH Ctrl+N:add Ctrl+F:SFTP Ctrl+K:keys Ctrl+P:snippets q:quit"
return StatusBarStyle.Render(hints) rendered := StatusBarStyle.Render(hints)
// Center within the terminal width
if tm.width > 0 {
w := lipgloss.Width(rendered)
if w < tm.width {
pad := (tm.width - w) / 2
rendered = strings.Repeat(" ", pad) + rendered
}
}
return rendered
} }