Style contact finder (#3717)

Marshall Bowers created

This PR styles the contact finder.

<img width="598" alt="Screenshot 2023-12-19 at 12 59 00 PM"
src="https://github.com/zed-industries/zed/assets/1486634/ca9d9345-bf27-4256-aabc-6017a0c4d217">

Release Notes:

- N/A

Change summary

crates/collab_ui2/src/collab_panel.rs                | 11 -
crates/collab_ui2/src/collab_panel/contact_finder.rs | 66 +++----------
crates/picker2/src/picker2.rs                        | 42 ++++----
3 files changed, 40 insertions(+), 79 deletions(-)

Detailed changes

crates/collab_ui2/src/collab_panel.rs 🔗

@@ -2204,14 +2204,9 @@ impl CollabPanel {
                     .into_any_element()
             }),
             Section::Contacts => Some(
-                div()
-                    .border_1()
-                    .border_color(gpui::red())
-                    .child(
-                        IconButton::new("add-contact", Icon::Plus)
-                            .on_click(cx.listener(|this, _, cx| this.toggle_contact_finder(cx)))
-                            .tooltip(|cx| Tooltip::text("Search for new contact", cx)),
-                    )
+                IconButton::new("add-contact", Icon::Plus)
+                    .on_click(cx.listener(|this, _, cx| this.toggle_contact_finder(cx)))
+                    .tooltip(|cx| Tooltip::text("Search for new contact", cx))
                     .into_any_element(),
             ),
             Section::Channels => Some(

crates/collab_ui2/src/collab_panel/contact_finder.rs 🔗

@@ -7,7 +7,7 @@ use gpui::{
 use picker::{Picker, PickerDelegate};
 use std::sync::Arc;
 use theme::ActiveTheme as _;
-use ui::{prelude::*, Avatar};
+use ui::{prelude::*, Avatar, ListItem};
 use util::{ResultExt as _, TryFutureExt};
 use workspace::ModalView;
 
@@ -23,7 +23,7 @@ impl ContactFinder {
             potential_contacts: Arc::from([]),
             selected_index: 0,
         };
-        let picker = cx.build_view(|cx| Picker::new(delegate, cx));
+        let picker = cx.build_view(|cx| Picker::new(delegate, cx).modal(false));
 
         Self { picker }
     }
@@ -37,16 +37,17 @@ impl ContactFinder {
 
 impl Render for ContactFinder {
     fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
-        fn render_mode_button(text: &'static str) -> AnyElement {
-            Label::new(text).into_any_element()
-        }
-
         v_stack()
+            .elevation_3(cx)
             .child(
                 v_stack()
+                    .px_2()
+                    .py_1()
+                    .bg(cx.theme().colors().element_background)
+                    // HACK: Prevent the background color from overflowing the parent container.
+                    .rounded_t(px(8.))
                     .child(Label::new("Contacts"))
-                    .child(h_stack().children([render_mode_button("Invite new contacts")]))
-                    .bg(cx.theme().colors().element_background),
+                    .child(h_stack().child(Label::new("Invite new contacts"))),
             )
             .child(self.picker.clone())
             .w(rems(34.))
@@ -72,7 +73,8 @@ impl FocusableView for ContactFinder {
 }
 
 impl PickerDelegate for ContactFinderDelegate {
-    type ListItem = Div;
+    type ListItem = ListItem;
+
     fn match_count(&self) -> usize {
         self.potential_contacts.len()
     }
@@ -150,46 +152,14 @@ impl PickerDelegate for ContactFinderDelegate {
             ContactRequestStatus::RequestAccepted => None,
         };
         Some(
-            div()
-                .flex_1()
-                .justify_between()
-                .child(Avatar::new(user.avatar_uri.clone()))
+            ListItem::new(ix)
+                .inset(true)
+                .selected(selected)
+                .start_slot(Avatar::new(user.avatar_uri.clone()))
                 .child(Label::new(user.github_login.clone()))
-                .children(icon_path.map(|icon_path| svg().path(icon_path))),
+                .end_slot::<IconElement>(
+                    icon_path.map(|icon_path| IconElement::from_path(icon_path)),
+                ),
         )
-        // todo!()
-        // Flex::row()
-        //     .with_children(user.avatar.clone().map(|avatar| {
-        //         Image::from_data(avatar)
-        //             .with_style(theme.contact_avatar)
-        //             .aligned()
-        //             .left()
-        //     }))
-        //     .with_child(
-        //         Label::new(user.github_login.clone(), style.label.clone())
-        //             .contained()
-        //             .with_style(theme.contact_username)
-        //             .aligned()
-        //             .left(),
-        //     )
-        //     .with_children(icon_path.map(|icon_path| {
-        //         Svg::new(icon_path)
-        //             .with_color(button_style.color)
-        //             .constrained()
-        //             .with_width(button_style.icon_width)
-        //             .aligned()
-        //             .contained()
-        //             .with_style(button_style.container)
-        //             .constrained()
-        //             .with_width(button_style.button_width)
-        //             .with_height(button_style.button_width)
-        //             .aligned()
-        //             .flex_float()
-        //     }))
-        //     .contained()
-        //     .with_style(style.container)
-        //     .constrained()
-        //     .with_height(tabbed_modal.row_height)
-        //     .into_any()
     }
 }

crates/picker2/src/picker2.rs 🔗

@@ -1,8 +1,8 @@
 use editor::Editor;
 use gpui::{
-    div, prelude::*, rems, uniform_list, AnyElement, AppContext, DismissEvent, Div, EventEmitter,
-    FocusHandle, FocusableView, Length, MouseButton, MouseDownEvent, Render, Task,
-    UniformListScrollHandle, View, ViewContext, WindowContext,
+    div, prelude::*, rems, uniform_list, AppContext, DismissEvent, Div, EventEmitter, FocusHandle,
+    FocusableView, Length, MouseButton, MouseDownEvent, Render, Task, UniformListScrollHandle,
+    View, ViewContext, WindowContext,
 };
 use std::{cmp, sync::Arc};
 use ui::{prelude::*, v_stack, Color, Divider, Label};
@@ -15,6 +15,11 @@ pub struct Picker<D: PickerDelegate> {
     pending_update_matches: Option<Task<()>>,
     confirm_on_update: Option<bool>,
     width: Option<Length>,
+
+    /// Whether the `Picker` is rendered as a self-contained modal.
+    ///
+    /// Set this to `false` when rendering the `Picker` as part of a larger modal.
+    is_modal: bool,
 }
 
 pub trait PickerDelegate: Sized + 'static {
@@ -58,6 +63,7 @@ impl<D: PickerDelegate> Picker<D> {
             pending_update_matches: None,
             confirm_on_update: None,
             width: None,
+            is_modal: true,
         };
         this.update_matches("".to_string(), cx);
         this
@@ -68,6 +74,11 @@ impl<D: PickerDelegate> Picker<D> {
         self
     }
 
+    pub fn modal(mut self, modal: bool) -> Self {
+        self.is_modal = modal;
+        self
+    }
+
     pub fn focus(&self, cx: &mut WindowContext) {
         self.editor.update(cx, |editor, cx| editor.focus(cx));
     }
@@ -234,7 +245,11 @@ impl<D: PickerDelegate> Render for Picker<D> {
                 el.w(width)
             })
             .overflow_hidden()
-            .elevation_3(cx)
+            // This is a bit of a hack to remove the modal styling when we're rendering the `Picker`
+            // as a part of a modal rather than the entire modal.
+            //
+            // We should revisit how the `Picker` is styled to make it more composable.
+            .when(self.is_modal, |this| this.elevation_3(cx))
             .on_action(cx.listener(Self::select_next))
             .on_action(cx.listener(Self::select_prev))
             .on_action(cx.listener(Self::select_first))
@@ -295,22 +310,3 @@ impl<D: PickerDelegate> Render for Picker<D> {
             })
     }
 }
-
-pub fn simple_picker_match(
-    selected: bool,
-    cx: &mut WindowContext,
-    children: impl FnOnce(&mut WindowContext) -> AnyElement,
-) -> AnyElement {
-    let colors = cx.theme().colors();
-
-    div()
-        .px_1()
-        .text_color(colors.text)
-        .text_ui()
-        .bg(colors.ghost_element_background)
-        .rounded_md()
-        .when(selected, |this| this.bg(colors.ghost_element_selected))
-        .hover(|this| this.bg(colors.ghost_element_hover))
-        .child((children)(cx))
-        .into_any()
-}