announcement_toast.rs

  1use crate::{ListBulletItem, Vector, VectorName, prelude::*};
  2use component::{Component, ComponentScope, example_group, single_example};
  3use gpui::{
  4    AnyElement, ClickEvent, IntoElement, ParentElement, SharedString, linear_color_stop,
  5    linear_gradient,
  6};
  7use smallvec::SmallVec;
  8
  9#[derive(IntoElement, RegisterComponent)]
 10pub struct AnnouncementToast {
 11    illustration: Option<AnyElement>,
 12    heading: Option<SharedString>,
 13    description: Option<SharedString>,
 14    bullet_items: SmallVec<[AnyElement; 6]>,
 15    primary_action_label: SharedString,
 16    primary_on_click: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
 17    secondary_action_label: SharedString,
 18    secondary_on_click: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
 19    dismiss_on_click: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
 20}
 21
 22impl AnnouncementToast {
 23    pub fn new() -> Self {
 24        Self {
 25            illustration: None,
 26            heading: None,
 27            description: None,
 28            bullet_items: SmallVec::new(),
 29            primary_action_label: "Learn More".into(),
 30            primary_on_click: Box::new(|_, _, _| {}),
 31            secondary_action_label: "View Release Notes".into(),
 32            secondary_on_click: Box::new(|_, _, _| {}),
 33            dismiss_on_click: Box::new(|_, _, _| {}),
 34        }
 35    }
 36
 37    pub fn illustration(mut self, illustration: impl IntoElement) -> Self {
 38        self.illustration = Some(illustration.into_any_element());
 39        self
 40    }
 41
 42    pub fn heading(mut self, heading: impl Into<SharedString>) -> Self {
 43        self.heading = Some(heading.into());
 44        self
 45    }
 46
 47    pub fn description(mut self, description: impl Into<SharedString>) -> Self {
 48        self.description = Some(description.into());
 49        self
 50    }
 51
 52    pub fn bullet_item(mut self, item: impl IntoElement) -> Self {
 53        self.bullet_items.push(item.into_any_element());
 54        self
 55    }
 56
 57    pub fn bullet_items(mut self, items: impl IntoIterator<Item = impl IntoElement>) -> Self {
 58        self.bullet_items
 59            .extend(items.into_iter().map(IntoElement::into_any_element));
 60        self
 61    }
 62
 63    pub fn primary_action_label(mut self, primary_action_label: impl Into<SharedString>) -> Self {
 64        self.primary_action_label = primary_action_label.into();
 65        self
 66    }
 67
 68    pub fn primary_on_click(
 69        mut self,
 70        handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
 71    ) -> Self {
 72        self.primary_on_click = Box::new(handler);
 73        self
 74    }
 75
 76    pub fn secondary_action_label(
 77        mut self,
 78        secondary_action_label: impl Into<SharedString>,
 79    ) -> Self {
 80        self.secondary_action_label = secondary_action_label.into();
 81        self
 82    }
 83
 84    pub fn secondary_on_click(
 85        mut self,
 86        handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
 87    ) -> Self {
 88        self.secondary_on_click = Box::new(handler);
 89        self
 90    }
 91
 92    pub fn dismiss_on_click(
 93        mut self,
 94        handler: impl Fn(&ClickEvent, &mut Window, &mut App) + 'static,
 95    ) -> Self {
 96        self.dismiss_on_click = Box::new(handler);
 97        self
 98    }
 99}
100
101impl RenderOnce for AnnouncementToast {
102    fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
103        let has_illustration = self.illustration.is_some();
104        let illustration = self.illustration;
105
106        v_flex()
107            .relative()
108            .w_full()
109            .elevation_3(cx)
110            .when_some(illustration, |this, i| this.child(i))
111            .child(
112                v_flex()
113                    .p_4()
114                    .gap_4()
115                    .when(has_illustration, |s| {
116                        s.border_t_1()
117                            .border_color(cx.theme().colors().border_variant)
118                    })
119                    .child(
120                        v_flex()
121                            .min_w_0()
122                            .when_some(self.heading, |this, heading| {
123                                this.child(Headline::new(heading).size(HeadlineSize::Small))
124                            })
125                            .when_some(self.description, |this, description| {
126                                this.child(Label::new(description).color(Color::Muted))
127                            }),
128                    )
129                    .when(!self.bullet_items.is_empty(), |this| {
130                        this.child(v_flex().min_w_0().gap_1().children(self.bullet_items))
131                    })
132                    .child(
133                        v_flex()
134                            .gap_1()
135                            .child(
136                                Button::new("try-now", self.primary_action_label)
137                                    .style(ButtonStyle::Outlined)
138                                    .full_width()
139                                    .on_click(self.primary_on_click),
140                            )
141                            .child(
142                                Button::new("release-notes", self.secondary_action_label)
143                                    .full_width()
144                                    .on_click(self.secondary_on_click),
145                            ),
146                    ),
147            )
148            .child(
149                div().absolute().top_1().right_1().child(
150                    IconButton::new("dismiss", IconName::Close)
151                        .icon_size(IconSize::Small)
152                        .on_click(self.dismiss_on_click),
153                ),
154            )
155    }
156}
157
158impl Component for AnnouncementToast {
159    fn scope() -> ComponentScope {
160        ComponentScope::Notification
161    }
162
163    fn description() -> Option<&'static str> {
164        Some("A special toast for announcing new and exciting features.")
165    }
166
167    fn preview(_window: &mut Window, cx: &mut App) -> Option<AnyElement> {
168        let illustration = h_flex()
169            .relative()
170            .h(rems_from_px(126.))
171            .bg(cx.theme().colors().editor_background)
172            .justify_center()
173            .gap_8()
174            .rounded_t_md()
175            .overflow_hidden()
176            .child(
177                div().absolute().inset_0().w(px(515.)).h(px(126.)).child(
178                    Vector::new(VectorName::AcpGrid, rems_from_px(515.), rems_from_px(126.))
179                        .color(Color::Custom(cx.theme().colors().text.opacity(0.02))),
180                ),
181            )
182            .child(div().absolute().inset_0().size_full().bg(linear_gradient(
183                0.,
184                linear_color_stop(
185                    cx.theme().colors().elevated_surface_background.opacity(0.1),
186                    0.9,
187                ),
188                linear_color_stop(
189                    cx.theme().colors().elevated_surface_background.opacity(0.),
190                    0.,
191                ),
192            )))
193            .child(
194                div()
195                    .absolute()
196                    .inset_0()
197                    .size_full()
198                    .bg(gpui::black().opacity(0.15)),
199            )
200            .child(
201                Vector::new(
202                    VectorName::AcpLogoSerif,
203                    rems_from_px(257.),
204                    rems_from_px(47.),
205                )
206                .color(Color::Custom(cx.theme().colors().text.opacity(0.8))),
207            );
208
209        let examples = vec![single_example(
210            "Basic",
211            div().w_80().child(
212                AnnouncementToast::new()
213                    .illustration(illustration)
214                    .heading("What's new in Zed")
215                    .description(
216                        "This version comes in with some changes to the workspace for a better experience.",
217                    )
218                    .bullet_item(ListBulletItem::new("Improved agent performance"))
219                    .bullet_item(ListBulletItem::new("New agentic features"))
220                    .bullet_item(ListBulletItem::new("Better agent capabilities"))
221
222            )
223            .into_any_element(),
224        )];
225
226        Some(
227            v_flex()
228                .gap_6()
229                .child(example_group(examples).vertical())
230                .into_any_element(),
231        )
232    }
233}