callout.rs

  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}