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,
|
alert::Alert,
|
||||||
button::{Button, ButtonGroup},
|
button::{Button, ButtonGroup},
|
||||||
dock::PanelControl,
|
dock::PanelControl,
|
||||||
|
text::TextView,
|
||||||
v_flex, IconName, Selectable as _, Sizable as _, Size,
|
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 {
|
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||||
v_flex()
|
v_flex()
|
||||||
.gap_4()
|
.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(
|
.child(
|
||||||
ButtonGroup::new("toggle-size")
|
ButtonGroup::new("toggle-size")
|
||||||
.outline()
|
.outline()
|
||||||
|
|
@ -114,17 +101,34 @@ impl Render for AlertStory {
|
||||||
})),
|
})),
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
section("Info").w_2_3().child(
|
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("Success! Your changes have been saved"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.child(
|
||||||
|
section("With variant").w_2_3().child(
|
||||||
|
v_flex()
|
||||||
|
.w_full()
|
||||||
|
.gap_3()
|
||||||
|
.child(
|
||||||
Alert::info("info1", "This is an info alert.")
|
Alert::info("info1", "This is an info alert.")
|
||||||
.with_size(self.size)
|
.with_size(self.size)
|
||||||
.title("Info message")
|
.title("Info message")
|
||||||
.on_close(cx.listener(|_, _, _, _| {
|
.on_close(cx.listener(|_, _, _, _| {
|
||||||
println!("Info alert closed");
|
println!("Info alert closed");
|
||||||
})),
|
})),
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
section("Success with Title").w_2_3().child(
|
|
||||||
Alert::success(
|
Alert::success(
|
||||||
"success-1",
|
"success-1",
|
||||||
"You have successfully submitted your form.\n\
|
"You have successfully submitted your form.\n\
|
||||||
|
|
@ -132,38 +136,100 @@ impl Render for AlertStory {
|
||||||
)
|
)
|
||||||
.with_size(self.size)
|
.with_size(self.size)
|
||||||
.title("Submit Successful"),
|
.title("Submit Successful"),
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.child(
|
.child(
|
||||||
section("Warning").w_2_3().child(
|
|
||||||
Alert::warning(
|
Alert::warning(
|
||||||
"warning-1",
|
"warning-1",
|
||||||
"This is a warning alert with icon and title.\n\
|
"This is a warning alert with icon and title.\n\
|
||||||
This is second line of text to test is the line-height is correct.",
|
This is second line of text to test is the line-height is correct.",
|
||||||
)
|
)
|
||||||
.with_size(self.size),
|
.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(
|
.child(
|
||||||
section("Error").w_2_3().child(
|
section("Banner").w_2_3().child(
|
||||||
Alert::error(
|
v_flex()
|
||||||
"error-1",
|
.w_full()
|
||||||
"There was an error submitting your form.\n\
|
.gap_2()
|
||||||
Please try again later, if you still have issues, please contact support.",
|
.child(
|
||||||
|
Alert::new(
|
||||||
|
"banner-1",
|
||||||
|
"This is a banner alert, it will take \
|
||||||
|
the full width of the container.",
|
||||||
)
|
)
|
||||||
.with_size(self.size)
|
.banner()
|
||||||
.title("Error!"),
|
.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(
|
.child(
|
||||||
section("Custom Icon").w_2_3().child(
|
section("Custom Icon").w_2_3().child(
|
||||||
Alert::info(
|
Alert::new(
|
||||||
"other-1",
|
"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")
|
.title("Custom Icon")
|
||||||
.with_size(self.size)
|
.with_size(self.size)
|
||||||
.icon(IconName::Bell),
|
.icon(IconName::Calendar),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -93,24 +93,17 @@ impl Gallery {
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(init_story) = init_story {
|
if let Some(init_story) = init_story {
|
||||||
this.set_active_story(init_story, cx);
|
this.set_active_story(init_story, window, cx);
|
||||||
}
|
}
|
||||||
|
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_active_story(&mut self, name: &str, cx: &mut App) {
|
fn set_active_story(&mut self, name: &str, window: &mut Window, cx: &mut App) {
|
||||||
let group_index = 1;
|
let name = name.to_string();
|
||||||
let Some(story_index) = self.stories.get(group_index).and_then(|(_, stories)| {
|
self.search_input.update(cx, |this, cx| {
|
||||||
stories
|
this.set_value(&name, window, cx);
|
||||||
.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 view(init_story: Option<&str>, window: &mut Window, cx: &mut App) -> Entity<Self> {
|
fn view(init_story: Option<&str>, window: &mut Window, cx: &mut App) -> Entity<Self> {
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,21 @@
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use gpui::{
|
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,
|
InteractiveElement, IntoElement, ParentElement as _, RenderOnce, SharedString,
|
||||||
StatefulInteractiveElement, StyleRefinement, Styled, Window,
|
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)]
|
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum AlertVariant {
|
pub enum AlertVariant {
|
||||||
#[default]
|
#[default]
|
||||||
|
Secondary,
|
||||||
Info,
|
Info,
|
||||||
Success,
|
Success,
|
||||||
Warning,
|
Warning,
|
||||||
|
|
@ -20,6 +25,7 @@ pub enum AlertVariant {
|
||||||
impl AlertVariant {
|
impl AlertVariant {
|
||||||
fn fg(&self, cx: &App) -> Hsla {
|
fn fg(&self, cx: &App) -> Hsla {
|
||||||
match self {
|
match self {
|
||||||
|
AlertVariant::Secondary => cx.theme().secondary_foreground,
|
||||||
AlertVariant::Info => cx.theme().info,
|
AlertVariant::Info => cx.theme().info,
|
||||||
AlertVariant::Success => cx.theme().success,
|
AlertVariant::Success => cx.theme().success,
|
||||||
AlertVariant::Warning => cx.theme().warning,
|
AlertVariant::Warning => cx.theme().warning,
|
||||||
|
|
@ -29,6 +35,17 @@ impl AlertVariant {
|
||||||
|
|
||||||
fn color(&self, cx: &App) -> Hsla {
|
fn color(&self, cx: &App) -> Hsla {
|
||||||
match self {
|
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::Info => cx.theme().info,
|
||||||
AlertVariant::Success => cx.theme().success,
|
AlertVariant::Success => cx.theme().success,
|
||||||
AlertVariant::Warning => cx.theme().warning,
|
AlertVariant::Warning => cx.theme().warning,
|
||||||
|
|
@ -43,7 +60,7 @@ pub struct Alert {
|
||||||
id: ElementId,
|
id: ElementId,
|
||||||
style: StyleRefinement,
|
style: StyleRefinement,
|
||||||
variant: AlertVariant,
|
variant: AlertVariant,
|
||||||
icon: Option<Icon>,
|
icon: Icon,
|
||||||
title: Option<SharedString>,
|
title: Option<SharedString>,
|
||||||
message: Text,
|
message: Text,
|
||||||
size: Size,
|
size: Size,
|
||||||
|
|
@ -54,12 +71,12 @@ pub struct Alert {
|
||||||
|
|
||||||
impl Alert {
|
impl Alert {
|
||||||
/// Create a new alert with the given message.
|
/// 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 {
|
Self {
|
||||||
id: id.into(),
|
id: id.into(),
|
||||||
style: StyleRefinement::default(),
|
style: StyleRefinement::default(),
|
||||||
variant: AlertVariant::default(),
|
variant: AlertVariant::default(),
|
||||||
icon: None,
|
icon: Icon::new(IconName::Info),
|
||||||
title: None,
|
title: None,
|
||||||
message: message.into(),
|
message: message.into(),
|
||||||
size: Size::default(),
|
size: Size::default(),
|
||||||
|
|
@ -105,7 +122,7 @@ impl Alert {
|
||||||
|
|
||||||
/// Set the icon for the alert.
|
/// Set the icon for the alert.
|
||||||
pub fn icon(mut self, icon: impl Into<Icon>) -> Self {
|
pub fn icon(mut self, icon: impl Into<Icon>) -> Self {
|
||||||
self.icon = Some(icon.into());
|
self.icon = icon.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -159,65 +176,40 @@ impl RenderOnce for Alert {
|
||||||
return Empty.into_any_element();
|
return Empty.into_any_element();
|
||||||
}
|
}
|
||||||
|
|
||||||
let (radius, padding_x, padding_y, gap, line_height, icon_mt) = match self.size {
|
let (radius, padding_x, padding_y, gap) = match self.size {
|
||||||
Size::XSmall => (cx.theme().radius, px(12.), px(6.), px(6.), 1.2, px(2.5)),
|
Size::XSmall => (cx.theme().radius, px(12.), px(6.), px(6.)),
|
||||||
Size::Small => (cx.theme().radius, px(12.), px(8.), px(6.), 1.2, px(1.5)),
|
Size::Small => (cx.theme().radius, px(12.), px(8.), px(6.)),
|
||||||
Size::Large => (
|
Size::Large => (cx.theme().radius_lg, px(20.), px(16.), px(12.)),
|
||||||
cx.theme().radius * 3.,
|
_ => (cx.theme().radius_lg, px(16.), px(12.), px(12.)),
|
||||||
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 color = self.variant.color(cx);
|
let color = self.variant.color(cx);
|
||||||
|
let fg = self.variant.fg(cx);
|
||||||
|
let border_color = self.variant.border_color(cx);
|
||||||
|
|
||||||
h_flex()
|
h_flex()
|
||||||
.id(self.id)
|
.id(self.id)
|
||||||
.w_full()
|
.w_full()
|
||||||
.bg(color.opacity(0.06))
|
.text_color(fg)
|
||||||
.text_color(self.variant.fg(cx))
|
.bg(color.opacity(0.08))
|
||||||
.px(padding_x)
|
.px(padding_x)
|
||||||
.py(padding_y)
|
.py(padding_y)
|
||||||
.gap(gap)
|
.gap(gap)
|
||||||
.justify_between()
|
.justify_between()
|
||||||
.line_height(relative(line_height))
|
.input_text_size(self.size.smaller())
|
||||||
.map(|this| match self.size {
|
|
||||||
Size::Large => this.text_base(),
|
|
||||||
_ => this.text_sm(),
|
|
||||||
})
|
|
||||||
.when(!self.banner, |this| {
|
|
||||||
this.rounded(radius)
|
|
||||||
.border_1()
|
.border_1()
|
||||||
.border_color(color)
|
.border_color(border_color)
|
||||||
.items_start()
|
.when(!self.banner, |this| this.rounded(radius).items_start())
|
||||||
})
|
|
||||||
.refine_style(&self.style)
|
.refine_style(&self.style)
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.flex()
|
.flex()
|
||||||
.flex_1()
|
.flex_1()
|
||||||
.items_start()
|
.items_start()
|
||||||
|
.when(self.banner, |this| this.items_center())
|
||||||
.overflow_hidden()
|
.overflow_hidden()
|
||||||
.gap(gap)
|
.gap(gap)
|
||||||
.child(
|
.child(self.icon)
|
||||||
div().mt(icon_mt).child(
|
|
||||||
self.icon
|
|
||||||
.unwrap_or(IconName::Info.into())
|
|
||||||
.with_size(self.size)
|
|
||||||
.flex_shrink_0(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.child(
|
.child(
|
||||||
div()
|
div()
|
||||||
.flex_1()
|
.flex_1()
|
||||||
|
|
@ -228,13 +220,17 @@ impl RenderOnce for Alert {
|
||||||
div()
|
div()
|
||||||
.w_full()
|
.w_full()
|
||||||
.truncate()
|
.truncate()
|
||||||
.mb_1()
|
|
||||||
.font_semibold()
|
.font_semibold()
|
||||||
|
.line_height(relative(1.))
|
||||||
|
.mb(rems(0.3))
|
||||||
.child(title),
|
.child(title),
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.child(self.message),
|
.child(
|
||||||
|
self.message
|
||||||
|
.style(TextViewStyle::default().paragraph_gap(rems(0.2))),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.when_some(self.on_close, |this, on_close| {
|
.when_some(self.on_close, |this, on_close| {
|
||||||
|
|
@ -250,7 +246,6 @@ impl RenderOnce for Alert {
|
||||||
})
|
})
|
||||||
.child(
|
.child(
|
||||||
Icon::new(IconName::Close)
|
Icon::new(IconName::Close)
|
||||||
.text_color(cx.theme().foreground)
|
|
||||||
.with_size(self.size.max(Size::Medium))
|
.with_size(self.size.max(Size::Medium))
|
||||||
.flex_shrink_0(),
|
.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 {
|
impl RenderOnce for Text {
|
||||||
fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
|
fn render(self, _: &mut Window, _: &mut App) -> impl IntoElement {
|
||||||
match self {
|
match self {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue