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 75223df96f..326b0fc266 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 d2d86937ff..e2e65f1598 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 430b656f09..7aeb7cb309 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()