From 26f81c481872c9f3a52e584eeb8140e76b1b6f85 Mon Sep 17 00:00:00 2001 From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com> Date: Mon, 9 Mar 2026 10:03:15 -0300 Subject: [PATCH] sidebar: Improve project header truncation (#51096) Touching up the scenario in which the project header label is too big. This uses the same gradient overlay treatment we're using for the thread item component. Release Notes: - N/A --- crates/sidebar/src/sidebar.rs | 42 ++++++++++-- crates/ui/src/components/ai/thread_item.rs | 6 +- crates/ui/src/components/list/list_item.rs | 78 ++++++++++++++++------ 3 files changed, 99 insertions(+), 27 deletions(-) diff --git a/crates/sidebar/src/sidebar.rs b/crates/sidebar/src/sidebar.rs index 40ba738ba98ff4d77932eabeca9bdf0a7d0b8861..45a56f7af203e8ffe01b8590f916b439a57c52fb 100644 --- a/crates/sidebar/src/sidebar.rs +++ b/crates/sidebar/src/sidebar.rs @@ -7,8 +7,8 @@ use editor::{Editor, EditorElement, EditorStyle}; use feature_flags::{AgentV2FeatureFlag, FeatureFlagViewExt as _}; use gpui::{ AnyElement, App, Context, Entity, EventEmitter, FocusHandle, Focusable, FontStyle, ListState, - Pixels, Render, SharedString, TextStyle, WeakEntity, Window, actions, list, prelude::*, px, - relative, rems, + Pixels, Render, SharedString, TextStyle, WeakEntity, Window, actions, linear_color_stop, + linear_gradient, list, prelude::*, px, relative, rems, }; use menu::{Cancel, Confirm, SelectFirst, SelectLast, SelectNext, SelectPrevious}; use project::Event as ProjectEvent; @@ -753,6 +753,7 @@ impl Sidebar { cx: &mut Context, ) -> AnyElement { let id = SharedString::from(format!("project-header-{}", ix)); + let group_name = SharedString::from(format!("header-group-{}", ix)); let ib_id = SharedString::from(format!("project-header-new-thread-{}", ix)); let is_collapsed = self.collapsed_groups.contains(path_list); @@ -786,11 +787,44 @@ impl Sidebar { .into_any_element() }; + let color = cx.theme().colors(); + let base_bg = color.panel_background; + let gradient_overlay = div() + .id("gradient_overlay") + .absolute() + .top_0() + .right_0() + .w_12() + .h_full() + .bg(linear_gradient( + 90., + linear_color_stop(base_bg, 0.6), + linear_color_stop(base_bg.opacity(0.0), 0.), + )) + .group_hover(group_name.clone(), |s| { + s.bg(linear_gradient( + 90., + linear_color_stop(color.element_hover, 0.6), + linear_color_stop(color.element_hover.opacity(0.0), 0.), + )) + }) + .group_active(group_name.clone(), |s| { + s.bg(linear_gradient( + 90., + linear_color_stop(color.element_active, 0.6), + linear_color_stop(color.element_active.opacity(0.0), 0.), + )) + }); + ListItem::new(id) + .group_name(group_name) .toggle_state(is_active_workspace) .focused(is_selected) .child( h_flex() + .relative() + .min_w_0() + .w_full() .p_1() .gap_1p5() .child( @@ -798,11 +832,11 @@ impl Sidebar { .size(IconSize::Small) .color(Color::Custom(cx.theme().colors().icon_muted.opacity(0.6))), ) - .child(label), + .child(label) + .child(gradient_overlay), ) .end_hover_slot( h_flex() - .gap_0p5() .when(workspace_count > 1, |this| { this.child( IconButton::new( diff --git a/crates/ui/src/components/ai/thread_item.rs b/crates/ui/src/components/ai/thread_item.rs index 171a6968290b3239e21faf9cd669559b88f9a964..be27e6332ca500747e1836bbd577c7fd5ffb2507 100644 --- a/crates/ui/src/components/ai/thread_item.rs +++ b/crates/ui/src/components/ai/thread_item.rs @@ -224,17 +224,17 @@ impl RenderOnce for ThreadItem { .absolute() .top_0() .right(px(-10.0)) - .w_12() + .w_8() .h_full() .bg(linear_gradient( 90., - linear_color_stop(base_bg, 0.6), + linear_color_stop(base_bg, 0.8), linear_color_stop(base_bg.opacity(0.0), 0.), )) .group_hover("thread-item", |s| { s.bg(linear_gradient( 90., - linear_color_stop(color.element_hover, 0.6), + linear_color_stop(color.element_hover, 0.8), linear_color_stop(color.element_hover.opacity(0.0), 0.), )) }); diff --git a/crates/ui/src/components/list/list_item.rs b/crates/ui/src/components/list/list_item.rs index d581fad9453d9812f17b7bc9e0297fb9927c8188..0a1fbe7f40970f265513751090ed998a5521dfef 100644 --- a/crates/ui/src/components/list/list_item.rs +++ b/crates/ui/src/components/list/list_item.rs @@ -1,7 +1,10 @@ use std::sync::Arc; use component::{Component, ComponentScope, example_group_with_title, single_example}; -use gpui::{AnyElement, AnyView, ClickEvent, MouseButton, MouseDownEvent, Pixels, px}; +use gpui::{ + AnyElement, AnyView, ClickEvent, MouseButton, MouseDownEvent, Pixels, linear_color_stop, + linear_gradient, px, +}; use smallvec::SmallVec; use crate::{Disclosure, prelude::*}; @@ -209,6 +212,43 @@ impl ParentElement for ListItem { impl RenderOnce for ListItem { fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement { + let color = cx.theme().colors(); + + let base_bg = if self.selected { + color.element_active + } else { + color.panel_background + }; + + let end_hover_gradient_overlay = div() + .id("gradient_overlay") + .absolute() + .top_0() + .right_0() + .w_24() + .h_full() + .bg(linear_gradient( + 90., + linear_color_stop(base_bg, 0.6), + linear_color_stop(base_bg.opacity(0.0), 0.), + )) + .when_some(self.group_name.clone(), |s, group_name| { + s.group_hover(group_name.clone(), |s| { + s.bg(linear_gradient( + 90., + linear_color_stop(color.element_hover, 0.6), + linear_color_stop(color.element_hover.opacity(0.0), 0.), + )) + }) + .group_active(group_name, |s| { + s.bg(linear_gradient( + 90., + linear_color_stop(color.element_active, 0.6), + linear_color_stop(color.element_active.opacity(0.0), 0.), + )) + }) + }); + h_flex() .id(self.id) .when_some(self.group_name, |this, group| this.group(group)) @@ -220,25 +260,22 @@ impl RenderOnce for ListItem { .px(DynamicSpacing::Base04.rems(cx)) }) .when(!self.inset && !self.disabled, |this| { - this - // TODO: Add focus state - // .when(self.state == InteractionState::Focused, |this| { - .when_some(self.focused, |this, focused| { - if focused { - this.border_1() - .border_color(cx.theme().colors().border_focused) - } else { - this.border_1() - } - }) - .when(self.selectable, |this| { - this.hover(|style| style.bg(cx.theme().colors().ghost_element_hover)) - .active(|style| style.bg(cx.theme().colors().ghost_element_active)) - .when(self.outlined, |this| this.rounded_sm()) - .when(self.selected, |this| { - this.bg(cx.theme().colors().ghost_element_selected) - }) - }) + this.when_some(self.focused, |this, focused| { + if focused { + this.border_1() + .border_color(cx.theme().colors().border_focused) + } else { + this.border_1() + } + }) + .when(self.selectable, |this| { + this.hover(|style| style.bg(cx.theme().colors().ghost_element_hover)) + .active(|style| style.bg(cx.theme().colors().ghost_element_active)) + .when(self.outlined, |this| this.rounded_sm()) + .when(self.selected, |this| { + this.bg(cx.theme().colors().ghost_element_selected) + }) + }) }) .when(self.rounded, |this| this.rounded_sm()) .when_some(self.on_hover, |this, on_hover| this.on_hover(on_hover)) @@ -350,6 +387,7 @@ impl RenderOnce for ListItem { .right(DynamicSpacing::Base06.rems(cx)) .top_0() .visible_on_hover("list_item") + .child(end_hover_gradient_overlay) .child(end_hover_slot), ) }),