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
This commit is contained in:
Danilo Leal authored and GitHub committed 2026-03-31 12:42:24 -03:00
1 parent 833b8074e7
commit 9503795c6c
5 files changed
+64 -67

No files matched your search

-7
View File
@@ -1,7 +0,0 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8 9.5C8.82843 9.5 9.5 8.82843 9.5 8C9.5 7.17157 8.82843 6.5 8 6.5C7.17157 6.5 6.5 7.17157 6.5 8C6.5 8.82843 7.17157 9.5 8 9.5Z" fill="#C6CAD0"/>
<path d="M2.25 4.80555V3.52777C2.25 3.18889 2.38462 2.86388 2.62425 2.62425C2.86388 2.38462 3.18889 2.25 3.52777 2.25H4.80555" stroke="#C6CAD0" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11.1945 2.25H12.4722C12.8111 2.25 13.1361 2.38462 13.3758 2.62425C13.6154 2.86388 13.75 3.18889 13.75 3.52777V4.80555" stroke="#C6CAD0" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M13.75 11.1945V12.4722C13.75 12.8111 13.6154 13.1361 13.3758 13.3758C13.1361 13.6154 12.8111 13.75 12.4722 13.75H11.1945" stroke="#C6CAD0" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4.80555 13.75H3.52777C3.18889 13.75 2.86388 13.6154 2.62425 13.3758C2.38462 13.1361 2.25 12.8111 2.25 12.4722V11.1945" stroke="#C6CAD0" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

-1
View File
@@ -131,7 +131,6 @@ pub enum IconName {
FileTree,
Filter,
Flame,
Focus,
Folder,
FolderOpen,
FolderPlus,
+46 -48
View File
@@ -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::<AgentPanel>(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::<AgentPanel>(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| {
+9 -6
View File
@@ -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::<Vec<_>>()
.join("\n")
.into();
let worktree_tooltip_title = if self.worktrees.len() > 1 {
"Thread Running in Local Git Worktrees"
} else {
+9 -5
View File
@@ -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| {