mirror of
https://github.com/Gu1llaum-3/sshm.git
synced 2025-09-04 09:46:32 +02:00
feat: improve search UX with persistent search bar and better navigation
- Always-visible search bar with focus indicators - Tab navigation between search and table - ASCII title header - Simplified keyboard shortcuts (/, Tab, ESC) - Fixed search validation workflow - Enhanced visual feedback and accessibility
This commit is contained in:
parent
fad2585d5e
commit
534b7d9a6c
@ -14,15 +14,28 @@ import (
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
var baseStyle = lipgloss.NewStyle().
|
||||
BorderStyle(lipgloss.NormalBorder()).
|
||||
BorderForeground(lipgloss.Color("240"))
|
||||
|
||||
var searchStyle = lipgloss.NewStyle().
|
||||
var searchStyleFocused = lipgloss.NewStyle().
|
||||
BorderStyle(lipgloss.RoundedBorder()).
|
||||
BorderForeground(lipgloss.Color("36")).
|
||||
Padding(0, 1)
|
||||
|
||||
var searchStyleUnfocused = lipgloss.NewStyle().
|
||||
BorderStyle(lipgloss.RoundedBorder()).
|
||||
BorderForeground(lipgloss.Color("240")).
|
||||
Padding(0, 1)
|
||||
|
||||
var headerStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("36")).
|
||||
Bold(true).
|
||||
Align(lipgloss.Center)
|
||||
|
||||
const asciiTitle = `
|
||||
_____ _____ _ _ _____
|
||||
| __| __| | | |
|
||||
|__ |__ | | | | |
|
||||
|_____|_____|__|__|_|_|_|
|
||||
`
|
||||
|
||||
type Model struct {
|
||||
table table.Model
|
||||
searchInput textinput.Model
|
||||
@ -47,13 +60,6 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
case tea.KeyMsg:
|
||||
switch msg.String() {
|
||||
case "esc", "ctrl+c":
|
||||
if m.searchMode {
|
||||
// Exit search mode
|
||||
m.searchMode = false
|
||||
m.searchInput.Blur()
|
||||
m.table.Focus()
|
||||
return m, nil
|
||||
}
|
||||
if m.deleteMode {
|
||||
// Exit delete mode
|
||||
m.deleteMode = false
|
||||
@ -74,9 +80,26 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
m.searchInput.Focus()
|
||||
return m, textinput.Blink
|
||||
}
|
||||
case "tab":
|
||||
if !m.deleteMode {
|
||||
// Toggle focus between search input and table
|
||||
if m.searchMode {
|
||||
// Switch from search to table
|
||||
m.searchMode = false
|
||||
m.searchInput.Blur()
|
||||
m.table.Focus()
|
||||
} else {
|
||||
// Switch from table to search
|
||||
m.searchMode = true
|
||||
m.table.Blur()
|
||||
m.searchInput.Focus()
|
||||
return m, textinput.Blink
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
case "enter":
|
||||
if m.searchMode {
|
||||
// Exit search mode and focus table
|
||||
// Validate search and return to table mode to allow commands
|
||||
m.searchMode = false
|
||||
m.searchInput.Blur()
|
||||
m.table.Focus()
|
||||
@ -153,17 +176,18 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
// Update components based on mode
|
||||
if m.searchMode {
|
||||
m.searchInput, cmd = m.searchInput.Update(msg)
|
||||
// Filter hosts when search input changes
|
||||
if m.searchInput.Value() != "" {
|
||||
m.filteredHosts = m.filterHosts(m.searchInput.Value())
|
||||
} else {
|
||||
m.filteredHosts = m.hosts
|
||||
}
|
||||
m.updateTableRows()
|
||||
} else {
|
||||
m.table, cmd = m.table.Update(msg)
|
||||
}
|
||||
|
||||
// Always filter hosts when search input changes (regardless of mode)
|
||||
if m.searchInput.Value() != "" {
|
||||
m.filteredHosts = m.filterHosts(m.searchInput.Value())
|
||||
} else {
|
||||
m.filteredHosts = m.hosts
|
||||
}
|
||||
m.updateTableRows()
|
||||
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
@ -174,20 +198,37 @@ func (m Model) View() string {
|
||||
|
||||
var view strings.Builder
|
||||
|
||||
// Add search bar
|
||||
searchPrompt := "Search (/ to search, ESC to exit search): "
|
||||
// Add ASCII title
|
||||
view.WriteString(headerStyle.Render(asciiTitle) + "\n")
|
||||
|
||||
// Add search bar (always visible) with appropriate style based on focus
|
||||
searchPrompt := "Search (/ to focus, Tab to switch): "
|
||||
if m.searchMode {
|
||||
view.WriteString(searchStyle.Render(searchPrompt+m.searchInput.View()) + "\n\n")
|
||||
view.WriteString(searchStyleFocused.Render(searchPrompt+m.searchInput.View()) + "\n\n")
|
||||
} else {
|
||||
view.WriteString(searchStyleUnfocused.Render(searchPrompt+m.searchInput.View()) + "\n\n")
|
||||
}
|
||||
|
||||
// Add table
|
||||
view.WriteString(baseStyle.Render(m.table.View()))
|
||||
// Add table with appropriate style based on focus
|
||||
if m.searchMode {
|
||||
// Table is not focused, use gray border
|
||||
tableStyle := lipgloss.NewStyle().
|
||||
BorderStyle(lipgloss.NormalBorder()).
|
||||
BorderForeground(lipgloss.Color("240"))
|
||||
view.WriteString(tableStyle.Render(m.table.View()))
|
||||
} else {
|
||||
// Table is focused, use green border
|
||||
tableStyle := lipgloss.NewStyle().
|
||||
BorderStyle(lipgloss.NormalBorder()).
|
||||
BorderForeground(lipgloss.Color("36"))
|
||||
view.WriteString(tableStyle.Render(m.table.View()))
|
||||
}
|
||||
|
||||
// Add help text
|
||||
if !m.searchMode {
|
||||
view.WriteString("\nUse ↑/↓ to navigate • Enter to connect • (a)dd • (e)dit • (d)elete • / to search • (q)uit")
|
||||
view.WriteString("\nUse ↑/↓ to navigate • Enter to connect • (a)dd • (e)dit • (d)elete • / to search • Tab to switch • q/ESC to quit")
|
||||
} else {
|
||||
view.WriteString("\nType to filter hosts by name or tag • Enter to select • ESC to exit search")
|
||||
view.WriteString("\nType to filter hosts • Enter to validate search • Tab to switch to table • ESC to quit")
|
||||
}
|
||||
|
||||
return view.String()
|
||||
@ -353,8 +394,8 @@ func RunInteractiveMode(hosts []config.SSHHost) error {
|
||||
for {
|
||||
m := NewModel(hosts)
|
||||
|
||||
// Start the application in terminal (without alt screen)
|
||||
p := tea.NewProgram(m)
|
||||
// Start the application in alt screen mode for clean exit
|
||||
p := tea.NewProgram(m, tea.WithAltScreen())
|
||||
finalModel, err := p.Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("error running TUI: %w", err)
|
||||
@ -370,9 +411,6 @@ func RunInteractiveMode(hosts []config.SSHHost) error {
|
||||
continue
|
||||
}
|
||||
|
||||
// Clear screen before returning to TUI
|
||||
fmt.Print("\033[2J\033[H")
|
||||
|
||||
// Refresh the hosts list after editing
|
||||
refreshedHosts, err := config.ParseSSHConfig()
|
||||
if err != nil {
|
||||
@ -390,9 +428,6 @@ func RunInteractiveMode(hosts []config.SSHHost) error {
|
||||
continue
|
||||
}
|
||||
|
||||
// Clear screen before returning to TUI
|
||||
fmt.Print("\033[2J\033[H")
|
||||
|
||||
// Refresh the hosts list after adding
|
||||
refreshedHosts, err := config.ParseSSHConfig()
|
||||
if err != nil {
|
||||
|
Loading…
x
Reference in New Issue
Block a user