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:
parent
cafb03777b
commit
9b0ce145f6
4 changed files with 176 additions and 110 deletions
|
|
@ -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),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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> {
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in a new issue