Fix documentation labels obscuring other content in completion menu (#4167)

Marshall Bowers created

This PF fixes an issue where the documentation labels in the completion
menu could end up completing obscuring the primary content.

#### Before
<img width="691" alt="Screenshot 2024-01-19 at 1 42 19 PM"
src="https://github.com/zed-industries/zed/assets/1486634/187f2978-08be-4f5e-ba93-91ed09cd6e87">

#### After

<img width="704" alt="Screenshot 2024-01-19 at 1 39 45 PM"
src="https://github.com/zed-industries/zed/assets/1486634/e5929c04-bace-4336-b878-4ada5660d235">

Since this involved changes to the `ListItem`, I also made sure to test
the other problematic case that was fixed in #3845 to make sure we
didn't regress there:

<img width="665" alt="Screenshot 2024-01-19 at 1 39 11 PM"
src="https://github.com/zed-industries/zed/assets/1486634/14b72d44-b6a6-4aef-a708-37fb60b8875a">

I also tried to capture these cases in the `ListItem` stories to make it
easier to test all of them at once:

<img width="1141" alt="Screenshot 2024-01-19 at 1 40 03 PM"
src="https://github.com/zed-industries/zed/assets/1486634/333886ff-e394-4cba-89db-98352481c7f6">

Release Notes:

- Fixed an issue where documentation labels could obscure other content
in the editor completion menu
([#2419](https://github.com/zed-industries/community/issues/2419)).

Change summary

crates/gpui/src/styled.rs                     | 21 ++++++++++++++++
crates/ui/src/components/list/list_item.rs    | 10 ++++---
crates/ui/src/components/stories/list_item.rs | 26 +++++++++++++++++++++
3 files changed, 53 insertions(+), 4 deletions(-)

Detailed changes

crates/gpui/src/styled.rs 🔗

@@ -307,6 +307,13 @@ pub trait Styled: Sized {
         self
     }
 
+    /// Sets the initial size of flex items for this element.
+    /// [Docs](https://tailwindcss.com/docs/flex-basis)
+    fn flex_basis(mut self, basis: impl Into<Length>) -> Self {
+        self.style().flex_basis = Some(basis.into());
+        self
+    }
+
     /// Sets the element to allow a flex item to grow to fill any available space.
     /// [Docs](https://tailwindcss.com/docs/flex-grow)
     fn flex_grow(mut self) -> Self {
@@ -314,6 +321,20 @@ pub trait Styled: Sized {
         self
     }
 
+    /// Sets the element to allow a flex item to shrink if needed.
+    /// [Docs](https://tailwindcss.com/docs/flex-shrink)
+    fn flex_shrink(mut self) -> Self {
+        self.style().flex_shrink = Some(1.);
+        self
+    }
+
+    /// Sets the element to prevent a flex item from shrinking.
+    /// [Docs](https://tailwindcss.com/docs/flex-shrink#dont-shrink)
+    fn flex_shrink_0(mut self) -> Self {
+        self.style().flex_shrink = Some(0.);
+        self
+    }
+
     /// Sets the element to align flex items to the start of the container's cross axis.
     /// [Docs](https://tailwindcss.com/docs/align-items#start)
     fn items_start(mut self) -> Self {

crates/ui/src/components/list/list_item.rs 🔗

@@ -222,17 +222,19 @@ impl RenderOnce for ListItem {
                     }))
                     .child(
                         h_flex()
-                            // HACK: We need to set *any* width value here in order for this container to size correctly.
-                            // Without this the `h_flex` will overflow the parent `inner_list_item`.
-                            .w_px()
-                            .flex_1()
+                            .flex_grow()
+                            .flex_shrink_0()
+                            .flex_basis(relative(0.25))
                             .gap_1()
+                            .overflow_hidden()
                             .children(self.start_slot)
                             .children(self.children),
                     )
                     .when_some(self.end_slot, |this, end_slot| {
                         this.justify_between().child(
                             h_flex()
+                                .flex_shrink()
+                                .overflow_hidden()
                                 .when(self.end_hover_slot.is_some(), |this| {
                                     this.visible()
                                         .group_hover("list_item", |this| this.invisible())

crates/ui/src/components/stories/list_item.rs 🔗

@@ -4,6 +4,8 @@ use story::Story;
 use crate::{prelude::*, Avatar};
 use crate::{IconName, ListItem};
 
+const OVERFLOWING_TEXT: &'static str = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean mauris ligula, luctus vel dignissim eu, vestibulum sed libero. Sed at convallis velit.";
+
 pub struct ListItemStory;
 
 impl Render for ListItemStory {
@@ -98,5 +100,29 @@ impl Render for ListItemStory {
                         println!("Right mouse down!");
                     }),
             )
+            .child(Story::label("With overflowing content in the `end_slot`"))
+            .child(
+                ListItem::new("with_overflowing_content_in_end_slot")
+                    .child("An excerpt")
+                    .end_slot(Label::new(OVERFLOWING_TEXT).color(Color::Muted)),
+            )
+            .child(Story::label(
+                "`inset` with overflowing content in the `end_slot`",
+            ))
+            .child(
+                ListItem::new("inset_with_overflowing_content_in_end_slot")
+                    .inset(true)
+                    .child("An excerpt")
+                    .end_slot(Label::new(OVERFLOWING_TEXT).color(Color::Muted)),
+            )
+            .child(Story::label(
+                "`inset` with overflowing content in `children` and `end_slot`",
+            ))
+            .child(
+                ListItem::new("inset_with_overflowing_content_in_children_and_end_slot")
+                    .inset(true)
+                    .child(Label::new(OVERFLOWING_TEXT))
+                    .end_slot(Label::new(OVERFLOWING_TEXT).color(Color::Muted)),
+            )
     }
 }