title bar: Adjust the onboarding banner component API (#27455)

Danilo Leal created

This PR encapsulates the layout building of the Onboarding Banner
component inside of it, allowing to, at the call site, just pass an
icon, title, and subtitle. The `subtitle` parameter, by default, uses
the `Introducing:` label, which I think will be the one we'll use most
of the time for this specific component.

Release Notes:

- N/A

Change summary

crates/title_bar/src/onboarding_banner.rs | 53 +++++++++++++++++++-----
crates/title_bar/src/title_bar.rs         | 35 ++++------------
2 files changed, 51 insertions(+), 37 deletions(-)

Detailed changes

crates/title_bar/src/banner.rs → crates/title_bar/src/onboarding_banner.rs 🔗

@@ -1,9 +1,9 @@
-use gpui::{Action, Entity, Global, Render};
+use gpui::{Action, Entity, Global, Render, SharedString};
 use ui::{prelude::*, ButtonLike, Tooltip};
 use util::ResultExt;
 
-/// Prompts the user to try Zed's features
-pub struct Banner {
+/// Prompts the user to try newly released Zed's features
+pub struct OnboardingBanner {
     dismissed: bool,
     source: String,
     details: BannerDetails,
@@ -11,23 +11,37 @@ pub struct Banner {
 
 #[derive(Clone)]
 struct BannerGlobal {
-    entity: Entity<Banner>,
+    entity: Entity<OnboardingBanner>,
 }
 impl Global for BannerGlobal {}
 
 pub struct BannerDetails {
     pub action: Box<dyn Action>,
-    pub banner_label: Box<dyn Fn(&Window, &mut Context<Banner>) -> AnyElement>,
+    pub icon_name: IconName,
+    pub label: SharedString,
+    pub subtitle: Option<SharedString>,
 }
 
-impl Banner {
-    pub fn new(source: &str, details: BannerDetails, cx: &mut Context<Self>) -> Self {
+impl OnboardingBanner {
+    pub fn new(
+        source: &str,
+        icon_name: IconName,
+        label: impl Into<SharedString>,
+        subtitle: Option<SharedString>,
+        action: Box<dyn Action>,
+        cx: &mut Context<Self>,
+    ) -> Self {
         cx.set_global(BannerGlobal {
             entity: cx.entity(),
         });
         Self {
             source: source.to_string(),
-            details,
+            details: BannerDetails {
+                action,
+                icon_name,
+                label: label.into(),
+                subtitle: subtitle.or(Some(SharedString::from("Introducing:"))),
+            },
             dismissed: get_dismissed(source),
         }
     }
@@ -90,8 +104,8 @@ pub fn restore_banner(cx: &mut App) {
         .detach_and_log_err(cx);
 }
 
-impl Render for Banner {
-    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
+impl Render for OnboardingBanner {
+    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
         if !self.should_show(cx) {
             return div();
         }
@@ -103,7 +117,24 @@ impl Render for Banner {
             .border_color(border_color)
             .child(
                 ButtonLike::new("try-a-feature")
-                    .child((self.details.banner_label)(window, cx))
+                    .child(
+                        h_flex()
+                            .h_full()
+                            .gap_1()
+                            .child(Icon::new(self.details.icon_name).size(IconSize::Small))
+                            .child(
+                                h_flex()
+                                    .gap_0p5()
+                                    .when_some(self.details.subtitle.as_ref(), |this, subtitle| {
+                                        this.child(
+                                            Label::new(subtitle)
+                                                .size(LabelSize::Small)
+                                                .color(Color::Muted),
+                                        )
+                                    })
+                                    .child(Label::new(&self.details.label).size(LabelSize::Small)),
+                            ),
+                    )
                     .on_click(cx.listener(|this, _, window, cx| {
                         telemetry::event!("Banner Clicked", source = this.source);
                         this.dismiss(cx);

crates/title_bar/src/title_bar.rs 🔗

@@ -1,6 +1,6 @@
 mod application_menu;
-mod banner;
 mod collab;
+mod onboarding_banner;
 mod platforms;
 mod window_controls;
 
@@ -16,7 +16,6 @@ use crate::application_menu::{
 
 use crate::platforms::{platform_linux, platform_mac, platform_windows};
 use auto_update::AutoUpdateStatus;
-use banner::{Banner, BannerDetails};
 use call::ActiveCall;
 use client::{Client, UserStore};
 use feature_flags::{FeatureFlagAppExt, ZedPro};
@@ -25,6 +24,7 @@ use gpui::{
     InteractiveElement, Interactivity, IntoElement, MouseButton, ParentElement, Render, Stateful,
     StatefulInteractiveElement, Styled, Subscription, WeakEntity, Window,
 };
+use onboarding_banner::OnboardingBanner;
 use project::Project;
 use rpc::proto;
 use settings::Settings as _;
@@ -39,7 +39,7 @@ use util::ResultExt;
 use workspace::{notifications::NotifyResultExt, Workspace};
 use zed_actions::{OpenBrowser, OpenRecent, OpenRemote};
 
-pub use banner::restore_banner;
+pub use onboarding_banner::restore_banner;
 
 #[cfg(feature = "stories")]
 pub use stories::*;
@@ -128,7 +128,7 @@ pub struct TitleBar {
     should_move: bool,
     application_menu: Option<Entity<ApplicationMenu>>,
     _subscriptions: Vec<Subscription>,
-    banner: Entity<Banner>,
+    banner: Entity<OnboardingBanner>,
 }
 
 impl Render for TitleBar {
@@ -316,29 +316,12 @@ impl TitleBar {
         subscriptions.push(cx.observe(&user_store, |_, _, cx| cx.notify()));
 
         let banner = cx.new(|cx| {
-            Banner::new(
+            OnboardingBanner::new(
                 "Git Onboarding",
-                BannerDetails {
-                    action: zed_actions::OpenGitIntegrationOnboarding.boxed_clone(),
-                    banner_label: Box::new(|_, _| {
-                        h_flex()
-                            .h_full()
-                            .items_center()
-                            .gap_1()
-                            .child(Icon::new(IconName::GitBranchSmall).size(IconSize::Small))
-                            .child(
-                                h_flex()
-                                    .gap_0p5()
-                                    .child(
-                                        Label::new("Introducing:")
-                                            .size(LabelSize::Small)
-                                            .color(Color::Muted),
-                                    )
-                                    .child(Label::new("Git Support").size(LabelSize::Small)),
-                            )
-                            .into_any_element()
-                    }),
-                },
+                IconName::GitBranchSmall,
+                "Git Support",
+                None,
+                zed_actions::OpenGitIntegrationOnboarding.boxed_clone(),
                 cx,
             )
         });