WIP

Antonio Scandurra created

Change summary

assets/icons/accept.svg                     |   3 
assets/icons/reject.svg                     |   3 
assets/themes/cave-dark.json                |  39 +++--
assets/themes/cave-light.json               |  39 +++--
assets/themes/dark.json                     |  43 +++--
assets/themes/light.json                    |  43 +++--
assets/themes/solarized-dark.json           |  39 +++--
assets/themes/solarized-light.json          |  39 +++--
assets/themes/sulphurpool-dark.json         |  39 +++--
assets/themes/sulphurpool-light.json        |  39 +++--
crates/client/src/user.rs                   |   9 
crates/contacts_panel/src/contact_finder.rs |  47 ++++--
crates/contacts_panel/src/contacts_panel.rs | 154 ++++++++++++----------
crates/theme/src/theme.rs                   |  12 +
styles/src/styleTree/contactFinder.ts       |  18 +
styles/src/styleTree/contactsPanel.ts       |  21 ++
16 files changed, 346 insertions(+), 241 deletions(-)

Detailed changes

assets/icons/accept.svg 🔗

@@ -0,0 +1,3 @@
+<svg width="8" height="6" viewBox="0 0 8 6" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M7.25 1L3.25 5L1 2.75" stroke="white" stroke-width="1.33333"/>
+</svg>

assets/icons/reject.svg 🔗

@@ -0,0 +1,3 @@
+<svg width="8" height="8" viewBox="0 0 8 8" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path d="M1 7L4 4M7 1L4 4M4 4L1 1M4 4L7 7" stroke="#9C9C9C" stroke-width="1.33333"/>
+</svg>

assets/themes/cave-dark.json 🔗

@@ -1240,11 +1240,12 @@
         "top": 4
       }
     },
-    "add_contact_icon": {
+    "add_contact_button": {
       "margin": {
         "left": 6
       },
-      "color": "#e2dfe7"
+      "color": "#e2dfe7",
+      "width": 8
     },
     "row_height": 28,
     "tree_branch_color": "#655f6d",
@@ -1262,15 +1263,18 @@
       }
     },
     "contact_button": {
-      "family": "Zed Mono",
+      "background": "#26232a",
+      "corner_radius": 8,
+      "padding": 4,
       "color": "#e2dfe7",
-      "size": 14,
+      "width": 8
+    },
+    "disabled_contact_button": {
       "background": "#26232a",
-      "corner_radius": 12,
-      "padding": {
-        "left": 7,
-        "right": 7
-      }
+      "corner_radius": 8,
+      "padding": 4,
+      "color": "#8b8792",
+      "width": 8
     },
     "header": {
       "family": "Zed Mono",
@@ -1477,15 +1481,18 @@
       }
     },
     "contact_button": {
-      "family": "Zed Mono",
+      "background": "#26232a",
+      "corner_radius": 9,
+      "padding": 4,
       "color": "#e2dfe7",
-      "size": 14,
+      "width": 10
+    },
+    "disabled_contact_button": {
       "background": "#26232a",
-      "corner_radius": 12,
-      "padding": {
-        "left": 7,
-        "right": 7
-      }
+      "corner_radius": 9,
+      "padding": 4,
+      "color": "#8b8792",
+      "width": 10
     }
   },
   "search": {

assets/themes/cave-light.json 🔗

@@ -1240,11 +1240,12 @@
         "top": 4
       }
     },
-    "add_contact_icon": {
+    "add_contact_button": {
       "margin": {
         "left": 6
       },
-      "color": "#26232a"
+      "color": "#26232a",
+      "width": 8
     },
     "row_height": 28,
     "tree_branch_color": "#7e7887",
@@ -1262,15 +1263,18 @@
       }
     },
     "contact_button": {
-      "family": "Zed Mono",
+      "background": "#e2dfe7",
+      "corner_radius": 8,
+      "padding": 4,
       "color": "#26232a",
-      "size": 14,
+      "width": 8
+    },
+    "disabled_contact_button": {
       "background": "#e2dfe7",
-      "corner_radius": 12,
-      "padding": {
-        "left": 7,
-        "right": 7
-      }
+      "corner_radius": 8,
+      "padding": 4,
+      "color": "#585260",
+      "width": 8
     },
     "header": {
       "family": "Zed Mono",
@@ -1477,15 +1481,18 @@
       }
     },
     "contact_button": {
-      "family": "Zed Mono",
+      "background": "#e2dfe7",
+      "corner_radius": 9,
+      "padding": 4,
       "color": "#26232a",
-      "size": 14,
+      "width": 10
+    },
+    "disabled_contact_button": {
       "background": "#e2dfe7",
-      "corner_radius": 12,
-      "padding": {
-        "left": 7,
-        "right": 7
-      }
+      "corner_radius": 9,
+      "padding": 4,
+      "color": "#585260",
+      "width": 10
     }
   },
   "search": {

assets/themes/dark.json 🔗

@@ -1240,11 +1240,12 @@
         "top": 4
       }
     },
