feat: responsive TUI layout — mobile/tablet/desktop support
- NEW responsive.go: wrapFooter, adaptiveSidePad, clampWidth, truncateStr - Footer auto-wraps to multi-line on narrow terminals (full labels preserved) - Box width clamped to terminal width across all tabs - Host/key/snippet rows truncated with ellipsis; compact format <35 cols - SFTP panes stack vertically when terminal < 50 cols - Tab bar truncates names on overflow - Form contentW minimum lowered 50->30 for mobile - Fixed: key_list_tab & snippet_list_tab dropped last data row (off-by-one bug)
This commit is contained in:
@@ -540,21 +540,33 @@ func (t *SFTPBrowserTab) View() string {
|
||||
SubtitleStyle.Render(fmt.Sprintf("Connecting SFTP to %s...", t.host.Name)))
|
||||
}
|
||||
|
||||
halfW := t.width / 2
|
||||
if halfW < 20 {
|
||||
halfW = 20
|
||||
// Responsive pane layout: stack vertically on narrow terminals
|
||||
var leftView, rightView string
|
||||
if t.width < 50 {
|
||||
// Stack vertically — each pane gets full width
|
||||
leftView = t.renderPane(&t.left, paneLocal, t.width)
|
||||
rightView = t.renderPane(&t.right, paneRemote, t.width)
|
||||
} else {
|
||||
// Side by side — each pane gets half width
|
||||
halfW := t.width / 2
|
||||
if halfW < 20 {
|
||||
halfW = 20
|
||||
}
|
||||
leftView = t.renderPane(&t.left, paneLocal, halfW)
|
||||
rightView = t.renderPane(&t.right, paneRemote, halfW)
|
||||
}
|
||||
|
||||
leftView := t.renderPane(&t.left, paneLocal, halfW)
|
||||
rightView := t.renderPane(&t.right, paneRemote, halfW)
|
||||
|
||||
var b strings.Builder
|
||||
|
||||
b.WriteString(lipgloss.PlaceHorizontal(t.width, lipgloss.Center,
|
||||
AppTitleStyle.Render(fmt.Sprintf("SFTP: %s@%s", t.host.Username, t.host.Hostname))))
|
||||
b.WriteString("\n")
|
||||
|
||||
b.WriteString(lipgloss.JoinHorizontal(lipgloss.Top, leftView, rightView))
|
||||
if t.width < 50 {
|
||||
b.WriteString(lipgloss.JoinVertical(lipgloss.Left, leftView, rightView))
|
||||
} else {
|
||||
b.WriteString(lipgloss.JoinHorizontal(lipgloss.Top, leftView, rightView))
|
||||
}
|
||||
|
||||
if t.err != nil {
|
||||
b.WriteString("\n" + lipgloss.PlaceHorizontal(t.width, lipgloss.Center,
|
||||
@@ -566,8 +578,12 @@ func (t *SFTPBrowserTab) View() string {
|
||||
InfoStyle.Render(t.transferMsg)))
|
||||
}
|
||||
|
||||
b.WriteString("\n" + lipgloss.PlaceHorizontal(t.width, lipgloss.Center,
|
||||
SubtitleStyle.Render("Ctrl+Tab:switch Ctrl+Q:close Tab:pane ↑↓:nav Enter/→:open ←/Backspace:up /:filter C:copy D:delete N:mkdir R:refresh Esc:close")))
|
||||
// Footer (wrapped to terminal width)
|
||||
footerText := "Ctrl+Tab:switch Ctrl+Q:close Tab:pane ↑↓:nav Enter/→:open ←/Backspace:up /:filter C:copy D:delete N:mkdir R:refresh Esc:close"
|
||||
footerWrapped := wrapFooter(footerText, t.width)
|
||||
for _, line := range strings.Split(footerWrapped, "\n") {
|
||||
b.WriteString("\n" + lipgloss.PlaceHorizontal(t.width, lipgloss.Center, SubtitleStyle.Render(line)))
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user