callout.rs

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