Rework the contact panel's styling to allow keyboard navigation

Max Brunsfeld and Nathan Sobo created

Co-authored-by: Nathan Sobo <nathan@zed.dev>

Change summary

assets/themes/cave-dark.json                |  63 +++++-
assets/themes/cave-light.json               |  63 +++++-
assets/themes/dark.json                     |  63 +++++-
assets/themes/light.json                    |  63 +++++-
assets/themes/solarized-dark.json           |  63 +++++-
assets/themes/solarized-light.json          |  63 +++++-
assets/themes/sulphurpool-dark.json         |  63 +++++-
assets/themes/sulphurpool-light.json        |  63 +++++-
crates/contacts_panel/src/contacts_panel.rs | 211 +++++++++++-----------
crates/theme/src/theme.rs                   |  12 
styles/src/styleTree/app.ts                 |   2 
styles/src/styleTree/contactsPanel.ts       |  61 ++++-
12 files changed, 526 insertions(+), 264 deletions(-)

Detailed changes

assets/themes/cave-dark.json 🔗

@@ -989,9 +989,7 @@
   "chat_panel": {
     "padding": {
       "top": 12,
-      "left": 12,
-      "bottom": 12,
-      "right": 12
+      "bottom": 12
     },
     "channel_name": {
       "family": "Zed Sans",
@@ -1208,9 +1206,7 @@
   "contacts_panel": {
     "padding": {
       "top": 12,
-      "left": 12,
-      "bottom": 12,
-      "right": 12
+      "bottom": 12
     },
     "user_query_editor": {
       "background": "#19171c",
@@ -1238,23 +1234,33 @@
         "left": 8,
         "right": 8,
         "top": 4
+      },
+      "margin": {
+        "left": 12,
+        "right": 12
       }
     },
     "user_query_editor_height": 32,
     "add_contact_button": {
       "margin": {
-        "left": 6
+        "left": 6,
+        "right": 12
       },
       "color": "#e2dfe7",
       "button_width": 8,
       "icon_width": 8
     },
+    "row_height": 28,
     "header_row": {
       "family": "Zed Mono",
       "color": "#8b8792",
       "size": 14,
       "margin": {
-        "top": 8
+        "top": 14
+      },
+      "padding": {
+        "left": 12,
+        "right": 12
       },
       "active": {
         "family": "Zed Mono",
@@ -1265,15 +1271,23 @@
     },
     "contact_row": {
       "padding": {
-        "left": 8
+        "left": 12,
+        "right": 12
       },
       "active": {
         "background": "#5852605c"
       }
     },
-    "row_height": 28,
-    "tree_branch_color": "#655f6d",
-    "tree_branch_width": 1,
+    "tree_branch": {
+      "color": "#655f6d",
+      "width": 1,
+      "hover": {
+        "color": "#655f6d"
+      },
+      "active": {
+        "color": "#655f6d"
+      }
+    },
     "contact_avatar": {
       "corner_radius": 10,
       "width": 18
@@ -1282,10 +1296,11 @@
       "family": "Zed Mono",
       "color": "#e2dfe7",
       "size": 14,
-      "padding": {
+      "margin": {
         "left": 8
       }
     },
+    "contact_button_spacing": 8,
     "contact_button": {
       "background": "#26232a",
       "color": "#e2dfe7",
@@ -1315,14 +1330,21 @@
         "color": "#8b8792",
         "size": 14,
         "margin": {
+          "left": 8,
           "right": 6
         }
       },
+      "guests": {
+        "margin": {
+          "left": 8,
+          "right": 8
+        }
+      },
       "padding": {
-        "left": 8
+        "left": 12,
+        "right": 12
       },
       "background": "#26232a",
-      "corner_radius": 6,
       "hover": {
         "background": "#5852603d"
       },
@@ -1342,14 +1364,21 @@
         "color": "#8b8792",
         "size": 14,
         "margin": {
+          "left": 8,
           "right": 6
         }
       },
+      "guests": {
+        "margin": {
+          "left": 8,
+          "right": 8
+        }
+      },
       "padding": {
-        "left": 8
+        "left": 12,
+        "right": 12
       },
       "background": "#26232a",
-      "corner_radius": 6,
       "hover": {
         "background": "#5852603d"
       },

assets/themes/cave-light.json 🔗

@@ -989,9 +989,7 @@
   "chat_panel": {
     "padding": {
       "top": 12,
-      "left": 12,
-      "bottom": 12,
-      "right": 12
+      "bottom": 12
     },
     "channel_name": {
       "family": "Zed Sans",
@@ -1208,9 +1206,7 @@
   "contacts_panel": {
     "padding": {
       "top": 12,
-      "left": 12,
-      "bottom": 12,
-      "right": 12
+      "bottom": 12
     },
     "user_query_editor": {
       "background": "#efecf4",
@@ -1238,23 +1234,33 @@
         "left": 8,
         "right": 8,
         "top": 4
+      },
+      "margin": {
+        "left": 12,
+        "right": 12
       }
     },
     "user_query_editor_height": 32,
     "add_contact_button": {
       "margin": {
-        "left": 6
+        "left": 6,
+        "right": 12
       },
       "color": "#26232a",
       "button_width": 8,
       "icon_width": 8
     },
+    "row_height": 28,
     "header_row": {
       "family": "Zed Mono",
       "color": "#585260",
       "size": 14,
       "margin": {
-        "top": 8
+        "top": 14
+      },
+      "padding": {
+        "left": 12,
+        "right": 12
       },
       "active": {
         "family": "Zed Mono",
@@ -1265,15 +1271,23 @@
     },
     "contact_row": {
       "padding": {
-        "left": 8
+        "left": 12,
+        "right": 12
       },
       "active": {
         "background": "#8b87922e"
       }
     },
-    "row_height": 28,
-    "tree_branch_color": "#7e7887",
-    "tree_branch_width": 1,
+    "tree_branch": {
+      "color": "#7e7887",
+      "width": 1,
+      "hover": {
+        "color": "#7e7887"
+      },
+      "active": {
+        "color": "#7e7887"
+      }
+    },
     "contact_avatar": {
       "corner_radius": 10,
       "width": 18
@@ -1282,10 +1296,11 @@
       "family": "Zed Mono",
       "color": "#26232a",
       "size": 14,
-      "padding": {
+      "margin": {
         "left": 8
       }
     },
+    "contact_button_spacing": 8,
     "contact_button": {
       "background": "#e2dfe7",
       "color": "#26232a",
@@ -1315,14 +1330,21 @@
         "color": "#585260",
         "size": 14,
         "margin": {
+          "left": 8,
           "right": 6
         }
       },
+      "guests": {
+        "margin": {
+          "left": 8,
+          "right": 8
+        }
+      },
       "padding": {
-        "left": 8
+        "left": 12,
+        "right": 12
       },
       "background": "#e2dfe7",
-      "corner_radius": 6,
       "hover": {
         "background": "#8b87921f"
       },
@@ -1342,14 +1364,21 @@
         "color": "#585260",
         "size": 14,
         "margin": {
+          "left": 8,
           "right": 6
         }
       },
+      "guests": {
+        "margin": {
+          "left": 8,
+          "right": 8
+        }
+      },
       "padding": {
-        "left": 8
+        "left": 12,
+        "right": 12
       },
       "background": "#e2dfe7",
-      "corner_radius": 6,
       "hover": {
         "background": "#8b87921f"
       },

assets/themes/dark.json 🔗

@@ -989,9 +989,7 @@
   "chat_panel": {
     "padding": {
       "top": 12,
-      "left": 12,
-      "bottom": 12,
-      "right": 12
+      "bottom": 12
     },
     "channel_name": {
       "family": "Zed Sans",
@@ -1208,9 +1206,7 @@
   "contacts_panel": {
     "padding": {
       "top": 12,
-      "left": 12,
-      "bottom": 12,
-      "right": 12
+      "bottom": 12
     },
     "user_query_editor": {
       "background": "#000000",
@@ -1238,23 +1234,33 @@
         "left": 8,
         "right": 8,
         "top": 4
+      },
+      "margin": {
+        "left": 12,
+        "right": 12
       }
     },
     "user_query_editor_height": 32,
     "add_contact_button": {
       "margin": {
-        "left": 6
+        "left": 6,
+        "right": 12
       },
       "color": "#c6c6c6",
       "button_width": 8,
       "icon_width": 8
     },
+    "row_height": 28,
     "header_row": {
       "family": "Zed Mono",
       "color": "#9c9c9c",
       "size": 14,
       "margin": {
-        "top": 8
+        "top": 14
+      },
+      "padding": {
+        "left": 12,
+        "right": 12
       },
       "active": {
         "family": "Zed Mono",
@@ -1265,15 +1271,23 @@
     },
     "contact_row": {
       "padding": {
-        "left": 8
+        "left": 12,
+        "right": 12
       },
       "active": {
         "background": "#1c1c1c"
       }
     },
-    "row_height": 28,
-    "tree_branch_color": "#404040",
-    "tree_branch_width": 1,
+    "tree_branch": {
+      "color": "#000000",
+      "width": 1,
+      "hover": {
+        "color": "#000000"
+      },
+      "active": {
+        "color": "#000000"
+      }
+    },
     "contact_avatar": {
       "corner_radius": 10,
       "width": 18
@@ -1282,10 +1296,11 @@
       "family": "Zed Mono",
       "color": "#f1f1f1",
       "size": 14,
-      "padding": {
+      "margin": {
         "left": 8
       }
     },
+    "contact_button_spacing": 8,
     "contact_button": {
       "background": "#2b2b2b",
       "color": "#c6c6c6",
@@ -1315,14 +1330,21 @@
         "color": "#9c9c9c",
         "size": 14,
         "margin": {
+          "left": 8,
           "right": 6
         }
       },
+      "guests": {
+        "margin": {
+          "left": 8,
+          "right": 8
+        }
+      },
       "padding": {
-        "left": 8
+        "left": 12,
+        "right": 12
       },
       "background": "#1c1c1c",
-      "corner_radius": 6,
       "hover": {
         "background": "#232323"
       },
@@ -1342,14 +1364,21 @@
         "color": "#9c9c9c",
         "size": 14,
         "margin": {
+          "left": 8,
           "right": 6
         }
       },
+      "guests": {
+        "margin": {
+          "left": 8,
+          "right": 8
+        }
+      },
       "padding": {
-        "left": 8
+        "left": 12,
+        "right": 12
       },
       "background": "#1c1c1c",
-      "corner_radius": 6,
       "hover": {
         "background": "#232323"
       },

assets/themes/light.json 🔗

@@ -989,9 +989,7 @@
   "chat_panel": {
     "padding": {
       "top": 12,
-      "left": 12,
-      "bottom": 12,
-      "right": 12
+      "bottom": 12
     },
     "channel_name": {
       "family": "Zed Sans",
@@ -1208,9 +1206,7 @@
   "contacts_panel": {
     "padding": {
       "top": 12,
-      "left": 12,
-      "bottom": 12,
-      "right": 12
+      "bottom": 12
     },
     "user_query_editor": {
       "background": "#ffffff",
@@ -1238,23 +1234,33 @@
         "left": 8,
         "right": 8,
         "top": 4
+      },
+      "margin": {
+        "left": 12,
+        "right": 12
       }
     },
     "user_query_editor_height": 32,
     "add_contact_button": {
       "margin": {
-        "left": 6
+        "left": 6,
+        "right": 12
       },
       "color": "#393939",
       "button_width": 8,
       "icon_width": 8
     },
+    "row_height": 28,
     "header_row": {
       "family": "Zed Mono",
       "color": "#474747",
       "size": 14,
       "margin": {
-        "top": 8
+        "top": 14
+      },
+      "padding": {
+        "left": 12,
+        "right": 12
       },
       "active": {
         "family": "Zed Mono",
@@ -1265,15 +1271,23 @@
     },
     "contact_row": {
       "padding": {
-        "left": 8
+        "left": 12,
+        "right": 12
       },
       "active": {
         "background": "#d5d5d5"
       }
     },
-    "row_height": 28,
-    "tree_branch_color": "#e3e3e3",
-    "tree_branch_width": 1,
+    "tree_branch": {
+      "color": "#b8b8b8",
+      "width": 1,
+      "hover": {
+        "color": "#b8b8b8"
+      },
+      "active": {
+        "color": "#b8b8b8"
+      }
+    },
     "contact_avatar": {
       "corner_radius": 10,
       "width": 18
@@ -1282,10 +1296,11 @@
       "family": "Zed Mono",
       "color": "#2b2b2b",
       "size": 14,
-      "padding": {
+      "margin": {
         "left": 8
       }
     },
+    "contact_button_spacing": 8,
     "contact_button": {
       "background": "#eaeaea",
       "color": "#393939",
@@ -1315,14 +1330,21 @@
         "color": "#474747",
         "size": 14,
         "margin": {
+          "left": 8,
           "right": 6
         }
       },
+      "guests": {
+        "margin": {
+          "left": 8,
+          "right": 8
+        }
+      },
       "padding": {
-        "left": 8
+        "left": 12,
+        "right": 12
       },
       "background": "#f8f8f8",
-      "corner_radius": 6,
       "hover": {
         "background": "#eaeaea"
       },
@@ -1342,14 +1364,21 @@
         "color": "#474747",
         "size": 14,
         "margin": {
+          "left": 8,
           "right": 6
         }
       },
+      "guests": {
+        "margin": {
+          "left": 8,
+          "right": 8
+        }
+      },
       "padding": {
-        "left": 8
+        "left": 12,
+        "right": 12
       },
       "background": "#f8f8f8",
-      "corner_radius": 6,
       "hover": {
         "background": "#eaeaea"
       },

assets/themes/solarized-dark.json 🔗

@@ -989,9 +989,7 @@
   "chat_panel": {
     "padding": {
       "top": 12,
-      "left": 12,
-      "bottom": 12,
-      "right": 12
+      "bottom": 12
     },
     "channel_name": {
       "family": "Zed Sans",
@@ -1208,9 +1206,7 @@
   "contacts_panel": {
     "padding": {
       "top": 12,
-      "left": 12,
-      "bottom": 12,
-      "right": 12
+      "bottom": 12
     },
     "user_query_editor": {
       "background": "#002b36",
@@ -1238,23 +1234,33 @@
         "left": 8,
         "right": 8,
         "top": 4
+      },
+      "margin": {
+        "left": 12,
+        "right": 12
       }
     },
     "user_query_editor_height": 32,
     "add_contact_button": {
       "margin": {
-        "left": 6
+        "left": 6,
+        "right": 12
       },
       "color": "#eee8d5",
       "button_width": 8,
       "icon_width": 8
     },
+    "row_height": 28,
     "header_row": {
       "family": "Zed Mono",
       "color": "#93a1a1",
       "size": 14,
       "margin": {
-        "top": 8
+        "top": 14
+      },
+      "padding": {
+        "left": 12,
+        "right": 12
       },
       "active": {
         "family": "Zed Mono",
@@ -1265,15 +1271,23 @@
     },
     "contact_row": {
       "padding": {
-        "left": 8
+        "left": 12,
+        "right": 12
       },
       "active": {
         "background": "#586e755c"
       }
     },
-    "row_height": 28,
-    "tree_branch_color": "#657b83",
-    "tree_branch_width": 1,
+    "tree_branch": {
+      "color": "#657b83",
+      "width": 1,
+      "hover": {
+        "color": "#657b83"
+      },
+      "active": {
+        "color": "#657b83"
+      }
+    },
     "contact_avatar": {
       "corner_radius": 10,
       "width": 18
@@ -1282,10 +1296,11 @@
       "family": "Zed Mono",
       "color": "#eee8d5",
       "size": 14,
-      "padding": {
+      "margin": {
         "left": 8
       }
     },
+    "contact_button_spacing": 8,
     "contact_button": {
       "background": "#073642",
       "color": "#eee8d5",
@@ -1315,14 +1330,21 @@
         "color": "#93a1a1",
         "size": 14,
         "margin": {
+          "left": 8,
           "right": 6
         }
       },
+      "guests": {
+        "margin": {
+          "left": 8,
+          "right": 8
+        }
+      },
       "padding": {
-        "left": 8
+        "left": 12,
+        "right": 12
       },
       "background": "#073642",
-      "corner_radius": 6,
       "hover": {
         "background": "#586e753d"
       },
@@ -1342,14 +1364,21 @@
         "color": "#93a1a1",
         "size": 14,
         "margin": {
+          "left": 8,
           "right": 6
         }
       },
+      "guests": {
+        "margin": {
+          "left": 8,
+          "right": 8
+        }
+      },
       "padding": {
-        "left": 8
+        "left": 12,
+        "right": 12
       },
       "background": "#073642",
-      "corner_radius": 6,
       "hover": {
         "background": "#586e753d"
       },

assets/themes/solarized-light.json 🔗

@@ -989,9 +989,7 @@
   "chat_panel": {
     "padding": {
       "top": 12,
-      "left": 12,
-      "bottom": 12,
-      "right": 12
+      "bottom": 12
     },
     "channel_name": {
       "family": "Zed Sans",
@@ -1208,9 +1206,7 @@
   "contacts_panel": {
     "padding": {
       "top": 12,
-      "left": 12,
-      "bottom": 12,
-      "right": 12
+      "bottom": 12
     },
     "user_query_editor": {
       "background": "#fdf6e3",
@@ -1238,23 +1234,33 @@
         "left": 8,
         "right": 8,
         "top": 4
+      },
+      "margin": {
+        "left": 12,
+        "right": 12
       }
     },
     "user_query_editor_height": 32,
     "add_contact_button": {
       "margin": {
-        "left": 6
+        "left": 6,
+        "right": 12
       },
       "color": "#073642",
       "button_width": 8,
       "icon_width": 8
     },
+    "row_height": 28,
     "header_row": {
       "family": "Zed Mono",
       "color": "#586e75",
       "size": 14,
       "margin": {
-        "top": 8
+        "top": 14
+      },
+      "padding": {
+        "left": 12,
+        "right": 12
       },
       "active": {
         "family": "Zed Mono",
@@ -1265,15 +1271,23 @@
     },
     "contact_row": {
       "padding": {
-        "left": 8
+        "left": 12,
+        "right": 12
       },
       "active": {
         "background": "#93a1a12e"
       }
     },
-    "row_height": 28,
-    "tree_branch_color": "#839496",
-    "tree_branch_width": 1,
+    "tree_branch": {
+      "color": "#839496",
+      "width": 1,
+      "hover": {
+        "color": "#839496"
+      },
+      "active": {
+        "color": "#839496"
+      }
+    },
     "contact_avatar": {
       "corner_radius": 10,
       "width": 18
@@ -1282,10 +1296,11 @@
       "family": "Zed Mono",
       "color": "#073642",
       "size": 14,
-      "padding": {
+      "margin": {
         "left": 8
       }
     },
+    "contact_button_spacing": 8,
     "contact_button": {
       "background": "#eee8d5",
       "color": "#073642",
@@ -1315,14 +1330,21 @@
         "color": "#586e75",
         "size": 14,
         "margin": {
+          "left": 8,
           "right": 6
         }
       },
+      "guests": {
+        "margin": {
+          "left": 8,
+          "right": 8
+        }
+      },
       "padding": {
-        "left": 8
+        "left": 12,
+        "right": 12
       },
       "background": "#eee8d5",
-      "corner_radius": 6,
       "hover": {
         "background": "#93a1a11f"
       },
@@ -1342,14 +1364,21 @@
         "color": "#586e75",
         "size": 14,
         "margin": {
+          "left": 8,
           "right": 6
         }
       },
+      "guests": {
+        "margin": {
+          "left": 8,
+          "right": 8
+        }
+      },
       "padding": {
-        "left": 8
+        "left": 12,
+        "right": 12
       },
       "background": "#eee8d5",
-      "corner_radius": 6,
       "hover": {
         "background": "#93a1a11f"
       },

assets/themes/sulphurpool-dark.json 🔗

@@ -989,9 +989,7 @@
   "chat_panel": {
     "padding": {
       "top": 12,
-      "left": 12,
-      "bottom": 12,
-      "right": 12
+      "bottom": 12
     },
     "channel_name": {
       "family": "Zed Sans",
@@ -1208,9 +1206,7 @@
   "contacts_panel": {
     "padding": {
       "top": 12,
-      "left": 12,
-      "bottom": 12,
-      "right": 12
+      "bottom": 12
     },
     "user_query_editor": {
       "background": "#202746",
@@ -1238,23 +1234,33 @@
         "left": 8,
         "right": 8,
         "top": 4
+      },
+      "margin": {
+        "left": 12,
+        "right": 12
       }
     },
     "user_query_editor_height": 32,
     "add_contact_button": {
       "margin": {
-        "left": 6
+        "left": 6,
+        "right": 12
       },
       "color": "#dfe2f1",
       "button_width": 8,
       "icon_width": 8
     },
+    "row_height": 28,
     "header_row": {
       "family": "Zed Mono",
       "color": "#979db4",
       "size": 14,
       "margin": {
-        "top": 8
+        "top": 14
+      },
+      "padding": {
+        "left": 12,
+        "right": 12
       },
       "active": {
         "family": "Zed Mono",
@@ -1265,15 +1271,23 @@
     },
     "contact_row": {
       "padding": {
-        "left": 8
+        "left": 12,
+        "right": 12
       },
       "active": {
         "background": "#5e66875c"
       }
     },
-    "row_height": 28,
-    "tree_branch_color": "#6b7394",
-    "tree_branch_width": 1,
+    "tree_branch": {
+      "color": "#6b7394",
+      "width": 1,
+      "hover": {
+        "color": "#6b7394"
+      },
+      "active": {
+        "color": "#6b7394"
+      }
+    },
     "contact_avatar": {
       "corner_radius": 10,
       "width": 18
@@ -1282,10 +1296,11 @@
       "family": "Zed Mono",
       "color": "#dfe2f1",
       "size": 14,
-      "padding": {
+      "margin": {
         "left": 8
       }
     },
+    "contact_button_spacing": 8,
     "contact_button": {
       "background": "#293256",
       "color": "#dfe2f1",
@@ -1315,14 +1330,21 @@
         "color": "#979db4",
         "size": 14,
         "margin": {
+          "left": 8,
           "right": 6
         }
       },
+      "guests": {
+        "margin": {
+          "left": 8,
+          "right": 8
+        }
+      },
       "padding": {
-        "left": 8
+        "left": 12,
+        "right": 12
       },
       "background": "#293256",
-      "corner_radius": 6,
       "hover": {
         "background": "#5e66873d"
       },
@@ -1342,14 +1364,21 @@
         "color": "#979db4",
         "size": 14,
         "margin": {
+          "left": 8,
           "right": 6
         }
       },
+      "guests": {
+        "margin": {
+          "left": 8,
+          "right": 8
+        }
+      },
       "padding": {
-        "left": 8
+        "left": 12,
+        "right": 12
       },
       "background": "#293256",
-      "corner_radius": 6,
       "hover": {
         "background": "#5e66873d"
       },

assets/themes/sulphurpool-light.json 🔗

@@ -989,9 +989,7 @@
   "chat_panel": {
     "padding": {
       "top": 12,
-      "left": 12,
-      "bottom": 12,
-      "right": 12
+      "bottom": 12
     },
     "channel_name": {
       "family": "Zed Sans",
@@ -1208,9 +1206,7 @@
   "contacts_panel": {
     "padding": {
       "top": 12,
-      "left": 12,
-      "bottom": 12,
-      "right": 12
+      "bottom": 12
     },
     "user_query_editor": {
       "background": "#f5f7ff",
@@ -1238,23 +1234,33 @@
         "left": 8,
         "right": 8,
         "top": 4
+      },
+      "margin": {
+        "left": 12,
+        "right": 12
       }
     },
     "user_query_editor_height": 32,
     "add_contact_button": {
       "margin": {
-        "left": 6
+        "left": 6,
+        "right": 12
       },
       "color": "#293256",
       "button_width": 8,
       "icon_width": 8
     },
+    "row_height": 28,
     "header_row": {
       "family": "Zed Mono",
       "color": "#5e6687",
       "size": 14,
       "margin": {
-        "top": 8
+        "top": 14
+      },
+      "padding": {
+        "left": 12,
+        "right": 12
       },
       "active": {
         "family": "Zed Mono",
@@ -1265,15 +1271,23 @@
     },
     "contact_row": {
       "padding": {
-        "left": 8
+        "left": 12,
+        "right": 12
       },
       "active": {
         "background": "#979db42e"
       }
     },
-    "row_height": 28,
-    "tree_branch_color": "#898ea4",
-    "tree_branch_width": 1,
+    "tree_branch": {
+      "color": "#898ea4",
+      "width": 1,
+      "hover": {
+        "color": "#898ea4"
+      },
+      "active": {
+        "color": "#898ea4"
+      }
+    },
     "contact_avatar": {
       "corner_radius": 10,
       "width": 18
@@ -1282,10 +1296,11 @@
       "family": "Zed Mono",
       "color": "#293256",
       "size": 14,
-      "padding": {
+      "margin": {
         "left": 8
       }
     },
+    "contact_button_spacing": 8,
     "contact_button": {
       "background": "#dfe2f1",
       "color": "#293256",
@@ -1315,14 +1330,21 @@
         "color": "#5e6687",
         "size": 14,
         "margin": {
+          "left": 8,
           "right": 6
         }
       },
+      "guests": {
+        "margin": {
+          "left": 8,
+          "right": 8
+        }
+      },
       "padding": {
-        "left": 8
+        "left": 12,
+        "right": 12
       },
       "background": "#dfe2f1",
-      "corner_radius": 6,
       "hover": {
         "background": "#979db41f"
       },
@@ -1342,14 +1364,21 @@
         "color": "#5e6687",
         "size": 14,
         "margin": {
+          "left": 8,
           "right": 6
         }
       },
+      "guests": {
+        "margin": {
+          "left": 8,
+          "right": 8
+        }
+      },
       "padding": {
-        "left": 8
+        "left": 12,
+        "right": 12
       },
       "background": "#dfe2f1",
-      "corner_radius": 6,
       "hover": {
         "background": "#979db41f"
       },

crates/contacts_panel/src/contacts_panel.rs 🔗

@@ -186,6 +186,7 @@ impl ContactsPanel {
                 .with_style(theme.contact_username.container)
                 .aligned()
                 .left()
+                .flex(1., true)
                 .boxed(),
             )
             .constrained()
@@ -211,6 +212,13 @@ impl ContactsPanel {
     ) -> ElementBox {
         let project = &contact.projects[project_ix];
         let project_id = project.id;
+        let is_host = Some(contact.user.id) == current_user_id;
+        let is_guest = !is_host
+            && project
+                .guests
+                .iter()
+                .any(|guest| Some(guest.id) == current_user_id);
+        let is_shared = project.is_shared;
 
         let font_cache = cx.font_cache();
         let host_avatar_height = theme
@@ -219,120 +227,103 @@ impl ContactsPanel {
             .or(theme.contact_avatar.height)
             .unwrap_or(0.);
         let row = &theme.unshared_project_row.default;
+        let tree_branch = theme.tree_branch.clone();
         let line_height = row.name.text.line_height(font_cache);
         let cap_height = row.name.text.cap_height(font_cache);
         let baseline_offset =
-            row.name.text.baseline_offset(font_cache) + (row.height - line_height) / 2.;
-        let tree_branch_width = theme.tree_branch_width;
-        let tree_branch_color = theme.tree_branch_color;
+            row.name.text.baseline_offset(font_cache) + (theme.row_height - line_height) / 2.;
 
-        Flex::row()
-            .with_child(
-                Canvas::new(move |bounds, _, cx| {
-                    let start_x = bounds.min_x() + (bounds.width() / 2.) - (tree_branch_width / 2.);
-                    let end_x = bounds.max_x();
-                    let start_y = bounds.min_y();
-                    let end_y = bounds.min_y() + baseline_offset - (cap_height / 2.);
-
-                    cx.scene.push_quad(gpui::Quad {
-                        bounds: RectF::from_points(
-                            vec2f(start_x, start_y),
-                            vec2f(
-                                start_x + tree_branch_width,
-                                if is_last_project {
-                                    end_y
-                                } else {
-                                    bounds.max_y()
-                                },
-                            ),
-                        ),
-                        background: Some(tree_branch_color),
-                        border: gpui::Border::default(),
-                        corner_radius: 0.,
-                    });
-                    cx.scene.push_quad(gpui::Quad {
-                        bounds: RectF::from_points(
-                            vec2f(start_x, end_y),
-                            vec2f(end_x, end_y + tree_branch_width),
-                        ),
-                        background: Some(tree_branch_color),
-                        border: gpui::Border::default(),
-                        corner_radius: 0.,
-                    });
-                })
-                .constrained()
-                .with_width(host_avatar_height)
-                .boxed(),
-            )
-            .with_child({
-                let is_host = Some(contact.user.id) == current_user_id;
-                let is_guest = !is_host
-                    && project
-                        .guests
-                        .iter()
-                        .any(|guest| Some(guest.id) == current_user_id);
-                let is_shared = project.is_shared;
-                let app_state = app_state.clone();
+        MouseEventHandler::new::<JoinProject, _, _>(project_id as usize, cx, |mouse_state, _| {
+            let tree_branch = *tree_branch.style_for(mouse_state, is_selected);
+            let row = if project.is_shared {
+                &theme.shared_project_row
+            } else {
+                &theme.unshared_project_row
+            }
+            .style_for(mouse_state, is_selected);
 
-                MouseEventHandler::new::<JoinProject, _, _>(
-                    project_id as usize,
-                    cx,
-                    |mouse_state, _| {
-                        let style = if project.is_shared {
-                            &theme.shared_project_row
-                        } else {
-                            &theme.unshared_project_row
-                        }
-                        .style_for(mouse_state, is_selected);
-                        Flex::row()
-                            .with_child(
-                                Label::new(
-                                    project.worktree_root_names.join(", "),
-                                    style.name.text.clone(),
-                                )
-                                .aligned()
-                                .left()
-                                .contained()
-                                .with_style(style.name.container)
-                                .boxed(),
-                            )
-                            .with_children(project.guests.iter().filter_map(|participant| {
-                                participant.avatar.clone().map(|avatar| {
-                                    Image::new(avatar)
-                                        .with_style(style.guest_avatar)
-                                        .aligned()
-                                        .left()
-                                        .contained()
-                                        .with_margin_right(style.guest_avatar_spacing)
-                                        .boxed()
-                                })
-                            }))
+            Flex::row()
+                .with_child(
+                    Canvas::new(move |bounds, _, cx| {
+                        let start_x =
+                            bounds.min_x() + (bounds.width() / 2.) - (tree_branch.width / 2.);
+                        let end_x = bounds.max_x();
+                        let start_y = bounds.min_y();
+                        let end_y = bounds.min_y() + baseline_offset - (cap_height / 2.);
+
+                        cx.scene.push_quad(gpui::Quad {
+                            bounds: RectF::from_points(
+                                vec2f(start_x, start_y),
+                                vec2f(
+                                    start_x + tree_branch.width,
+                                    if is_last_project {
+                                        end_y
+                                    } else {
+                                        bounds.max_y()
+                                    },
+                                ),
+                            ),
+                            background: Some(tree_branch.color),
+                            border: gpui::Border::default(),
+                            corner_radius: 0.,
+                        });
+                        cx.scene.push_quad(gpui::Quad {
+                            bounds: RectF::from_points(
+                                vec2f(start_x, end_y),
+                                vec2f(end_x, end_y + tree_branch.width),
+                            ),
+                            background: Some(tree_branch.color),
+                            border: gpui::Border::default(),
+                            corner_radius: 0.,
+                        });
+                    })
+                    .constrained()
+                    .with_width(host_avatar_height)
+                    .boxed(),
+                )
+                .with_child(
+                    Label::new(
+                        project.worktree_root_names.join(", "),
+                        row.name.text.clone(),
+                    )
+                    .aligned()
+                    .left()
+                    .contained()
+                    .with_style(row.name.container)
+                    .flex(1., false)
+                    .boxed(),
+                )
+                .with_children(project.guests.iter().filter_map(|participant| {
+                    participant.avatar.clone().map(|avatar| {
+                        Image::new(avatar)
+                            .with_style(row.guest_avatar)
+                            .aligned()
+                            .left()
                             .contained()
-                            .with_style(style.container)
-                            .constrained()
-                            .with_height(style.height)
+                            .with_margin_right(row.guest_avatar_spacing)
                             .boxed()
-                    },
-                )
-                .with_cursor_style(if !is_host && is_shared {
-                    CursorStyle::PointingHand
-                } else {
-                    CursorStyle::Arrow
-                })
-                .on_click(move |_, cx| {
-                    if !is_host && !is_guest {
-                        cx.dispatch_global_action(JoinProject {
-                            project_id,
-                            app_state: app_state.clone(),
-                        });
-                    }
-                })
-                .flex(1., true)
+                    })
+                }))
+                .constrained()
+                .with_height(theme.row_height)
+                .contained()
+                .with_style(row.container)
                 .boxed()
-            })
-            .constrained()
-            .with_height(row.height)
-            .boxed()
+        })
+        .with_cursor_style(if !is_host && is_shared {
+            CursorStyle::PointingHand
+        } else {
+            CursorStyle::Arrow
+        })
+        .on_click(move |_, cx| {
+            if !is_host && !is_guest {
+                cx.dispatch_global_action(JoinProject {
+                    project_id,
+                    app_state: app_state.clone(),
+                });
+            }
+        })
+        .boxed()
     }
 
     fn render_contact_request(
@@ -364,11 +355,13 @@ impl ContactsPanel {
                 .with_style(theme.contact_username.container)
                 .aligned()
                 .left()
+                .flex(1., true)
                 .boxed(),
             );
 
         let user_id = user.id;
         let is_contact_request_pending = user_store.read(cx).is_contact_request_pending(&user);
+        let button_spacing = theme.contact_button_spacing;
 
         if is_incoming {
             row.add_children([
@@ -380,7 +373,7 @@ impl ContactsPanel {
                     };
                     render_icon_button(button_style, "icons/reject.svg")
                         .aligned()
-                        .flex_float()
+                        // .flex_float()
                         .boxed()
                 })
                 .with_cursor_style(CursorStyle::PointingHand)
@@ -390,7 +383,9 @@ impl ContactsPanel {
                         accept: false,
                     })
                 })
-                .flex_float()
+                // .flex_float()
+                .contained()
+                .with_margin_right(button_spacing)
                 .boxed(),
                 MouseEventHandler::new::<Accept, _, _>(user.id as usize, cx, |mouse_state, _| {
                     let button_style = if is_contact_request_pending {

crates/theme/src/theme.rs 🔗

@@ -246,9 +246,15 @@ pub struct ContactsPanel {
     pub contact_avatar: ImageStyle,
     pub contact_username: ContainedText,
     pub contact_button: Interactive<IconButton>,
+    pub contact_button_spacing: f32,
     pub disabled_contact_button: IconButton,
-    pub tree_branch_width: f32,
-    pub tree_branch_color: Color,
+    pub tree_branch: Interactive<TreeBranch>,
+}
+
+#[derive(Deserialize, Default, Clone, Copy)]
+pub struct TreeBranch {
+    pub width: f32,
+    pub color: Color,
 }
 
 #[derive(Deserialize, Default)]
@@ -273,8 +279,8 @@ pub struct IconButton {
 pub struct ProjectRow {
     #[serde(flatten)]
     pub container: ContainerStyle,
-    pub height: f32,
     pub name: ContainedText,
+    pub guests: ContainerStyle,
     pub guest_avatar: ImageStyle,
     pub guest_avatar_spacing: f32,
 }

styles/src/styleTree/app.ts 🔗

@@ -12,7 +12,7 @@ import workspace from "./workspace";
 import projectDiagnostics from "./projectDiagnostics";
 
 export const panel = {
-  padding: { top: 12, left: 12, bottom: 12, right: 12 },
+  padding: { top: 12, bottom: 12 },
 };
 
 export default function app(theme: Theme): Object {

styles/src/styleTree/contactsPanel.ts 🔗

@@ -3,7 +3,10 @@ import { panel } from "./app";
 import { backgroundColor, border, borderColor, iconColor, player, text } from "./components";
 
 export default function contactsPanel(theme: Theme) {
-  const project = {
+  const nameMargin = 8;
+  const sidePadding = 12;
+
+  const projectRow = {
     guestAvatarSpacing: 4,
     height: 24,
     guestAvatar: {
@@ -13,11 +16,19 @@ export default function contactsPanel(theme: Theme) {
     name: {
       ...text(theme, "mono", "placeholder", { size: "sm" }),
       margin: {
+        left: nameMargin,
         right: 6,
       },
     },
+    guests: {
+      margin: {
+        left: nameMargin,
+        right: nameMargin,
+      }
+    },
     padding: {
-      left: 8,
+      left: sidePadding,
+      right: sidePadding,
     },
   };
 
@@ -44,41 +55,61 @@ export default function contactsPanel(theme: Theme) {
         right: 8,
         top: 4,
       },
+      margin: {
+        left: sidePadding,
+        right: sidePadding,
+      }
     },
     userQueryEditorHeight: 32,
     addContactButton: {
-      margin: { left: 6 },
+      margin: { left: 6, right: 12 },
       color: iconColor(theme, "primary"),
       buttonWidth: 8,
       iconWidth: 8,
     },
+    rowHeight: 28,
     headerRow: {
       ...text(theme, "mono", "secondary", { size: "sm" }),
-      margin: { top: 8 },
+      margin: { top: 14 },
+      padding: {
+        left: sidePadding,
+        right: sidePadding,
+      },
       active: {
         ...text(theme, "mono", "primary", { size: "sm" }),
         background: backgroundColor(theme, 100, "active"),
       }
     },
     contactRow: {
-      padding: { left: 8 },
+      padding: {
+        left: sidePadding,
+        right: sidePadding
+      },
       active: {
         background: backgroundColor(theme, 100, "active"),
       }
     },
-    rowHeight: 28,
-    treeBranchColor: borderColor(theme, "muted"),
-    treeBranchWidth: 1,
+    treeBranch: {
+      color: borderColor(theme, "active"),
+      width: 1,
+      hover: {
+        color: borderColor(theme, "active"),
+      },
+      active: {
+        color: borderColor(theme, "active"),
+      }
+    },
     contactAvatar: {
       cornerRadius: 10,
       width: 18,
     },
     contactUsername: {
       ...text(theme, "mono", "primary", { size: "sm" }),
-      padding: {
-        left: 8,
+      margin: {
+        left: nameMargin,
       },
     },
+    contactButtonSpacing: nameMargin,
     contactButton: {
       ...contactButton,
       hover: {
@@ -91,11 +122,10 @@ export default function contactsPanel(theme: Theme) {
       color: iconColor(theme, "muted"),
     },
     sharedProjectRow: {
-      ...project,
+      ...projectRow,
       background: backgroundColor(theme, 300),
-      cornerRadius: 6,
       name: {
-        ...project.name,
+        ...projectRow.name,
         ...text(theme, "mono", "secondary", { size: "sm" }),
       },
       hover: {
@@ -106,11 +136,10 @@ export default function contactsPanel(theme: Theme) {
       }
     },
     unsharedProjectRow: {
-      ...project,
+      ...projectRow,
       background: backgroundColor(theme, 300),
-      cornerRadius: 6,
       name: {
-        ...project.name,
+        ...projectRow.name,
         ...text(theme, "mono", "secondary", { size: "sm" }),
       },
       hover: {