alert: Improve Alert styles. (#1044)

<img width="1201" alt="image"
src="https://github.com/user-attachments/assets/7484132b-8bdd-4353-a6f0-f038189d9b15"
/>
This commit is contained in:
Jason Lee 2025-07-04 15:29:05 +08:00 committed by GitHub
parent cafb03777b
commit 9b0ce145f6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 176 additions and 110 deletions

View file

@ -6,6 +6,7 @@ use gpui_component::{
alert::Alert,
button::{Button, ButtonGroup},
dock::PanelControl,
text::TextView,
v_flex, IconName, Selectable as _, Sizable as _, Size,
};
@ -64,20 +65,6 @@ impl Render for AlertStory {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
v_flex()
.gap_4()
.child(
Alert::warning(
"banner-1",
"This is a banner alert, it will take the full width of the container.",
)
.banner()
.on_close(cx.listener(|this, _, _, cx| {
this.banner_visible = !this.banner_visible;
cx.notify();
}))
.visible(self.banner_visible)
.with_size(self.size)
.icon(IconName::Bell),
)
.child(
ButtonGroup::new("toggle-size")
.outline()
@ -114,56 +101,135 @@ impl Render for AlertStory {
})),
)
.child(
section("Info").w_2_3().child(
Alert::info("info1", "This is an info alert.")
.with_size(self.size)
.title("Info message")
.on_close(cx.listener(|_, _, _, _| {
println!("Info alert closed");
})),
),
)
.child(
section("Success with Title").w_2_3().child(
Alert::success(
"success-1",
"You have successfully submitted your form.\n\
Thank you for your submission!",
section("Default").w_2_3().child(
Alert::new(
"alert-default",
TextView::markdown(
"md",
"This is an alert with icon, title and description (in Markdown).\n\
- This is a **list** item.\n\
- This is another list item.",
),
)
.with_size(self.size)
.title("Submit Successful"),
.title("Success! Your changes have been saved"),
),
)
.child(
section("Warning").w_2_3().child(
Alert::warning(
"warning-1",
"This is a warning alert with icon and title.\n\
This is second line of text to test is the line-height is correct.",
)
.with_size(self.size),
section("With variant").w_2_3().child(
v_flex()
.w_full()
.gap_3()
.child(
Alert::info("info1", "This is an info alert.")
.with_size(self.size)
.title("Info message")
.on_close(cx.listener(|_, _, _, _| {
println!("Info alert closed");
})),
)
.child(
Alert::success(
"success-1",
"You have successfully submitted your form.\n\
Thank you for your submission!",
)
.with_size(self.size)
.title("Submit Successful"),
)
.child(
Alert::warning(
"warning-1",
"This is a warning alert with icon and title.\n\
This is second line of text to test is the line-height is correct.",
)
.with_size(self.size),
)
.child(
Alert::error(
"error-1",
TextView::markdown(
"error-message",
"Please verify your billing information and try again.\n\
- Check your card details\n\
- Ensure sufficient funds\n\
- Verify billing address",
),
)
.with_size(self.size)
.title("Unable to process your payment."),
),
),
)
.child(
section("Error").w_2_3().child(
Alert::error(
"error-1",
"There was an error submitting your form.\n\
Please try again later, if you still have issues, please contact support.",
)
.with_size(self.size)
.title("Error!"),
section("Banner").w_2_3().child(
v_flex()
.w_full()
.gap_2()
.child(
Alert::new(
"banner-1",
"This is a banner alert, it will take \
the full width of the container.",
)
.banner()
.on_close(cx.listener(|this, _, _, cx| {
this.banner_visible = !this.banner_visible;
cx.notify();
}))
.visible(self.banner_visible)
.with_size(self.size),
)
.child(
Alert::info(
"banner-info",
"This is a banner alert, it will take the full width of the\
container.",
)
.banner()
.with_size(self.size),
)
.child(
Alert::success(
"banner-success",
"This is a banner alert, it will take the full width of the\
container.",
)
.banner()
.with_size(self.size),
)
.child(
Alert::warning(
"banner-warning",
"This is a banner alert, it will take the full width of the\
container.",
)
.banner()
.with_size(self.size),
)
.child(
Alert::error(
"banner-error",
"This is a banner alert, it will take the full width of the\
container.",
)
.banner()
.with_size(self.size),
),
),
)
.child(
section("Custom Icon").w_2_3().child(
Alert::info(
Alert::new(
"other-1",
"Custom icon with info alert with long long long long long long long long long long long long long long long long long long long long messageeeeeeeee.",
"Custom icon with info alert with long \
long long long long long long long long \
long long long long long long long long long \
long long messageeeeeeeee.",
)
.title("Custom Icon")
.with_size(self.size)
.icon(IconName::Bell),
.icon(IconName::Calendar),
),
)
}

View file

@ -93,24 +93,17 @@ impl Gallery {
};
if let Some(init_story) = init_story {
this.set_active_story(init_story, cx);
this.set_active_story(init_story, window, cx);
}
this
}
fn set_active_story(&mut self, name: &str, cx: &mut App) {
let group_index = 1;
let Some(story_index) = self.stories.get(group_index).and_then(|(_, stories)| {
stories
.iter()
.position(|story| story.read(cx).name.to_lowercase().replace("story", "") == name)
}) else {
return;
};
self.active_group_index = Some(group_index);
self.active_index = Some(story_index);
fn set_active_story(&mut self, name: &str, window: &mut Window, cx: &mut App) {
let name = name.to_string();
self.search_input.update(cx, |this, cx| {
this.set_value(&name, window, cx);
})
}
fn view(init_story: Option<&str>, window: &mut Window, cx: &mut App) -> Entity<Self> {

View file

@ -1,16 +1,21 @@
use std::rc::Rc;
use gpui::{
div, prelude::FluentBuilder as _, px, relative, App, ClickEvent, ElementId, Empty, Hsla,
div, prelude::FluentBuilder as _, px, relative, rems, App, ClickEvent, ElementId, Empty, Hsla,
InteractiveElement, IntoElement, ParentElement as _, RenderOnce, SharedString,
StatefulInteractiveElement, StyleRefinement, Styled, Window,
};
use crate::{h_flex, text::Text, ActiveTheme as _, Icon, IconName, Sizable, Size, StyledExt};
use crate::{
h_flex,
text::{Text, TextViewStyle},
ActiveTheme as _, Icon, IconName, Sizable, Size, StyleSized, StyledExt,
};
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub enum AlertVariant {
#[default]
Secondary,
Info,
Success,
Warning,
@ -20,6 +25,7 @@ pub enum AlertVariant {
impl AlertVariant {
fn fg(&self, cx: &App) -> Hsla {
match self {
AlertVariant::Secondary => cx.theme().secondary_foreground,
AlertVariant::Info => cx.theme().info,
AlertVariant::Success => cx.theme().success,
AlertVariant::Warning => cx.theme().warning,
@ -29,6 +35,17 @@ impl AlertVariant {
fn color(&self, cx: &App) -> Hsla {
match self {
AlertVariant::Secondary => cx.theme().secondary,
AlertVariant::Info => cx.theme().info,
AlertVariant::Success => cx.theme().success,
AlertVariant::Warning => cx.theme().warning,
AlertVariant::Error => cx.theme().danger,
}
}
fn border_color(&self, cx: &App) -> Hsla {
match self {
AlertVariant::Secondary => cx.theme().border,
AlertVariant::Info => cx.theme().info,
AlertVariant::Success => cx.theme().success,
AlertVariant::Warning => cx.theme().warning,
@ -43,7 +60,7 @@ pub struct Alert {
id: ElementId,
style: StyleRefinement,
variant: AlertVariant,
icon: Option<Icon>,
icon: Icon,
title: Option<SharedString>,
message: Text,
size: Size,
@ -54,12 +71,12 @@ pub struct Alert {
impl Alert {
/// Create a new alert with the given message.
fn new(id: impl Into<ElementId>, message: impl Into<Text>) -> Self {
pub fn new(id: impl Into<ElementId>, message: impl Into<Text>) -> Self {
Self {
id: id.into(),
style: StyleRefinement::default(),
variant: AlertVariant::default(),
icon: None,
icon: Icon::new(IconName::Info),
title: None,
message: message.into(),
size: Size::default(),
@ -105,7 +122,7 @@ impl Alert {
/// Set the icon for the alert.
pub fn icon(mut self, icon: impl Into<Icon>) -> Self {
self.icon = Some(icon.into());
self.icon = icon.into();
self
}
@ -159,65 +176,40 @@ impl RenderOnce for Alert {
return Empty.into_any_element();
}
let (radius, padding_x, padding_y, gap, line_height, icon_mt) = match self.size {
Size::XSmall => (cx.theme().radius, px(12.), px(6.), px(6.), 1.2, px(2.5)),
Size::Small => (cx.theme().radius, px(12.), px(8.), px(6.), 1.2, px(1.5)),
Size::Large => (
cx.theme().radius * 3.,
px(20.),
px(16.),
px(12.),
1.4,
px(0.),
),
_ => (
cx.theme().radius * 2.,
px(16.),
px(12.),
px(8.),
1.3,
px(1.),
),
let (radius, padding_x, padding_y, gap) = match self.size {
Size::XSmall => (cx.theme().radius, px(12.), px(6.), px(6.)),
Size::Small => (cx.theme().radius, px(12.), px(8.), px(6.)),
Size::Large => (cx.theme().radius_lg, px(20.), px(16.), px(12.)),
_ => (cx.theme().radius_lg, px(16.), px(12.), px(12.)),
};
let color = self.variant.color(cx);
let fg = self.variant.fg(cx);
let border_color = self.variant.border_color(cx);
h_flex()
.id(self.id)
.w_full()
.bg(color.opacity(0.06))
.text_color(self.variant.fg(cx))
.text_color(fg)
.bg(color.opacity(0.08))
.px(padding_x)
.py(padding_y)
.gap(gap)
.justify_between()
.line_height(relative(line_height))
.map(|this| match self.size {
Size::Large => this.text_base(),
_ => this.text_sm(),
})
.when(!self.banner, |this| {
this.rounded(radius)
.border_1()
.border_color(color)
.items_start()
})
.input_text_size(self.size.smaller())
.border_1()
.border_color(border_color)
.when(!self.banner, |this| this.rounded(radius).items_start())
.refine_style(&self.style)
.child(
div()
.flex()
.flex_1()
.items_start()
.when(self.banner, |this| this.items_center())
.overflow_hidden()
.gap(gap)
.child(
div().mt(icon_mt).child(
self.icon
.unwrap_or(IconName::Info.into())
.with_size(self.size)
.flex_shrink_0(),
),
)
.child(self.icon)
.child(
div()
.flex_1()
@ -228,13 +220,17 @@ impl RenderOnce for Alert {
div()
.w_full()
.truncate()
.mb_1()
.font_semibold()
.line_height(relative(1.))
.mb(rems(0.3))
.child(title),
)
})
})
.child(self.message),
.child(
self.message
.style(TextViewStyle::default().paragraph_gap(rems(0.2))),
),
),
)
.when_some(self.on_close, |this, on_close| {
@ -250,7 +246,6 @@ impl RenderOnce for Alert {
})
.child(
Icon::new(IconName::Close)
.text_color(cx.theme().foreground)
.with_size(self.size.max(Size::Medium))
.flex_shrink_0(),
),

View file

@ -59,6 +59,18 @@ impl From<TextView> for Text {
}
}
impl Text {
/// Set the style for [`TextView`].
///
/// Do nothing if this is `String`.
pub fn style(self, style: TextViewStyle) -> Self {
match self {
Self::String(s) => Self::String(s),
Self::TextView(e) => Self::TextView(e.style(style)),
}
}
}
impl RenderOnce for Text {
fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
match self {