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:
+32
-19
@@ -153,11 +153,11 @@ func (t *KeyListTab) View() string {
|
||||
text string
|
||||
plain string
|
||||
}
|
||||
|
||||
titlePlain := "SSH Key Pairs"
|
||||
|
||||
// Build content rows
|
||||
var rows []styledRow
|
||||
|
||||
title := "SSH Key Pairs"
|
||||
titlePlain := title
|
||||
|
||||
if t.err != nil {
|
||||
errLine := fmt.Sprintf("Error: %v", t.err)
|
||||
rows = append(rows, styledRow{text: ErrorStyle.Render(errLine), plain: errLine})
|
||||
@@ -190,36 +190,49 @@ func (t *KeyListTab) View() string {
|
||||
}
|
||||
}
|
||||
|
||||
footerPlain := "Ctrl+Tab:switch Ctrl+Q:close ↑↓:nav Ctrl+N:add Ctrl+E:edit D:delete Esc:back"
|
||||
footer := styledRow{text: SubtitleStyle.Render(footerPlain), plain: footerPlain}
|
||||
|
||||
widestRow := lipgloss.Width(titlePlain)
|
||||
// Responsive width
|
||||
sidePad := adaptiveSidePad(t.width)
|
||||
widestContent := lipgloss.Width(titlePlain)
|
||||
for _, r := range rows {
|
||||
if w := lipgloss.Width(r.plain); w > widestRow {
|
||||
widestRow = w
|
||||
if w := lipgloss.Width(r.plain); w > widestContent {
|
||||
widestContent = w
|
||||
}
|
||||
}
|
||||
if w := lipgloss.Width(footer.plain); w > widestRow {
|
||||
widestRow = w
|
||||
targetW := clampWidth(widestContent+sidePad*2, t.width)
|
||||
innerW := targetW - sidePad*2
|
||||
if innerW < 1 {
|
||||
innerW = 1
|
||||
}
|
||||
|
||||
const sidePad = 6
|
||||
targetW := widestRow + sidePad*2
|
||||
// Footer (wrapped)
|
||||
footerText := "Ctrl+Tab:switch Ctrl+Q:close ↑↓:nav Ctrl+N:add Ctrl+E:edit D:delete Esc:back"
|
||||
footerWrapped := wrapFooter(footerText, innerW)
|
||||
|
||||
// Render
|
||||
var inner strings.Builder
|
||||
|
||||
titleStyled := lipgloss.NewStyle().Bold(true).Foreground(gbYellow).Render(titlePlain)
|
||||
inner.WriteString(lipgloss.PlaceHorizontal(targetW, lipgloss.Center, titleStyled))
|
||||
inner.WriteString("\n\n")
|
||||
|
||||
// Render rows, excluding the footer (last element)
|
||||
for _, r := range rows[:len(rows)-1] {
|
||||
line := lipgloss.PlaceHorizontal(targetW, lipgloss.Center, r.text)
|
||||
for _, r := range rows {
|
||||
plain := r.plain
|
||||
if lipgloss.Width(plain) > innerW {
|
||||
plain = truncateStr(plain, innerW)
|
||||
}
|
||||
styled := r.text
|
||||
if lipgloss.Width(r.plain) > innerW {
|
||||
styled = truncateStr(r.text, innerW)
|
||||
}
|
||||
line := lipgloss.PlaceHorizontal(targetW, lipgloss.Center, styled)
|
||||
inner.WriteString(line)
|
||||
inner.WriteString("\n")
|
||||
}
|
||||
inner.WriteString("\n")
|
||||
inner.WriteString(lipgloss.PlaceHorizontal(targetW, lipgloss.Center, footer.text))
|
||||
|
||||
for _, line := range strings.Split(footerWrapped, "\n") {
|
||||
inner.WriteString(lipgloss.PlaceHorizontal(targetW, lipgloss.Center, SubtitleStyle.Render(line)))
|
||||
inner.WriteString("\n")
|
||||
}
|
||||
|
||||
box := BorderStyle.Render(inner.String())
|
||||
var b strings.Builder
|
||||
|
||||
Reference in New Issue
Block a user