refactor: round borders, Y/N delete confirm, fix selected row styling

This commit is contained in:
2026-05-27 01:40:59 +08:00
parent c9f218f2e3
commit d3bd4af634
10 changed files with 72 additions and 36 deletions
+1 -1
View File
@@ -25,7 +25,7 @@ pub fn draw_dialog(
Block::default()
.title(title.to_string())
.borders(Borders::ALL)
.border_type(BorderType::Plain)
.border_type(BorderType::Rounded)
.border_style(Style::default().fg(border_color))
.bg(PANEL),
)
+1 -1
View File
@@ -21,7 +21,7 @@ pub fn draw_input(
Block::default()
.title(title.to_string())
.borders(Borders::ALL)
.border_type(BorderType::Plain)
.border_type(BorderType::Rounded)
.border_style(if focused {
Style::default().fg(ACCENT)
} else {
+2 -2
View File
@@ -10,7 +10,7 @@ pub fn panel(title: impl Into<String>) -> Block<'static> {
Block::default()
.title(Line::from(format!(" {} ", title.into())).fg(TEXT).bold())
.borders(Borders::ALL)
.border_type(BorderType::Plain)
.border_type(BorderType::Rounded)
.border_style(Style::default().fg(DIM_BORDER))
.bg(PANEL)
}
@@ -19,7 +19,7 @@ pub fn panel_accent(title: impl Into<String>) -> Block<'static> {
Block::default()
.title(Line::from(format!(" {} ", title.into())).fg(ACCENT).bold())
.borders(Borders::ALL)
.border_type(BorderType::Plain)
.border_type(BorderType::Rounded)
.border_style(Style::default().fg(ACCENT))
.bg(PANEL)
}
+2 -1
View File
@@ -1,7 +1,7 @@
use crate::ui::{GREEN, PANEL_ALT, RED};
use ratatui::{
style::{Style, Stylize},
widgets::{Block, Borders, Clear, Paragraph, Widget},
widgets::{Block, BorderType, Borders, Clear, Paragraph, Widget},
};
pub fn draw_toast(frame: &mut ratatui::Frame<'_>, message: &str, success: bool) {
@@ -19,6 +19,7 @@ pub fn draw_toast(frame: &mut ratatui::Frame<'_>, message: &str, success: bool)
.block(
Block::default()
.borders(Borders::ALL)
.border_type(BorderType::Rounded)
.border_style(Style::default().fg(border_color))
.bg(PANEL_ALT),
)
+1 -1
View File
@@ -12,6 +12,6 @@ pub fn draw_delete_confirm(frame: &mut ratatui::Frame<'_>, app: &App) {
7,
RED,
" Confirm Delete ",
&format!("Delete connection '{name}'?\n\n Enter confirm · Esc cancel"),
&format!("Delete connection '{name}'?\n\n [Y] Yes · [N] No"),
);
}
+2 -2
View File
@@ -54,8 +54,8 @@ fn form_hints() -> Vec<Hint> {
fn delete_hints() -> Vec<Hint> {
vec![
Hint { key: "Enter", desc: "confirm" },
Hint { key: "Esc", desc: "cancel" },
Hint { key: "Y", desc: "yes" },
Hint { key: "N", desc: "no" },
]
}
+22 -11
View File
@@ -1,6 +1,5 @@
use crate::app::{App, Mode};
use crate::ui::component::common::layout::centered_rect;
use crate::ui::{ACCENT, PANEL, SELECTED_BG, TEXT};
use crate::ui::{ACCENT, MUTED, PANEL, SELECTED_BG, TEXT};
use super::View;
@@ -10,7 +9,8 @@ use ratatui::{
Frame,
layout::Rect,
style::{Modifier, Style, Stylize},
widgets::{Block, Borders, Clear, ListItem, ListState},
text::{Line, Span},
widgets::{Block, BorderType, Borders, Clear, ListState},
};
const ACTIONS: &[(&str, &str)] = &[
@@ -24,17 +24,27 @@ const ACTIONS: &[(&str, &str)] = &[
pub struct ActionMenuView;
impl View for ActionMenuView {
fn draw(&self, frame: &mut Frame<'_>, _app: &App, _area: Rect) {
let width = 44u16;
let height = (ACTIONS.len() as u16) + 4;
let area = centered_rect(width, height, frame.area());
fn draw(&self, frame: &mut Frame<'_>, _app: &App, area: Rect) {
let list_width = 52u16.min(area.width.saturating_sub(4));
let list_height = (ACTIONS.len() as u16 + 4).min(area.height);
let x = area.x + (area.width.saturating_sub(list_width)) / 2;
let y = area.y + (area.height.saturating_sub(list_height)) / 2;
let list_area = Rect {
x,
y,
width: list_width,
height: list_height,
};
frame.render_widget(Clear, area);
frame.render_widget(Clear, list_area);
let items: Vec<ListItem<'_>> = ACTIONS
let items: Vec<Line<'_>> = ACTIONS
.iter()
.map(|(label, desc)| {
ListItem::new(format!(" {label:<14}{desc}"))
Line::from(vec![
Span::styled(format!(" {label:<14}"), Style::default().fg(TEXT)),
Span::styled(desc.to_string(), Style::default().fg(MUTED)),
])
})
.collect();
@@ -44,6 +54,7 @@ impl View for ActionMenuView {
.title(" Actions ")
.title_style(Style::default().fg(ACCENT).bold())
.borders(Borders::ALL)
.border_type(BorderType::Rounded)
.border_style(Style::default().fg(ACCENT))
.bg(PANEL),
)
@@ -58,7 +69,7 @@ impl View for ActionMenuView {
let mut state = ListState::default();
state.select(Some(_app.session.action_menu.cursor));
frame.render_stateful_widget(list, area, &mut state);
frame.render_stateful_widget(list, list_area, &mut state);
}
fn handle_key(&self, app: &mut App, key: KeyEvent) -> Result<()> {
+6 -2
View File
@@ -74,7 +74,11 @@ fn draw_credentials(frame: &mut Frame<'_>, app: &App, area: Rect) {
Row::new([
Cell::from(format!("{} {}", if selected { ">" } else { " " }, (*name)))
.style(style),
Cell::from(format!("{kind_text}")).style(Style::default().fg(dot_color)),
Cell::from(format!("{kind_text}")).style(if selected {
Style::default().fg(dot_color).bg(SELECTED_BG)
} else {
Style::default().fg(dot_color)
}),
Cell::from(ref_text).style(style),
])
})
@@ -95,7 +99,7 @@ fn draw_credentials(frame: &mut Frame<'_>, app: &App, area: Rect) {
.style(Style::default().fg(BLUE).add_modifier(Modifier::BOLD)),
)
.block(panel("Credentials"))
.column_spacing(2)
.column_spacing(0)
.row_highlight_style(Style::default().bg(SELECTED_BG));
frame.render_widget(table, area);
}
+21 -12
View File
@@ -181,7 +181,7 @@ pub fn draw_connection_list(frame: &mut Frame<'_>, app: &App, area: Rect) {
.style(Style::default().fg(BLUE).add_modifier(Modifier::BOLD)),
)
.block(panel(title))
.column_spacing(2)
.column_spacing(0)
.row_highlight_style(Style::default().bg(SELECTED_BG));
frame.render_widget(table, area);
}
@@ -220,7 +220,7 @@ fn connection_row(
Style::default().fg(TEXT)
};
let is_shell = matches!(profile.kind, ConnectionType::Shell { .. });
let type_badge = if is_shell { "[SHL]" } else { "[SSH]" };
let type_badge = if is_shell { "SHL" } else { "SSH" };
let badge_color = if is_shell { PURPLE } else { ACCENT };
let target = match &profile.kind {
@@ -259,16 +259,23 @@ fn connection_row(
_ => MUTED,
};
let badge_style = if selected {
Style::default().fg(badge_color).bg(SELECTED_BG).add_modifier(Modifier::BOLD)
} else {
Style::default().fg(crate::ui::BG).bg(badge_color).add_modifier(Modifier::BOLD)
};
Row::new([
Cell::from(format!("{marker} {}", display_name(name))).style(row_style),
Cell::from(type_badge).style(
Style::default()
.fg(crate::ui::BG)
.bg(badge_color)
.add_modifier(Modifier::BOLD),
),
Cell::from(Line::from(vec![
Span::styled(format!(" {} ", type_badge), badge_style),
])).style(row_style),
Cell::from(target).style(row_style),
Cell::from(auth_state).style(Style::default().fg(auth_color)),
Cell::from(auth_state).style(if selected {
Style::default().fg(auth_color).bg(SELECTED_BG)
} else {
Style::default().fg(auth_color)
}),
])
.height(1)
}
@@ -295,7 +302,7 @@ pub fn draw_detail_panel(frame: &mut Frame<'_>, app: &App, area: Rect) {
let mut lines: Vec<Line> = Vec::new();
let is_shell = matches!(profile.kind, ConnectionType::Shell { .. });
let badge = if is_shell { "[SHL]" } else { "[SSH]" };
let badge = if is_shell { "SHL" } else { "SSH" };
let badge_color = if is_shell { PURPLE } else { ACCENT };
lines.push(Line::raw(""));
lines.push(Line::from(vec![
@@ -553,14 +560,16 @@ impl View for DeleteConfirmView {
fn handle_key(&self, app: &mut App, key: KeyEvent) -> Result<()> {
match key.code {
KeyCode::Esc => app.session.mode = Mode::Home,
KeyCode::Enter => {
KeyCode::Char('y') | KeyCode::Char('Y') => {
match app.delete_selected() {
Ok(()) => app.toast("deleted", true),
Err(err) => app.toast(err.to_string(), false),
}
app.session.mode = Mode::Home;
}
KeyCode::Esc | KeyCode::Char('n') | KeyCode::Char('N') => {
app.session.mode = Mode::Home;
}
_ => {}
}
Ok(())
+14 -3
View File
@@ -69,8 +69,13 @@ fn draw_import(frame: &mut Frame<'_>, app: &App, area: Rect) {
.as_ref()
.map(|c| format!("conflict: {}", c.name))
.unwrap_or_else(|| "-".to_string());
let check_cell_style = if selected_row {
Style::default().bg(SELECTED_BG)
} else {
Style::default()
};
rows.push(Row::new([
Cell::from(if checked { " [x]" } else { " [ ]" }).style(check_style),
Cell::from(if checked { " [x]" } else { " [ ]" }).style(check_style.patch(check_cell_style)),
Cell::from(item.name.clone()).style(style),
Cell::from(item.path.display().to_string()).style(style),
Cell::from(status).style(style),
@@ -98,8 +103,13 @@ fn draw_import(frame: &mut Frame<'_>, app: &App, area: Rect) {
} else {
Style::default().fg(MUTED)
};
let check_cell_style = if selected_row {
Style::default().bg(SELECTED_BG)
} else {
Style::default()
};
rows.push(Row::new([
Cell::from(if checked { " [x]" } else { " [ ]" }).style(check_style),
Cell::from(if checked { " [x]" } else { " [ ]" }).style(check_style.patch(check_cell_style)),
Cell::from(item.name.clone()).style(style),
Cell::from(format!("{}@{}:{}", item.user, item.host, item.port)).style(style),
Cell::from(
@@ -146,7 +156,8 @@ fn draw_import(frame: &mut Frame<'_>, app: &App, area: Rect) {
"Import",
"Space toggle a all A none Enter import Esc cancel",
))
.column_spacing(2);
.column_spacing(0)
.row_highlight_style(Style::default().bg(SELECTED_BG));
frame.render_widget(table, area);
}