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:
@@ -83,7 +83,6 @@ func newHostFormTab(mode formMode, host *models.Host, dataDir string) *HostFormT
|
||||
inputs[fieldPort].Placeholder = "22"
|
||||
inputs[fieldPort].SetValue("22")
|
||||
inputs[fieldUsername].Placeholder = "root"
|
||||
inputs[fieldAuthType].Placeholder = "password / key / both"
|
||||
inputs[fieldAuthType].SetValue("password")
|
||||
inputs[fieldPassword].EchoMode = textinput.EchoPassword
|
||||
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{} }
|
||||
|
||||
case "enter":
|
||||
if t.focus == fieldAuthType {
|
||||
t.toggleAuthType()
|
||||
return t, nil
|
||||
}
|
||||
if t.focus == fieldCount-1 {
|
||||
return t.submit()
|
||||
}
|
||||
t.nextField()
|
||||
|
||||
case " ", "left", "right":
|
||||
if t.focus == fieldAuthType {
|
||||
t.toggleAuthType()
|
||||
return t, nil
|
||||
}
|
||||
fallthrough
|
||||
|
||||
case "tab", "down":
|
||||
t.nextField()
|
||||
|
||||
@@ -158,6 +168,10 @@ func (t *HostFormTab) Update(msg tea.Msg) (Tab, tea.Cmd) {
|
||||
return t.submit()
|
||||
|
||||
default:
|
||||
if t.focus == fieldAuthType {
|
||||
// ignore typing on auth type field
|
||||
return t, nil
|
||||
}
|
||||
var cmd tea.Cmd
|
||||
t.inputs[t.focus], cmd = t.inputs[t.focus].Update(msg)
|
||||
return t, cmd
|
||||
@@ -189,6 +203,22 @@ func (t *HostFormTab) prevField() {
|
||||
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) {
|
||||
name := t.inputs[fieldName].Value()
|
||||
hostname := t.inputs[fieldHostname].Value()
|
||||
@@ -278,17 +308,36 @@ func (t *HostFormTab) View() string {
|
||||
b.WriteString(style.Render(label + ":"))
|
||||
b.WriteString("\n")
|
||||
|
||||
renderedInput := input.View()
|
||||
|
||||
// Add parentheses showing auth options
|
||||
if i == fieldAuthType && i != t.focus {
|
||||
b.WriteString(" ")
|
||||
if i == fieldAuthType {
|
||||
// Render auth type as toggle pills
|
||||
current := input.Value()
|
||||
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 {
|
||||
b.WriteString(" ")
|
||||
parts = append(parts, TagStyle.Render(" "+p+" "))
|
||||
}
|
||||
} else {
|
||||
parts = append(parts, SubtitleStyle.Render(" "+p+" "))
|
||||
}
|
||||
}
|
||||
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 {
|
||||
renderedInput := input.View()
|
||||
b.WriteString(" ")
|
||||
b.WriteString(renderedInput)
|
||||
b.WriteString("\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
// Help text
|
||||
b.WriteString(SubtitleStyle.Render(
|
||||
|
||||
+12
-2
@@ -4,6 +4,7 @@ import (
|
||||
"strings"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
// Tab represents a single tab in the TUI
|
||||
@@ -194,8 +195,17 @@ func renderTabBar(tm *TabManager) string {
|
||||
return TabBarStyle.Render(bar)
|
||||
}
|
||||
|
||||
// renderStatusBar renders the bottom status bar
|
||||
// renderStatusBar renders the bottom status bar, centered
|
||||
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"
|
||||
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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user