From 9b37206147ec379bdc63515e9d700611f41ac46f Mon Sep 17 00:00:00 2001
From: Danilo Leal <67129314+danilo-leal@users.noreply.github.com>
Date: Tue, 29 Apr 2025 07:54:39 -0300
Subject: [PATCH] extensions_ui: Add design changes to expose the filters more
(#29582)
Closes https://github.com/zed-industries/zed/issues/28086
The main motivator for this change is to have the "MCP Servers" filter
more clearly visible. And because of this, all other filters end up more
visible, as they're not in a dropdown menu anymore. Ended up pushing
some other small changes here and there as well. This is our final
product:
Release Notes:
- N/A
---
.../src/components/extension_card.rs | 2 +-
.../src/components/feature_upsell.rs | 21 ++-
crates/extensions_ui/src/extensions_ui.rs | 154 ++++++++----------
3 files changed, 75 insertions(+), 102 deletions(-)
diff --git a/crates/extensions_ui/src/components/extension_card.rs b/crates/extensions_ui/src/components/extension_card.rs
index 75223df96fb8a16b8aedd59c9b3a931397ce7c50..326b0fc2668035be1bd30fa7b83066c94c468d30 100644
--- a/crates/extensions_ui/src/components/extension_card.rs
+++ b/crates/extensions_ui/src/components/extension_card.rs
@@ -40,7 +40,7 @@ impl RenderOnce for ExtensionCard {
.bg(cx.theme().colors().elevated_surface_background)
.border_1()
.border_color(cx.theme().colors().border)
- .rounded_sm()
+ .rounded_md()
.children(self.children)
.when(self.overridden_by_dev_extension, |card| {
card.child(
diff --git a/crates/extensions_ui/src/components/feature_upsell.rs b/crates/extensions_ui/src/components/feature_upsell.rs
index d2d86937ff624f077fbc25f95cce893d9bc69531..e2e65f1598ad1a22a3fe854425a32013115616d6 100644
--- a/crates/extensions_ui/src/components/feature_upsell.rs
+++ b/crates/extensions_ui/src/components/feature_upsell.rs
@@ -1,6 +1,6 @@
use gpui::{AnyElement, Div, StyleRefinement};
use smallvec::SmallVec;
-use ui::{ButtonLike, prelude::*};
+use ui::prelude::*;
#[derive(IntoElement)]
pub struct FeatureUpsell {
@@ -46,21 +46,20 @@ impl FeatureUpsell {
impl RenderOnce for FeatureUpsell {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
self.base
- .p_4()
+ .py_2()
+ .px_4()
.justify_between()
- .border_color(cx.theme().colors().border)
- .child(v_flex().overflow_hidden().child(Label::new(self.text)))
+ .flex_wrap()
+ .border_color(cx.theme().colors().border_variant)
+ .child(Label::new(self.text))
.child(h_flex().gap_2().children(self.children).when_some(
self.docs_url,
|el, docs_url| {
el.child(
- ButtonLike::new("open_docs")
- .child(
- h_flex()
- .gap_2()
- .child(Label::new("View docs"))
- .child(Icon::new(IconName::ArrowUpRight)),
- )
+ Button::new("open_docs", "View Documentation")
+ .icon(IconName::ArrowUpRight)
+ .icon_size(IconSize::XSmall)
+ .icon_position(IconPosition::End)
.on_click({
let docs_url = docs_url.clone();
move |_event, _window, cx| {
diff --git a/crates/extensions_ui/src/extensions_ui.rs b/crates/extensions_ui/src/extensions_ui.rs
index 430b656f09679bf5c0e21512615c3c447e001f8b..7aeb7cb309b315aeed161c83e311a79dc0d41dc5 100644
--- a/crates/extensions_ui/src/extensions_ui.rs
+++ b/crates/extensions_ui/src/extensions_ui.rs
@@ -165,7 +165,7 @@ fn extension_provides_label(provides: ExtensionProvides) -> &'static str {
ExtensionProvides::Languages => "Languages",
ExtensionProvides::Grammars => "Grammars",
ExtensionProvides::LanguageServers => "Language Servers",
- ExtensionProvides::ContextServers => "Context Servers",
+ ExtensionProvides::ContextServers => "MCP Servers",
ExtensionProvides::SlashCommands => "Slash Commands",
ExtensionProvides::IndexedDocsProviders => "Indexed Docs Providers",
ExtensionProvides::Snippets => "Snippets",
@@ -573,6 +573,7 @@ impl ExtensionsPage {
extension.authors.join(", ")
))
.size(LabelSize::Small)
+ .color(Color::Muted)
.truncate(),
)
.child(Label::new("<>").size(LabelSize::Small)),
@@ -594,7 +595,6 @@ impl ExtensionsPage {
)
.icon_color(Color::Accent)
.icon_size(IconSize::Small)
- .style(ButtonStyle::Filled)
.on_click(cx.listener({
let repository_url = repository_url.clone();
move |_, _, _, cx| {
@@ -701,6 +701,7 @@ impl ExtensionsPage {
extension.manifest.authors.join(", ")
))
.size(LabelSize::Small)
+ .color(Color::Muted)
.truncate(),
)
.child(
@@ -731,7 +732,6 @@ impl ExtensionsPage {
)
.icon_color(Color::Accent)
.icon_size(IconSize::Small)
- .style(ButtonStyle::Filled)
.on_click(cx.listener({
let repository_url = repository_url.clone();
move |_, _, _, cx| {
@@ -751,8 +751,7 @@ impl ExtensionsPage {
IconName::Ellipsis,
)
.icon_color(Color::Accent)
- .icon_size(IconSize::Small)
- .style(ButtonStyle::Filled),
+ .icon_size(IconSize::Small),
)
.menu(move |window, cx| {
Some(Self::render_remote_extension_context_menu(
@@ -950,19 +949,20 @@ impl ExtensionsPage {
cx.theme().colors().border
};
- h_flex().w_full().gap_2().key_context(key_context).child(
- h_flex()
- .flex_1()
- .px_2()
- .py_1()
- .gap_2()
- .border_1()
- .border_color(editor_border)
- .min_w(rems_from_px(384.))
- .rounded_lg()
- .child(Icon::new(IconName::MagnifyingGlass))
- .child(self.render_text_input(&self.query_editor, cx)),
- )
+ h_flex()
+ .key_context(key_context)
+ .h_8()
+ .flex_1()
+ .min_w(rems_from_px(384.))
+ .pl_1p5()
+ .pr_2()
+ .py_1()
+ .gap_2()
+ .border_1()
+ .border_color(editor_border)
+ .rounded_lg()
+ .child(Icon::new(IconName::MagnifyingGlass).color(Color::Muted))
+ .child(self.render_text_input(&self.query_editor, cx))
}
fn render_text_input(
@@ -1193,52 +1193,6 @@ impl ExtensionsPage {
upsell.when(ix < upsells_count, |upsell| upsell.border_b_1())
}))
}
-
- fn build_extension_provides_filter_menu(
- &self,
- window: &mut Window,
- cx: &mut Context,
- ) -> Entity {
- let this = cx.entity();
- ContextMenu::build(window, cx, |mut menu, _window, _cx| {
- menu = menu.header("Extension Category").toggleable_entry(
- "All",
- self.provides_filter.is_none(),
- IconPosition::End,
- None,
- {
- let this = this.clone();
- move |_window, cx| {
- this.update(cx, |this, cx| {
- this.change_provides_filter(None, cx);
- });
- }
- },
- );
-
- for provides in ExtensionProvides::iter() {
- let label = extension_provides_label(provides);
-
- menu = menu.toggleable_entry(
- label,
- self.provides_filter == Some(provides),
- IconPosition::End,
- None,
- {
- let this = this.clone();
- move |_window, cx| {
- this.update(cx, |this, cx| {
- this.change_provides_filter(Some(provides), cx);
- this.provides_filter = Some(provides);
- });
- }
- },
- )
- }
-
- menu
- })
- }
}
impl Render for ExtensionsPage {
@@ -1249,9 +1203,8 @@ impl Render for ExtensionsPage {
.child(
v_flex()
.gap_4()
- .p_4()
- .border_b_1()
- .border_color(cx.theme().colors().border)
+ .pt_4()
+ .px_4()
.bg(cx.theme().colors().editor_background)
.child(
h_flex()
@@ -1271,29 +1224,9 @@ impl Render for ExtensionsPage {
.child(
h_flex()
.w_full()
- .gap_2()
- .justify_between()
- .child(h_flex().gap_2().child(self.render_search(cx)).child({
- let this = cx.entity().clone();
- PopoverMenu::new("extension-provides-filter")
- .menu(move |window, cx| {
- Some(this.update(cx, |this, cx| {
- this.build_extension_provides_filter_menu(window, cx)
- }))
- })
- .trigger_with_tooltip(
- Button::new(
- "extension-provides-filter-button",
- self.provides_filter
- .map(extension_provides_label)
- .unwrap_or("All"),
- )
- .icon(IconName::Filter)
- .icon_position(IconPosition::Start),
- Tooltip::text("Filter extensions by category"),
- )
- .anchor(gpui::Corner::TopLeft)
- }))
+ .gap_4()
+ .flex_wrap()
+ .child(self.render_search(cx))
.child(
h_flex()
.child(
@@ -1343,6 +1276,47 @@ impl Render for ExtensionsPage {
),
),
)
+ .child(
+ h_flex()
+ .id("filter-row")
+ .gap_2()
+ .py_2p5()
+ .px_4()
+ .border_b_1()
+ .border_color(cx.theme().colors().border_variant)
+ .overflow_x_scroll()
+ .child(
+ Button::new("filter-all-categories", "All")
+ .when(self.provides_filter.is_none(), |button| {
+ button.style(ButtonStyle::Filled)
+ })
+ .when(self.provides_filter.is_some(), |button| {
+ button.style(ButtonStyle::Subtle)
+ })
+ .toggle_state(self.provides_filter.is_none())
+ .on_click(cx.listener(|this, _event, _, cx| {
+ this.change_provides_filter(None, cx);
+ })),
+ )
+ .children(ExtensionProvides::iter().map(|provides| {
+ let label = extension_provides_label(provides);
+ Button::new(
+ SharedString::from(format!("filter-category-{}", label)),
+ label,
+ )
+ .style(if self.provides_filter == Some(provides) {
+ ButtonStyle::Filled
+ } else {
+ ButtonStyle::Subtle
+ })
+ .toggle_state(self.provides_filter == Some(provides))
+ .on_click({
+ cx.listener(move |this, _event, _, cx| {
+ this.change_provides_filter(Some(provides), cx);
+ })
+ })
+ })),
+ )
.child(self.render_feature_upsells(cx))
.child(
v_flex()