-    "add_contact_icon": {
+    "add_contact_button": {
       "margin": {
         "left": 6
       },
-      "color": "#c6c6c6"
+      "color": "#c6c6c6",
+      "width": 8
     },
     "row_height": 28,
     "tree_branch_color": "#404040",
@@ -1262,15 +1263,18 @@
       }
     },
     "contact_button": {
-      "family": "Zed Mono",
-      "color": "#f1f1f1",
-      "size": 14,
       "background": "#2b2b2b",
-      "corner_radius": 12,
-      "padding": {
-        "left": 7,
-        "right": 7
-      }
+      "corner_radius": 8,
+      "padding": 4,
+      "color": "#c6c6c6",
+      "width": 8
+    },
+    "disabled_contact_button": {
+      "background": "#2b2b2b",
+      "corner_radius": 8,
+      "padding": 4,
+      "color": "#555555",
+      "width": 8
     },
     "header": {
       "family": "Zed Mono",
@@ -1477,15 +1481,18 @@
       }
     },
     "contact_button": {
-      "family": "Zed Mono",
-      "color": "#f1f1f1",
-      "size": 14,
       "background": "#2b2b2b",
-      "corner_radius": 12,
-      "padding": {
-        "left": 7,
-        "right": 7
-      }
+      "corner_radius": 9,
+      "padding": 4,
+      "color": "#c6c6c6",
+      "width": 10
+    },
+    "disabled_contact_button": {
+      "background": "#2b2b2b",
+      "corner_radius": 9,
+      "padding": 4,
+      "color": "#555555",
+      "width": 10
     }
   },
   "search": {

assets/themes/light.json 🔗

@@ -1240,11 +1240,12 @@
         "top": 4
       }
     },
-    "add_contact_icon": {
+    "add_contact_button": {
       "margin": {
         "left": 6
       },
-      "color": "#393939"
+      "color": "#393939",
+      "width": 8
     },
     "row_height": 28,
     "tree_branch_color": "#e3e3e3",
@@ -1262,15 +1263,18 @@
       }
     },
     "contact_button": {
-      "family": "Zed Mono",
-      "color": "#2b2b2b",
-      "size": 14,
       "background": "#eaeaea",
-      "corner_radius": 12,
-      "padding": {
-        "left": 7,
-        "right": 7
-      }
+      "corner_radius": 8,
+      "padding": 4,
+      "color": "#393939",
+      "width": 8
+    },
+    "disabled_contact_button": {
+      "background": "#eaeaea",
+      "corner_radius": 8,
+      "padding": 4,
+      "color": "#9c9c9c",
+      "width": 8
     },
     "header": {
       "family": "Zed Mono",
@@ -1477,15 +1481,18 @@
       }
     },
     "contact_button": {
-      "family": "Zed Mono",
-      "color": "#2b2b2b",
-      "size": 14,
       "background": "#eaeaea",
-      "corner_radius": 12,
-      "padding": {
-        "left": 7,
-        "right": 7
-      }
+      "corner_radius": 9,
+      "padding": 4,
+      "color": "#393939",
+      "width": 10
+    },
+    "disabled_contact_button": {
+      "background": "#eaeaea",
+      "corner_radius": 9,
+      "padding": 4,
+      "color": "#9c9c9c",
+      "width": 10
     }
   },
   "search": {

assets/themes/solarized-dark.json 🔗

@@ -1240,11 +1240,12 @@
         "top": 4
       }
     },
