use crate::{prelude::*, *};
use std::sync::Arc;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum AlertVariant {
#[default]
Info,
Success,
Warning,
Danger,
}
#[derive(IntoElement)]
pub struct Alert {
id: SharedString,
variant: AlertVariant,
title: SharedString,
body: Option<SharedString>,
closable: bool,
on_close: Option<Arc<dyn Fn(&mut Window, &mut App) + Send + Sync + 'static>>,
style: StyleRefinement,
}
impl Alert {
#[track_caller]
pub fn new(title: impl Into<SharedString>) -> Self {
Self {
id: crate::caller_element_id("alert"),
variant: AlertVariant::Info,
title: title.into(),
body: None,
closable: false,
on_close: None,
style: StyleRefinement::default(),
}
}
pub fn id(mut self, id: impl Into<SharedString>) -> Self {
self.id = id.into();
self
}
pub fn variant(mut self, variant: AlertVariant) -> Self {
self.variant = variant;
self
}
pub fn body(mut self, body: impl Into<SharedString>) -> Self {
self.body = Some(body.into());
self
}
pub fn closable(mut self, closable: bool) -> Self {
self.closable = closable;
self
}
pub fn on_close<F>(mut self, f: F) -> Self
where
F: Fn(&mut Window, &mut App) + Send + Sync + 'static,
{
self.on_close = Some(Arc::new(f));
self
}
}
impl Styled for Alert {
fn style(&mut self) -> &mut StyleRefinement {
&mut self.style
}
}
impl RenderOnce for Alert {
fn render(self, _window: &mut Window, cx: &mut App) -> impl IntoElement {
let theme = cx.theme();
let border = theme.tokens.border;
let muted_foreground = theme.tokens.muted_foreground.color;
let (accent, icon) = match self.variant {
AlertVariant::Info => (rgb(0x3b82f6), IconName::Info),
AlertVariant::Success => (rgb(0x22c55e), IconName::CircleCheck),
AlertVariant::Warning => (rgb(0xeab308), IconName::TriangleAlert),
AlertVariant::Danger => (rgb(0xef4444), IconName::CircleX),
};
let user_style = self.style;
div()
.flex()
.flex_row()
.w_full()
.rounded_md()
.border_1()
.border_color(border)
.bg(accent.opacity(0.08))
.overflow_hidden()
.child(div().w(px(3.0)).flex_shrink_0().bg(accent))
.child(
div()
.flex()
.flex_row()
.items_start()
.gap(px(8.0))
.flex_1()
.px(px(12.0))
.py(px(10.0))
.child(div().text_color(accent).child(icon))
.child(
div()
.flex()
.flex_col()
.flex_1()
.gap(px(2.0))
.child(div().text_sm().text_color(accent).child(self.title))
.when_some(self.body, |this, body| {
this.child(div().text_xs().text_color(muted_foreground).child(body))
}),
),
)
.when(self.closable, |this| {
let on_close = self.on_close.clone();
let close_id = SharedString::from(format!("{}-close", self.id));
this.child(
Button::new(close_id)
.ghost()
.small()
.icon(IconName::Close)
.on_click(move |_, window, cx| {
if let Some(ref cb) = on_close {
cb(window, cx);
}
}),
)
})
.map(|mut this| {
this.style().refine(&user_style);
this
})
}
}