use dioxus::prelude::*;
use crate::types::Color;
#[derive(Clone, PartialEq, Props)]
pub struct ToastProps {
pub show: Signal<bool>,
#[props(default)]
pub title: String,
#[props(default)]
pub subtitle: String,
#[props(default = true)]
pub show_close: bool,
#[props(default)]
pub color: Option<Color>,
#[props(default)]
pub on_dismiss: Option<EventHandler<()>>,
#[props(default)]
pub class: String,
pub children: Element,
}
#[component]
pub fn Toast(props: ToastProps) -> Element {
let is_shown = *props.show.read();
let mut show_signal = props.show;
let on_dismiss = props.on_dismiss.clone();
if !is_shown {
return rsx! {};
}
let dismiss = move |_| {
show_signal.set(false);
if let Some(handler) = &on_dismiss {
handler.call(());
}
};
let color_class = match &props.color {
Some(c) => format!(" text-bg-{c}"),
None => String::new(),
};
let full_class = if props.class.is_empty() {
format!("toast show{color_class}")
} else {
format!("toast show{color_class} {}", props.class)
};
let close_class = if props.color.is_some() {
"btn-close btn-close-white me-2 m-auto"
} else {
"btn-close"
};
rsx! {
div {
class: "{full_class}",
role: "alert",
"aria-live": "assertive",
"aria-atomic": "true",
if !props.title.is_empty() {
div { class: "toast-header",
strong { class: "me-auto", "{props.title}" }
if !props.subtitle.is_empty() {
small { "{props.subtitle}" }
}
if props.show_close {
button {
class: "btn-close",
r#type: "button",
"aria-label": "Close",
onclick: dismiss,
}
}
}
div { class: "toast-body", {props.children} }
} else if props.show_close {
div { class: "d-flex",
div { class: "toast-body", {props.children} }
button {
class: "{close_class}",
r#type: "button",
"aria-label": "Close",
onclick: move |_| show_signal.set(false),
}
}
} else {
div { class: "toast-body", {props.children} }
}
}
}
}
#[derive(Clone, PartialEq, Props)]
pub struct ToastContainerProps {
#[props(default)]
pub position: ToastPosition,
#[props(default)]
pub class: String,
pub children: Element,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub enum ToastPosition {
TopStart,
TopCenter,
#[default]
TopEnd,
MiddleCenter,
BottomStart,
BottomCenter,
BottomEnd,
}
impl std::fmt::Display for ToastPosition {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ToastPosition::TopStart => write!(f, "top-0 start-0"),
ToastPosition::TopCenter => write!(f, "top-0 start-50 translate-middle-x"),
ToastPosition::TopEnd => write!(f, "top-0 end-0"),
ToastPosition::MiddleCenter => {
write!(f, "top-50 start-50 translate-middle")
}
ToastPosition::BottomStart => write!(f, "bottom-0 start-0"),
ToastPosition::BottomCenter => {
write!(f, "bottom-0 start-50 translate-middle-x")
}
ToastPosition::BottomEnd => write!(f, "bottom-0 end-0"),
}
}
}
#[component]
pub fn ToastContainer(props: ToastContainerProps) -> Element {
let pos = props.position;
let full_class = if props.class.is_empty() {
format!("toast-container position-fixed p-3 {pos}")
} else {
format!("toast-container position-fixed p-3 {pos} {}", props.class)
};
rsx! {
div { class: "{full_class}", {props.children} }
}
}