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}