-    "add_contact_icon": {
+    "add_contact_button": {
       "margin": {
         "left": 6
       },
-      "color": "#eee8d5"
+      "color": "#eee8d5",
+      "width": 8
     },
     "row_height": 28,
     "tree_branch_color": "#657b83",
@@ -1262,15 +1263,18 @@
       }
     },
     "contact_button": {
-      "family": "Zed Mono",
+      "background": "#073642",
+      "corner_radius": 8,
+      "padding": 4,
       "color": "#eee8d5",
-      "size": 14,
+      "width": 8
+    },
+    "disabled_contact_button": {
       "background": "#073642",
-      "corner_radius": 12,
-      "padding": {
-        "left": 7,
-        "right": 7
-      }
+      "corner_radius": 8,
+      "padding": 4,
+      "color": "#93a1a1",
+      "width": 8
     },
     "header": {
       "family": "Zed Mono",
@@ -1477,15 +1481,18 @@
       }
     },
     "contact_button": {
-      "family": "Zed Mono",
+      "background": "#073642",
+      "corner_radius": 9,
+      "padding": 4,
       "color": "#eee8d5",
-      "size": 14,
+      "width": 10
+    },
+    "disabled_contact_button": {
       "background": "#073642",
-      "corner_radius": 12,
-      "padding": {
-        "left": 7,
-        "right": 7
-      }
+      "corner_radius": 9,
+      "padding": 4,
+      "color": "#93a1a1",
+      "width": 10
     }
   },
   "search": {

assets/themes/solarized-light.json 🔗

@@ -1240,11 +1240,12 @@
         "top": 4
       }
     },
-    "add_contact_icon": {
+    "add_contact_button": {
       "margin": {
         "left": 6
       },
-      "color": "#073642"
+      "color": "#073642",
+      "width": 8
     },
     "row_height": 28,
     "tree_branch_color": "#839496",
@@ -1262,15 +1263,18 @@
       }
     },
     "contact_button": {
-      "family": "Zed Mono",
+      "background": "#eee8d5",
+      "corner_radius": 8,
+      "padding": 4,
       "color": "#073642",
-      "size": 14,
+      "width": 8
+    },
+    "disabled_contact_button": {
       "background": "#eee8d5",
-      "corner_radius": 12,
-      "padding": {
-        "left": 7,
-        "right": 7
-      }
+      "corner_radius": 8,
+      "padding": 4,
+      "color": "#586e75",
+      "width": 8
     },
     "header": {
       "family": "Zed Mono",
@@ -1477,15 +1481,18 @@
       }
     },
     "contact_button": {
-      "family": "Zed Mono",
+      "background": "#eee8d5",
+      "corner_radius": 9,
+      "padding": 4,
       "color": "#073642",
-      "size": 14,
+      "width": 10
+    },
+    "disabled_contact_button": {
       "background": "#eee8d5",
-      "corner_radius": 12,
-      "padding": {
-        "left": 7,
-        "right": 7
-      }
+      "corner_radius": 9,
+      "padding": 4,
+      "color": "#586e75",
+      "width": 10
     }
   },
   "search": {

assets/themes/sulphurpool-dark.json 🔗

@@ -1240,11 +1240,12 @@
         "top": 4
       }
     },
-    "add_contact_icon": {
+    "add_contact_button": {
       "margin": {
         "left": 6
       },
-      "color": "#dfe2f1"
+      "color": "#dfe2f1",
+      "width": 8
     },
     "row_height": 28,
     "tree_branch_color": "#6b7394",
@@ -1262,15 +1263,18 @@
       }
     },
     "contact_button": {
-      "family": "Zed Mono",
+      "background": "#293256",
+      "corner_radius": 8,
+      "padding": 4,
       "color": "#dfe2f1",
-      "size": 14,
+      "width": 8
+    },
+    "disabled_contact_button": {
       "background": "#293256",
-      "corner_radius": 12,
-      "padding": {
-        "left": 7,
-        "right": 7
-      }
+      "corner_radius": 8,
+      "padding": 4,
+      "color": "#979db4",
+      "width": 8
     },
     "header": {
       "family": "Zed Mono",
@@ -1477,15 +1481,18 @@
       }
     },
     "contact_button": {
-      "family": "Zed Mono",
+      "background": "#293256",
+      "corner_radius": 9,
+      "padding": 4,
       "color": "#dfe2f1",
-      "size": 14,
+      "width": 10
+    },
+    "disabled_contact_button": {
       "background": "#293256",
-      "corner_radius": 12,
-      "padding": {
-        "left": 7,
-        "right": 7
-      }
+      "corner_radius": 9,
+      "padding": 4,
+      "color": "#979db4",
+      "width": 10
     }
   },
   "search": {

assets/themes/sulphurpool-light.json 🔗

@@ -1240,11 +1240,12 @@
         "top": 4
       }
     },
