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].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
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user