1use crate::prelude::*;
2use gpui::ClickEvent;
3
4#[derive(IntoElement, RegisterComponent)]
5pub struct Callout {
6 title: SharedString,
7 message: Option<SharedString>,
8 icon: Icon,
9 cta_label: SharedString,
10 cta_action: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
11 line_height: Option<Pixels>,
12}
13
14impl Callout {
15 pub fn single_line(
16 title: SharedString,
17 icon: Icon,
18 cta_label: SharedString,
19 cta_action: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
20 ) -> Self {
21 Self {
22 title,
23 message: None,
24 icon,
25 cta_label,
26 cta_action,
27 line_height: None,
28 }
29 }
30
31 pub fn multi_line(
32 title: SharedString,
33 message: SharedString,
34 icon: Icon,
35 cta_label: SharedString,
36 cta_action: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
37 ) -> Self {
38 Self {
39 title,
40 message: Some(message),
41 icon,
42 cta_label,
43 cta_action,
44 line_height: None,
45 }
46 }
47
48 pub fn line_height(mut self, line_height: Pixels) -> Self {
49 self.line_height = Some(line_height);
50 self
51 }
52}
53
54impl RenderOnce for Callout {
55 fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
56 let line_height = self.line_height.unwrap_or(window.line_height());
57
58 h_flex()
59 .p_2()
60 .gap_2()
61 .w_full()
62 .items_center()
63 .justify_between()
64 .bg(cx.theme().colors().panel_background)
65 .border_t_1()
66 .border_color(cx.theme().colors().border)
67 .overflow_x_hidden()
68 .child(
69 h_flex()
70 .flex_shrink()
71 .overflow_hidden()
72 .gap_2()
73 .items_start()
74 .child(
75 h_flex()
76 .h(line_height)
77 .items_center()
78 .justify_center()
79 .child(self.icon),
80 )
81 .child(
82 v_flex()
83 .flex_shrink()
84 .overflow_hidden()
85 .child(
86 h_flex()
87 .h(line_height)
88 .items_center()
89 .child(Label::new(self.title).size(LabelSize::Small)),
90 )
91 .when_some(self.message, |this, message| {
92 this.child(
93 div()
94 .w_full()
95 .flex_1()
96 .child(message)
97 .text_ui_sm(cx)
98 .text_color(cx.theme().colors().text_muted),
99 )
100 }),
101 ),
102 )
103 .child(
104 div().flex_none().child(
105 Button::new("cta", self.cta_label)
106 .on_click(self.cta_action)
107 .style(ButtonStyle::Filled)
108 .label_size(LabelSize::Small),
109 ),
110 )
111 }
112}
113
114impl Component for Callout {
115 fn scope() -> ComponentScope {
116 ComponentScope::Notification
117 }
118
119 fn description() -> Option<&'static str> {
120 Some(
121 "Used to display a callout for situations where the user needs to know some information, and likely make a decision. This might be a thread running out of tokens, or running out of prompts on a plan and needing to upgrade.",
122 )
123 }
124
125 fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
126 let callout_examples = vec![
127 single_example(
128 "Single Line",
129 Callout::single_line(
130 "Your settings contain deprecated values, please update them.".into(),
131 Icon::new(IconName::Warning)
132 .color(Color::Warning)
133 .size(IconSize::Small),
134 "Backup & Update".into(),
135 Box::new(|_, _, _| {}),
136 )
137 .into_any_element(),
138 )
139 .width(px(580.)),
140 single_example(
141 "Multi Line",
142 Callout::multi_line(
143 "Thread reached the token limit".into(),
144 "Start a new thread from a summary to continue the conversation.".into(),
145 Icon::new(IconName::X)
146 .color(Color::Error)
147 .size(IconSize::Small),
148 "Start New Thread".into(),
149 Box::new(|_, _, _| {}),
150 )
151 .into_any_element(),
152 )
153 .width(px(580.)),
154 ];
155
156 Some(
157 example_group(callout_examples)
158 .vertical()
159 .into_any_element(),
160 )
161 }
162}