From 11efe82a1fa35f4c6b66a88e8c0c9fc5f5cb67a6 Mon Sep 17 00:00:00 2001 From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Date: Tue, 31 Mar 2026 12:42:24 -0300 Subject: [PATCH] sidebar: Add some design adjustments (#52832) - Adjust thread item and gradient fade colors for themes that define transparent colors for the tokens we use on them - Make the entire project header clickable area activate the workspace instead of collapsing the group. The chevron is now an icon button that does that, which makes it consistent with the collab panel and settings UI. Release Notes: - N/A --- assets/icons/focus.svg | 7 -- crates/icons/src/icons.rs | 1 - crates/sidebar/src/sidebar.rs | 94 +++++++++++----------- crates/ui/src/components/ai/thread_item.rs | 15 ++-- crates/ui/src/components/gradient_fade.rs | 14 ++-- 5 files changed, 64 insertions(+), 67 deletions(-) delete mode 100644 assets/icons/focus.svg diff --git a/assets/icons/focus.svg b/assets/icons/focus.svg deleted file mode 100644 index 9003e437cee1afa43e87fa273c9510284bb5ae0b..0000000000000000000000000000000000000000 --- a/assets/icons/focus.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/crates/icons/src/icons.rs b/crates/icons/src/icons.rs index 89932125c1bfbc05202038c1abac2a6380e19e93..acec738030ab2d36c2aebaaf45cf127e41bf385f 100644 --- a/crates/icons/src/icons.rs +++ b/crates/icons/src/icons.rs @@ -131,7 +131,6 @@ pub enum IconName { FileTree, Filter, Flame, - Focus, Folder, FolderOpen, FolderPlus, diff --git a/crates/sidebar/src/sidebar.rs b/crates/sidebar/src/sidebar.rs index a0f6e88c2b3a6238c89d1d57aa684923d81bc473..31c796b200cc84d062cad6f404dd7ec4736453a4 100644 --- a/crates/sidebar/src/sidebar.rs +++ b/crates/sidebar/src/sidebar.rs @@ -1281,13 +1281,14 @@ impl Sidebar { ) -> AnyElement { let id_prefix = if is_sticky { "sticky-" } else { "" }; let id = SharedString::from(format!("{id_prefix}project-header-{ix}")); + let disclosure_id = SharedString::from(format!("disclosure-{ix}")); let group_name = SharedString::from(format!("{id_prefix}header-group-{ix}")); let is_collapsed = self.collapsed_groups.contains(path_list); - let disclosure_icon = if is_collapsed { - IconName::ChevronRight + let (disclosure_icon, disclosure_tooltip) = if is_collapsed { + (IconName::ChevronRight, "Expand Project") } else { - IconName::ChevronDown + (IconName::ChevronDown, "Collapse Project") }; let has_new_thread_entry = self @@ -1325,8 +1326,8 @@ impl Sidebar { .group(&group_name) .h(Tab::content_height(cx)) .w_full() - .pl_1p5() - .pr_1() + .pl(px(5.)) + .pr_1p5() .border_1() .map(|this| { if is_selected { @@ -1339,16 +1340,21 @@ impl Sidebar { .hover(|s| s.bg(hover_color)) .child( h_flex() + .when(!is_active, |this| this.cursor_pointer()) .relative() .min_w_0() .w_full() - .gap_1p5() + .gap(px(5.)) .child( - h_flex().size_4().flex_none().justify_center().child( - Icon::new(disclosure_icon) - .size(IconSize::Small) - .color(Color::Custom(cx.theme().colors().icon_muted.opacity(0.5))), - ), + IconButton::new(disclosure_id, disclosure_icon) + .shape(ui::IconButtonShape::Square) + .icon_size(IconSize::Small) + .icon_color(Color::Custom(cx.theme().colors().icon_muted.opacity(0.5))) + .tooltip(Tooltip::text(disclosure_tooltip)) + .on_click(cx.listener(move |this, _, window, cx| { + this.selection = None; + this.toggle_collapse(&path_list_for_toggle, window, cx); + })), ) .child(label) .when_some( @@ -1425,39 +1431,6 @@ impl Sidebar { })), ) }) - .when(!is_active, |this| { - this.child( - IconButton::new( - SharedString::from(format!( - "{id_prefix}project-header-open-workspace-{ix}", - )), - IconName::Focus, - ) - .icon_size(IconSize::Small) - .icon_color(Color::Muted) - .tooltip(Tooltip::text("Activate Workspace")) - .on_click(cx.listener({ - move |this, _, window, cx| { - this.active_entry = - Some(ActiveEntry::Draft(workspace_for_open.clone())); - if let Some(multi_workspace) = this.multi_workspace.upgrade() { - multi_workspace.update(cx, |multi_workspace, cx| { - multi_workspace.activate( - workspace_for_open.clone(), - window, - cx, - ); - }); - } - if AgentPanel::is_visible(&workspace_for_open, cx) { - workspace_for_open.update(cx, |workspace, cx| { - workspace.focus_panel::(window, cx); - }); - } - } - })), - ) - }) .when(show_new_thread_button, |this| { this.child( IconButton::new( @@ -1483,10 +1456,29 @@ impl Sidebar { ) }) }) - .on_click(cx.listener(move |this, _, window, cx| { - this.selection = None; - this.toggle_collapse(&path_list_for_toggle, window, cx); - })) + .when(!is_active, |this| { + this.tooltip(Tooltip::text("Activate Workspace")) + .on_click(cx.listener({ + move |this, _, window, cx| { + this.active_entry = + Some(ActiveEntry::Draft(workspace_for_open.clone())); + if let Some(multi_workspace) = this.multi_workspace.upgrade() { + multi_workspace.update(cx, |multi_workspace, cx| { + multi_workspace.activate( + workspace_for_open.clone(), + window, + cx, + ); + }); + } + if AgentPanel::is_visible(&workspace_for_open, cx) { + workspace_for_open.update(cx, |workspace, cx| { + workspace.focus_panel::(window, cx); + }); + } + } + })) + }) .into_any_element() } @@ -2779,6 +2771,11 @@ impl Sidebar { let id = SharedString::from(format!("thread-entry-{}", ix)); + let color = cx.theme().colors(); + let sidebar_bg = color + .title_bar_background + .blend(color.panel_background.opacity(0.32)); + let timestamp = format_history_entry_timestamp( self.thread_last_message_sent_or_queued .get(&thread.metadata.session_id) @@ -2788,6 +2785,7 @@ impl Sidebar { ); ThreadItem::new(id, title) + .base_bg(sidebar_bg) .icon(thread.icon) .status(thread.status) .when_some(thread.icon_from_external_svg.clone(), |this, svg| { diff --git a/crates/ui/src/components/ai/thread_item.rs b/crates/ui/src/components/ai/thread_item.rs index e03dce5fe2e5ce9e52ca01da72e69605abfff765..d6b5f56e0abb33521ae69acc0b61b36b015cf987 100644 --- a/crates/ui/src/components/ai/thread_item.rs +++ b/crates/ui/src/components/ai/thread_item.rs @@ -218,21 +218,23 @@ impl RenderOnce for ThreadItem { let color = cx.theme().colors(); let sidebar_base_bg = color .title_bar_background - .blend(color.panel_background.opacity(0.2)); + .blend(color.panel_background.opacity(0.32)); - let base_bg = self.base_bg.unwrap_or(sidebar_base_bg); + let raw_bg = self.base_bg.unwrap_or(sidebar_base_bg); + let apparent_bg = color.background.blend(raw_bg); let base_bg = if self.selected { - color.element_active + apparent_bg.blend(color.element_active) } else { - base_bg + apparent_bg }; let hover_color = color .element_active .blend(color.element_background.opacity(0.2)); + let hover_bg = apparent_bg.blend(hover_color); - let gradient_overlay = GradientFade::new(base_bg, hover_color, hover_color) + let gradient_overlay = GradientFade::new(base_bg, hover_bg, hover_bg) .width(px(64.0)) .right(px(-10.0)) .gradient_stop(0.75) @@ -399,7 +401,7 @@ impl RenderOnce for ThreadItem { .child(gradient_overlay) .when(self.hovered, |this| { this.when_some(self.action_slot, |this, slot| { - let overlay = GradientFade::new(base_bg, hover_color, hover_color) + let overlay = GradientFade::new(base_bg, hover_bg, hover_bg) .width(px(64.0)) .right(px(6.)) .gradient_stop(0.75) @@ -432,6 +434,7 @@ impl RenderOnce for ThreadItem { .collect::>() .join("\n") .into(); + let worktree_tooltip_title = if self.worktrees.len() > 1 { "Thread Running in Local Git Worktrees" } else { diff --git a/crates/ui/src/components/gradient_fade.rs b/crates/ui/src/components/gradient_fade.rs index 2173fdf06ea8c07c947f092066c2a12d716d4b44..8a982695ecea7cef9abcf9de2db7ba550971eb8a 100644 --- a/crates/ui/src/components/gradient_fade.rs +++ b/crates/ui/src/components/gradient_fade.rs @@ -49,10 +49,14 @@ impl GradientFade { } impl RenderOnce for GradientFade { - fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement { + fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement { let stop = self.gradient_stop; - let hover_bg = self.hover_bg; - let active_bg = self.active_bg; + + // Best-effort to flatten potentially-transparent colors to opaque ones. + let app_bg = cx.theme().colors().background; + let base_bg = app_bg.blend(self.base_bg); + let hover_bg = app_bg.blend(self.hover_bg); + let active_bg = app_bg.blend(self.active_bg); div() .id("gradient_fade") @@ -63,8 +67,8 @@ impl RenderOnce for GradientFade { .h_full() .bg(linear_gradient( 90., - linear_color_stop(self.base_bg, stop), - linear_color_stop(self.base_bg.opacity(0.0), 0.), + linear_color_stop(base_bg, stop), + linear_color_stop(base_bg.opacity(0.0), 0.), )) .when_some(self.group_name.clone(), |element, group_name| { element.group_hover(group_name, move |s| {