sidebar: Improve project header truncation (#51096)

Danilo Leal created

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

Change summary

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(-)

Detailed changes

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<Self>,
     ) -> 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(

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.),
                 ))
             });

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),
                         )
                     }),