@@ -39,7 +39,7 @@ use proto::Plan;
use settings::Settings;
use std::time::Duration;
use theme::ThemeSettings;
-use ui::{Disclosure, KeyBinding, PopoverMenuHandle, Tooltip, prelude::*};
+use ui::{Callout, Disclosure, KeyBinding, PopoverMenuHandle, Tooltip, prelude::*};
use util::{ResultExt as _, maybe};
use workspace::{CollaboratorId, Workspace};
use zed_llm_client::CompletionIntent;
@@ -1175,6 +1175,7 @@ impl MessageEditor {
.map_or(false, |model| {
model.provider.id().0 == ZED_CLOUD_PROVIDER_ID
});
+
if !is_using_zed_provider {
return None;
}
@@ -1229,14 +1230,6 @@ impl MessageEditor {
token_usage_ratio: TokenUsageRatio,
cx: &mut Context<Self>,
) -> Option<Div> {
- let title = if token_usage_ratio == TokenUsageRatio::Exceeded {
- "Thread reached the token limit"
- } else {
- "Thread reaching the token limit soon"
- };
-
- let message = "Start a new thread from a summary to continue the conversation.";
-
let icon = if token_usage_ratio == TokenUsageRatio::Exceeded {
Icon::new(IconName::X)
.color(Color::Error)
@@ -1247,19 +1240,36 @@ impl MessageEditor {
.size(IconSize::XSmall)
};
+ let title = if token_usage_ratio == TokenUsageRatio::Exceeded {
+ "Thread reached the token limit"
+ } else {
+ "Thread reaching the token limit soon"
+ };
+
Some(
div()
- .child(ui::Callout::multi_line(
- title,
- message,
- icon,
- "Start New Thread",
- Box::new(cx.listener(|this, _, window, cx| {
- let from_thread_id = Some(this.thread.read(cx).id().clone());
- window.dispatch_action(Box::new(NewThread { from_thread_id }), cx);
- })),
- ))
- .line_height(line_height),
+ .border_t_1()
+ .border_color(cx.theme().colors().border)
+ .child(
+ Callout::new()
+ .line_height(line_height)
+ .icon(icon)
+ .title(title)
+ .description(
+ "Start a new thread from a summary to continue the conversation.",
+ )
+ .primary_action(
+ Button::new("start-new-thread", "Start New Thread")
+ .label_size(LabelSize::Small)
+ .on_click(cx.listener(|this, _, window, cx| {
+ let from_thread_id = Some(this.thread.read(cx).id().clone());
+ window.dispatch_action(
+ Box::new(NewThread { from_thread_id }),
+ cx,
+ );
+ })),
+ ),
+ ),
)
}
@@ -2,7 +2,7 @@ use client::zed_urls;
use component::{empty_example, example_group_with_title, single_example};
use gpui::{AnyElement, App, IntoElement, RenderOnce, Window};
use language_model::RequestUsage;
-use ui::{Callout, Color, Icon, IconName, IconSize, prelude::*};
+use ui::{Callout, prelude::*};
use zed_llm_client::{Plan, UsageLimit};
#[derive(IntoElement, RegisterComponent)]
@@ -91,16 +91,23 @@ impl RenderOnce for UsageCallout {
.size(IconSize::XSmall)
};
- Callout::multi_line(
- title,
- message,
- icon,
- button_text,
- Box::new(move |_, _, cx| {
- cx.open_url(&url);
- }),
- )
- .into_any_element()
+ div()
+ .border_t_1()
+ .border_color(cx.theme().colors().border)
+ .child(
+ Callout::new()
+ .icon(icon)
+ .title(title)
+ .description(message)
+ .primary_action(
+ Button::new("upgrade", button_text)
+ .label_size(LabelSize::Small)
+ .on_click(move |_, _, cx| {
+ cx.open_url(&url);
+ }),
+ ),
+ )
+ .into_any_element()
}
}
@@ -189,10 +196,8 @@ impl Component for UsageCallout {
);
Some(
- div()
+ v_flex()
.p_4()
- .flex()
- .flex_col()
.gap_4()
.child(free_examples)
.child(trial_examples)
@@ -1,51 +1,77 @@
-use gpui::ClickEvent;
+use gpui::AnyElement;
use crate::prelude::*;
+/// A callout component for displaying important information that requires user attention.
+///
+/// # Usage Example
+///
+/// ```
+/// use ui::{Callout};
+///
+/// Callout::new()
+/// .icon(Icon::new(IconName::Warning).color(Color::Warning))
+/// .title(Label::new("Be aware of your subscription!"))
+/// .description(Label::new("Your subscription is about to expire. Renew now!"))
+/// .primary_action(Button::new("renew", "Renew Now"))
+/// .secondary_action(Button::new("remind", "Remind Me Later"))
+/// ```
+///
#[derive(IntoElement, RegisterComponent)]
pub struct Callout {
- title: SharedString,
- message: Option<SharedString>,
- icon: Icon,
- cta_label: SharedString,
- cta_action: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
+ icon: Option<Icon>,
+ title: Option<SharedString>,
+ description: Option<SharedString>,
+ primary_action: Option<AnyElement>,
+ secondary_action: Option<AnyElement>,
line_height: Option<Pixels>,
}
impl Callout {
- pub fn single_line(
- title: impl Into<SharedString>,
- icon: Icon,
- cta_label: impl Into<SharedString>,
- cta_action: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
- ) -> Self {
+ /// Creates a new `Callout` component with default styling.
+ pub fn new() -> Self {
Self {
- title: title.into(),
- message: None,
- icon,
- cta_label: cta_label.into(),
- cta_action,
+ icon: None,
+ title: None,
+ description: None,
+ primary_action: None,
+ secondary_action: None,
line_height: None,
}
}
- pub fn multi_line(
- title: impl Into<SharedString>,
- message: impl Into<SharedString>,
- icon: Icon,
- cta_label: impl Into<SharedString>,
- cta_action: Box<dyn Fn(&ClickEvent, &mut Window, &mut App) + 'static>,
- ) -> Self {
- Self {
- title: title.into(),
- message: Some(message.into()),
- icon,
- cta_label: cta_label.into(),
- cta_action,
- line_height: None,
- }
+ /// Sets the icon to display in the callout.
+ pub fn icon(mut self, icon: Icon) -> Self {
+ self.icon = Some(icon);
+ self
+ }
+
+ /// Sets the title of the callout.
+ pub fn title(mut self, title: impl Into<SharedString>) -> Self {
+ self.title = Some(title.into());
+ self
+ }
+
+ /// Sets the description of the callout.
+ /// The description can be single or multi-line text.
+ pub fn description(mut self, description: impl Into<SharedString>) -> Self {
+ self.description = Some(description.into());
+ self
}
+ /// Sets the primary call-to-action button.
+ pub fn primary_action(mut self, action: impl IntoElement) -> Self {
+ self.primary_action = Some(action.into_any_element());
+ self
+ }
+
+ /// Sets an optional secondary call-to-action button.
+ pub fn secondary_action(mut self, action: impl IntoElement) -> Self {
+ self.secondary_action = Some(action.into_any_element());
+ self
+ }
+
+ /// Sets a custom line height for the callout content.
pub fn line_height(mut self, line_height: Pixels) -> Self {
self.line_height = Some(line_height);
self
@@ -57,57 +83,54 @@ impl RenderOnce for Callout {
let line_height = self.line_height.unwrap_or(window.line_height());
h_flex()
+ .w_full()
.p_2()
.gap_2()
- .w_full()
- .items_center()
- .justify_between()
+ .items_start()
.bg(cx.theme().colors().panel_background)
- .border_t_1()
- .border_color(cx.theme().colors().border)
.overflow_x_hidden()
+ .when_some(self.icon, |this, icon| {
+ this.child(h_flex().h(line_height).justify_center().child(icon))
+ })
.child(
- h_flex()
- .flex_shrink()
- .overflow_hidden()
- .gap_2()
- .items_start()
+ v_flex()
+ .w_full()
.child(
h_flex()
.h(line_height)
- .items_center()
- .justify_center()
- .child(self.icon),
+ .w_full()
+ .gap_1()
+ .flex_wrap()
+ .justify_between()
+ .when_some(self.title, |this, title| {
+ this.child(h_flex().child(Label::new(title).size(LabelSize::Small)))
+ })
+ .when(
+ self.primary_action.is_some() || self.secondary_action.is_some(),
+ |this| {
+ this.child(
+ h_flex()
+ .gap_1()
+ .when_some(self.secondary_action, |this, action| {
+ this.child(action)
+ })
+ .when_some(self.primary_action, |this, action| {
+ this.child(action)
+ }),
+ )
+ },
+ ),
)
- .child(
- v_flex()
- .flex_shrink()
- .overflow_hidden()
- .child(
- h_flex()
- .h(line_height)
- .items_center()
- .child(Label::new(self.title).size(LabelSize::Small)),
- )
- .when_some(self.message, |this, message| {
- this.child(
- div()
- .w_full()
- .flex_1()
- .child(message)
- .text_ui_sm(cx)
- .text_color(cx.theme().colors().text_muted),
- )
- }),
- ),
- )
- .child(
- div().flex_none().child(
- Button::new("cta", self.cta_label)
- .on_click(self.cta_action)
- .style(ButtonStyle::Filled)
- .label_size(LabelSize::Small),
- ),
+ .when_some(self.description, |this, description| {
+ this.child(
+ div()
+ .w_full()
+ .flex_1()
+ .child(description)
+ .text_ui_sm(cx)
+ .text_color(cx.theme().colors().text_muted),
+ )
+ }),
)
}
}
@@ -126,30 +149,75 @@ impl Component for Callout {
fn preview(_window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
let callout_examples = vec![
single_example(
- "Single Line",
- Callout::single_line(
- "Your settings contain deprecated values, please update them.",
- Icon::new(IconName::Warning)
- .color(Color::Warning)
- .size(IconSize::Small),
- "Backup & Update",
- Box::new(|_, _, _| {}),
- )
- .into_any_element(),
+ "Simple with Title Only",
+ Callout::new()
+ .icon(
+ Icon::new(IconName::Info)
+ .color(Color::Accent)
+ .size(IconSize::Small),
+ )
+ .title("System maintenance scheduled for tonight")
+ .primary_action(Button::new("got-it", "Got it").label_size(LabelSize::Small))
+ .into_any_element(),
)
.width(px(580.)),
single_example(
- "Multi Line",
- Callout::multi_line(
- "Thread reached the token limit",
- "Start a new thread from a summary to continue the conversation.",
- Icon::new(IconName::X)
- .color(Color::Error)
- .size(IconSize::Small),
- "Start New Thread",
- Box::new(|_, _, _| {}),
- )
- .into_any_element(),
+ "With Title and Description",
+ Callout::new()
+ .icon(
+ Icon::new(IconName::Warning)
+ .color(Color::Warning)
+ .size(IconSize::Small),
+ )
+ .title("Your settings contain deprecated values")
+ .description(
+ "We'll backup your current settings and update them to the new format.",
+ )
+ .primary_action(
+ Button::new("update", "Backup & Update").label_size(LabelSize::Small),
+ )
+ .secondary_action(
+ Button::new("dismiss", "Dismiss").label_size(LabelSize::Small),
+ )
+ .into_any_element(),
+ )
+ .width(px(580.)),
+ single_example(
+ "Error with Multiple Actions",
+ Callout::new()
+ .icon(
+ Icon::new(IconName::X)
+ .color(Color::Error)
+ .size(IconSize::Small),
+ )
+ .title("Thread reached the token limit")
+ .description("Start a new thread from a summary to continue the conversation.")
+ .primary_action(
+ Button::new("new-thread", "Start New Thread").label_size(LabelSize::Small),
+ )
+ .secondary_action(
+ Button::new("view-summary", "View Summary").label_size(LabelSize::Small),
+ )
+ .into_any_element(),
+ )
+ .width(px(580.)),
+ single_example(
+ "Multi-line Description",
+ Callout::new()
+ .icon(
+ Icon::new(IconName::Sparkle)
+ .color(Color::Accent)
+ .size(IconSize::Small),
+ )
+ .title("Upgrade to Pro")
+ .description("⢠Unlimited threads\n⢠Priority support\n⢠Advanced analytics")
+ .primary_action(
+ Button::new("upgrade", "Upgrade Now").label_size(LabelSize::Small),
+ )
+ .secondary_action(
+ Button::new("learn-more", "Learn More").label_size(LabelSize::Small),
+ )
+ .into_any_element(),
)
.width(px(580.)),
];