-    "add_contact_icon": {
+    "add_contact_button": {
       "margin": {
         "left": 6
       },
-      "color": "#293256"
+      "color": "#293256",
+      "width": 8
     },
     "row_height": 28,
     "tree_branch_color": "#898ea4",
@@ -1262,15 +1263,18 @@
       }
     },
     "contact_button": {
-      "family": "Zed Mono",
+      "background": "#dfe2f1",
+      "corner_radius": 8,
+      "padding": 4,
       "color": "#293256",
-      "size": 14,
+      "width": 8
+    },
+    "disabled_contact_button": {
       "background": "#dfe2f1",
-      "corner_radius": 12,
-      "padding": {
-        "left": 7,
-        "right": 7
-      }
+      "corner_radius": 8,
+      "padding": 4,
+      "color": "#5e6687",
+      "width": 8
     },
     "header": {
       "family": "Zed Mono",
@@ -1477,15 +1481,18 @@
       }
     },
     "contact_button": {
-      "family": "Zed Mono",
+      "background": "#dfe2f1",
+      "corner_radius": 9,
+      "padding": 4,
       "color": "#293256",
-      "size": 14,
+      "width": 10
+    },
+    "disabled_contact_button": {
       "background": "#dfe2f1",
-      "corner_radius": 12,
-      "padding": {
-        "left": 7,
-        "right": 7
-      }
+      "corner_radius": 9,
+      "padding": 4,
+      "color": "#5e6687",
+      "width": 10
     }
   },
   "search": {

crates/client/src/user.rs 🔗

@@ -35,7 +35,6 @@ pub struct ProjectMetadata {
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
 pub enum ContactRequestStatus {
     None,
-    Pending,
     RequestSent,
     RequestReceived,
     RequestAccepted,
@@ -278,10 +277,12 @@ impl UserStore {
         &self.outgoing_contact_requests
     }
 
+    pub fn is_contact_request_pending(&self, user: &User) -> bool {
+        self.pending_contact_requests.contains_key(&user.id)
+    }
+
     pub fn contact_request_status(&self, user: &User) -> ContactRequestStatus {
-        if self.pending_contact_requests.contains_key(&user.id) {
-            ContactRequestStatus::Pending
-        } else if self
+        if self
             .contacts
             .binary_search_by_key(&&user.github_login, |contact| &contact.user.github_login)
             .is_ok()

crates/contacts_panel/src/contact_finder.rs 🔗

@@ -108,16 +108,25 @@ impl PickerDelegate for ContactFinder {
         cx: &gpui::AppContext,
     ) -> ElementBox {
         let theme = &cx.global::<Settings>().theme;
-        let contact = &self.potential_contacts[ix];
-        let request_status = self.user_store.read(cx).contact_request_status(&contact);
-        let label = match request_status {
-            ContactRequestStatus::None | ContactRequestStatus::RequestReceived => "+",
-            ContactRequestStatus::RequestSent => "-",
-            ContactRequestStatus::Pending | ContactRequestStatus::RequestAccepted => "•",
+        let user = &self.potential_contacts[ix];
+        let request_status = self.user_store.read(cx).contact_request_status(&user);
+
+        let icon_path = match request_status {
+            ContactRequestStatus::None | ContactRequestStatus::RequestReceived => {
+                "icons/accept.svg"
+            }
+            ContactRequestStatus::RequestSent | ContactRequestStatus::RequestAccepted => {
+                "icons/reject.svg"
+            }
+        };
+        let button_style = if self.user_store.read(cx).is_contact_request_pending(&user) {
+            &theme.contact_finder.disabled_contact_button
+        } else {
+            &theme.contact_finder.contact_button
         };
         let style = theme.picker.item.style_for(mouse_state, selected);
         Flex::row()
-            .with_children(contact.avatar.clone().map(|avatar| {
+            .with_children(user.avatar.clone().map(|avatar| {
                 Image::new(avatar)
                     .with_style(theme.contact_finder.contact_avatar)
                     .aligned()
@@ -125,7 +134,7 @@ impl PickerDelegate for ContactFinder {
                     .boxed()
             }))
             .with_child(
-                Label::new(contact.github_login.clone(), style.label.clone())
+                Label::new(user.github_login.clone(), style.label.clone())
                     .contained()
                     .with_style(theme.contact_finder.contact_username)
                     .aligned()
@@ -133,15 +142,19 @@ impl PickerDelegate for ContactFinder {
                     .boxed(),
             )
             .with_child(
-                Label::new(
-                    label.to_string(),
-                    theme.contact_finder.contact_button.text.clone(),
-                )
-                .contained()
-                .with_style(theme.contact_finder.contact_button.container)
-                .aligned()
-                .flex_float()
-                .boxed(),
+                Svg::new(icon_path)
+                    .with_color(button_style.color)
+                    .constrained()
+                    .with_width(button_style.icon_width)
+                    .aligned()
+                    .constrained()
+                    .with_width(button_style.button_width)
+                    .with_height(button_style.button_width)
+                    .contained()
+                    .with_style(button_style.container)
+                    .aligned()
+                    .flex_float()
+                    .boxed(),
             )
             .contained()
             .with_style(style.container)

crates/contacts_panel/src/contacts_panel.rs 🔗

@@ -1,6 +1,6 @@
 mod contact_finder;
 
-use client::{Contact, ContactRequestStatus, User, UserStore};
+use client::{Contact, User, UserStore};
 use editor::Editor;
 use fuzzy::{match_strings, StringMatchCandidate};
 use gpui::{
@@ -314,7 +314,6 @@ impl ContactsPanel {
         enum Accept {}
 
         let user_id = user.id;
-        let request_status = user_store.read(cx).contact_request_status(&user);
 
         let mut row = Flex::row()
             .with_children(user.avatar.clone().map(|avatar| {
@@ -336,51 +335,60 @@ impl ContactsPanel {
                 .boxed(),
             );
 
-        if request_status == ContactRequestStatus::Pending {
-            row.add_child(
-                Label::new("…".to_string(), theme.contact_button.text.clone())
+        let button_style = if user_store.read(cx).is_contact_request_pending(&user) {
+            &theme.disabled_contact_button
+        } else {
+            &theme.contact_button
+        };
+
+        row.add_children([
+            MouseEventHandler::new::<Reject, _, _>(user.id as usize, cx, |_, _| {
+                Svg::new("icons/reject.svg")
+                    .with_color(button_style.color)
+                    .constrained()
+                    .with_width(button_style.icon_width)
+                    .aligned()
+                    .constrained()
+                    .with_width(button_style.button_width)
+                    .with_height(button_style.button_width)
                     .contained()
-                    .with_style(theme.contact_button.container)
+                    .with_style(button_style.container)
                     .aligned()
-                    .flex_float()
-                    .boxed(),
-            );
-        } else {
-            row.add_children([
-                MouseEventHandler::new::<Reject, _, _>(user.id as usize, cx, |_, _| {
-                    Label::new("Reject".to_string(), theme.contact_button.text.clone())
-                        .contained()
-                        .with_style(theme.contact_button.container)
-                        .aligned()
-                        .flex_float()
-                        .boxed()
-                })
-                .on_click(move |_, cx| {
-                    cx.dispatch_action(RespondToContactRequest {
-                        user_id,
-                        accept: false,
-                    })
-                })
-                .with_cursor_style(CursorStyle::PointingHand)
-                .flex_float()
-                .boxed(),
-                MouseEventHandler::new::<Accept, _, _>(user.id as usize, cx, |_, _| {
-                    Label::new("Accept".to_string(), theme.contact_button.text.clone())
-                        .contained()
-                        .with_style(theme.contact_button.container)
-                        .aligned()
-                        .boxed()
+                    .boxed()
+            })
+            .on_click(move |_, cx| {
+                cx.dispatch_action(RespondToContactRequest {
+                    user_id,
+                    accept: false,
                 })
-                .on_click(move |_, cx| {
-                    cx.dispatch_action(RespondToContactRequest {
-                        user_id,
-                        accept: true,
-                    })
+            })
+            .with_cursor_style(CursorStyle::PointingHand)
+            .flex_float()
+            .boxed(),
+            MouseEventHandler::new::<Accept, _, _>(user.id as usize, cx, |_, _| {
+                Svg::new("icons/accept.svg")
+                    .with_color(button_style.color)
+                    .constrained()
+                    .with_width(button_style.icon_width)
+                    .with_height(button_style.icon_width)
+                    .aligned()
+                    .constrained()
+                    .with_width(button_style.button_width)
+                    .with_height(button_style.button_width)
+                    .contained()
+                    .with_style(button_style.container)
+                    .aligned()
+                    .boxed()
+            })
+            .on_click(move |_, cx| {
+                cx.dispatch_action(RespondToContactRequest {
+                    user_id,
+                    accept: true,
                 })
-                .with_cursor_style(CursorStyle::PointingHand)
-                .boxed(),
-            ]);
-        }
+            })
+            .with_cursor_style(CursorStyle::PointingHand)
+            .boxed(),
+        ]);
 
         row.constrained().with_height(theme.row_height).boxed()
     }
@@ -394,7 +402,11 @@ impl ContactsPanel {
         enum Cancel {}
 
         let user_id = user.id;
-        let request_status = user_store.read(cx).contact_request_status(&user);
+        let button_style = if user_store.read(cx).is_contact_request_pending(&user) {
+            &theme.disabled_contact_button
+        } else {
+            &theme.contact_button
+        };
 
         let mut row = Flex::row()
             .with_children(user.avatar.clone().map(|avatar| {
@@ -416,31 +428,23 @@ impl ContactsPanel {
                 .boxed(),
             );
 
-        if request_status == ContactRequestStatus::Pending {
-            row.add_child(
-                Label::new("…".to_string(), theme.contact_button.text.clone())
+        row.add_child(
+            MouseEventHandler::new::<Cancel, _, _>(user.id as usize, cx, |_, _| {
+                Svg::new("icons/reject.svg")
+                    .with_color(button_style.color)
+                    .constrained()
+                    .with_width(button_style.icon_width)
+                    .with_height(button_style.icon_width)
                     .contained()
-                    .with_style(theme.contact_button.container)
+                    .with_style(button_style.container)
                     .aligned()
-                    .flex_float()
-                    .boxed(),
-            );
-        } else {
-            row.add_child(
-                MouseEventHandler::new::<Cancel, _, _>(user.id as usize, cx, |_, _| {
-                    Label::new("Cancel".to_string(), theme.contact_button.text.clone())
-                        .contained()
-                        .with_style(theme.contact_button.container)
-                        .aligned()
-                        .flex_float()
-                        .boxed()
-                })
-                .on_click(move |_, cx| cx.dispatch_action(RemoveContact(user_id)))
-                .with_cursor_style(CursorStyle::PointingHand)
-                .flex_float()
-                .boxed(),
-            );
-        }
+                    .boxed()
+            })
+            .on_click(move |_, cx| cx.dispatch_action(RemoveContact(user_id)))
+            .with_cursor_style(CursorStyle::PointingHand)
+            .flex_float()
+            .boxed(),
+        );
 
         row.constrained().with_height(theme.row_height).boxed()
     }
@@ -452,6 +456,7 @@ impl ContactsPanel {
 
         self.entries.clear();
 
+        let mut request_entries = Vec::new();
         let incoming = user_store.incoming_contact_requests();
         if !incoming.is_empty() {
             self.match_candidates.clear();
@@ -475,8 +480,7 @@ impl ContactsPanel {
                 executor.clone(),
             ));
             if !matches.is_empty() {
-                self.entries.push(ContactEntry::Header("Requests Received"));
-                self.entries.extend(
+                request_entries.extend(
                     matches.iter().map(|mat| {
                         ContactEntry::IncomingRequest(incoming[mat.candidate_id].clone())
                     }),
@@ -507,8 +511,7 @@ impl ContactsPanel {
                 executor.clone(),
             ));
             if !matches.is_empty() {
-                self.entries.push(ContactEntry::Header("Requests Sent"));
-                self.entries.extend(
+                request_entries.extend(
                     matches.iter().map(|mat| {
                         ContactEntry::OutgoingRequest(outgoing[mat.candidate_id].clone())
                     }),
@@ -516,6 +519,11 @@ impl ContactsPanel {
             }
         }
 
+        if !request_entries.is_empty() {
+            self.entries.push(ContactEntry::Header("Requests"));
+            self.entries.append(&mut request_entries);
+        }
+
         let contacts = user_store.contacts();
         if !contacts.is_empty() {
             self.match_candidates.clear();
@@ -617,11 +625,11 @@ impl View for ContactsPanel {
                         .with_child(
                             MouseEventHandler::new::<AddContact, _, _>(0, cx, |_, _| {
                                 Svg::new("icons/add-contact.svg")
-                                    .with_color(theme.add_contact_icon.color)
+                                    .with_color(theme.add_contact_button.color)
                                     .constrained()
                                     .with_height(12.)
                                     .contained()
-                                    .with_style(theme.add_contact_icon.container)
+                                    .with_style(theme.add_contact_button.container)
                                     .aligned()
                                     .boxed()
                             })

crates/theme/src/theme.rs 🔗

@@ -237,11 +237,12 @@ pub struct ContactsPanel {
     pub container: ContainerStyle,
     pub header: ContainedText,
     pub user_query_editor: FieldEditor,
-    pub add_contact_icon: AddContactIcon,
+    pub add_contact_button: IconButton,
     pub row_height: f32,
     pub contact_avatar: ImageStyle,
     pub contact_username: ContainedText,
-    pub contact_button: ContainedText,
+    pub contact_button: IconButton,
+    pub disabled_contact_button: IconButton,
     pub tree_branch_width: f32,
     pub tree_branch_color: Color,
     pub shared_project: ProjectRow,
@@ -255,14 +256,17 @@ pub struct ContactFinder {
     pub row_height: f32,
     pub contact_avatar: ImageStyle,
     pub contact_username: ContainerStyle,
-    pub contact_button: ContainedText,
+    pub contact_button: IconButton,
+    pub disabled_contact_button: IconButton,
 }
 
 #[derive(Deserialize, Default)]
-pub struct AddContactIcon {
+pub struct IconButton {
     #[serde(flatten)]
     pub container: ContainerStyle,
     pub color: Color,
+    pub icon_width: f32,
+    pub button_width: f32,
 }
 
 #[derive(Deserialize, Default)]

styles/src/styleTree/contactFinder.ts 🔗

@@ -1,8 +1,16 @@
 import Theme from "../themes/theme";
 import picker from "./picker";
-import { backgroundColor, text } from "./components";
+import { backgroundColor, iconColor } from "./components";
 
 export default function contactFinder(theme: Theme) {
+  const contactButton = {
+    background: backgroundColor(theme, 100),
+    color: iconColor(theme, "primary"),
+    iconWidth: 8,
+    buttonWidth: 16,
+    cornerRadius: 8,
+  };
+
   return {
     ...picker(theme),
     rowHeight: 28,
@@ -15,11 +23,11 @@ export default function contactFinder(theme: Theme) {
         left: 8,
       },
     },
-    contactButton: {
-      ...text(theme, "mono", "primary", { size: "sm" }),
+    contactButton,
+    disabledContactButton: {
+      ...contactButton,
       background: backgroundColor(theme, 100),
-      cornerRadius: 12,
-      padding: { left: 7, right: 7 }
+      color: iconColor(theme, "muted"),
     },
   }
 }

styles/src/styleTree/contactsPanel.ts 🔗

@@ -31,6 +31,14 @@ export default function contactsPanel(theme: Theme) {
     },
   };
 
+  const contactButton = {
+    background: backgroundColor(theme, 100),
+    color: iconColor(theme, "primary"),
+    iconWidth: 8,
+    buttonWidth: 16,
+    cornerRadius: 8,
+  };
+
   return {
     ...panel,
     userQueryEditor: {
@@ -47,9 +55,10 @@ export default function contactsPanel(theme: Theme) {
         top: 4,
       },
     },
-    addContactIcon: {
+    addContactButton: {
       margin: { left: 6 },
-      color: iconColor(theme, "primary")
+      color: iconColor(theme, "primary"),
+      width: 8,
     },
     rowHeight: 28,
     treeBranchColor: borderColor(theme, "muted"),
@@ -64,11 +73,11 @@ export default function contactsPanel(theme: Theme) {
         left: 8,
       },
     },
-    contactButton: {
-      ...text(theme, "mono", "primary", { size: "sm" }),
+    contactButton,
+    disabledContactButton: {
+      ...contactButton,
       background: backgroundColor(theme, 100),
-      cornerRadius: 12,
-      padding: { left: 7, right: 7 }
+      color: iconColor(theme, "muted"),
     },
     header: {
       ...text(theme, "mono", "secondary", { size: "sm" }),