dioxus_bootstrap_css/
toast.rs1use dioxus::prelude::*;
2
3use crate::types::Color;
4
5#[derive(Clone, PartialEq, Props)]
41pub struct ToastProps {
42 pub show: Signal<bool>,
44 #[props(default)]
46 pub title: String,
47 #[props(default)]
49 pub subtitle: String,
50 #[props(default = true)]
52 pub show_close: bool,
53 #[props(default)]
55 pub color: Option<Color>,
56 #[props(default)]
58 pub on_dismiss: Option<EventHandler<()>>,
59 #[props(default)]
61 pub class: String,
62 pub children: Element,
64}
65
66#[component]
67pub fn Toast(props: ToastProps) -> Element {
68 let is_shown = *props.show.read();
69 let mut show_signal = props.show;
70 let on_dismiss = props.on_dismiss.clone();
71
72 if !is_shown {
73 return rsx! {};
74 }
75
76 let dismiss = move |_| {
77 show_signal.set(false);
78 if let Some(handler) = &on_dismiss {
79 handler.call(());
80 }
81 };
82
83 let color_class = match &props.color {
84 Some(c) => format!(" text-bg-{c}"),
85 None => String::new(),
86 };
87
88 let full_class = if props.class.is_empty() {
89 format!("toast show{color_class}")
90 } else {
91 format!("toast show{color_class} {}", props.class)
92 };
93
94 let close_class = if props.color.is_some() {
96 "btn-close btn-close-white me-2 m-auto"
97 } else {
98 "btn-close"
99 };
100
101 rsx! {
102 div {
103 class: "{full_class}",
104 role: "alert",
105 "aria-live": "assertive",
106 "aria-atomic": "true",
107 if !props.title.is_empty() {
108 div { class: "toast-header",
110 strong { class: "me-auto", "{props.title}" }
111 if !props.subtitle.is_empty() {
112 small { "{props.subtitle}" }
113 }
114 if props.show_close {
115 button {
116 class: "btn-close",
117 r#type: "button",
118 "aria-label": "Close",
119 onclick: dismiss,
120 }
121 }
122 }
123 div { class: "toast-body", {props.children} }
124 } else if props.show_close {
125 div { class: "d-flex",
127 div { class: "toast-body", {props.children} }
128 button {
129 class: "{close_class}",
130 r#type: "button",
131 "aria-label": "Close",
132 onclick: move |_| show_signal.set(false),
133 }
134 }
135 } else {
136 div { class: "toast-body", {props.children} }
138 }
139 }
140 }
141}
142
143#[derive(Clone, PartialEq, Props)]
154pub struct ToastContainerProps {
155 #[props(default)]
157 pub position: ToastPosition,
158 #[props(default)]
160 pub class: String,
161 pub children: Element,
163}
164
165#[derive(Clone, Copy, Debug, Default, PartialEq)]
167pub enum ToastPosition {
168 TopStart,
169 TopCenter,
170 #[default]
171 TopEnd,
172 MiddleCenter,
173 BottomStart,
174 BottomCenter,
175 BottomEnd,
176}
177
178impl std::fmt::Display for ToastPosition {
179 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
180 match self {
181 ToastPosition::TopStart => write!(f, "top-0 start-0"),
182 ToastPosition::TopCenter => write!(f, "top-0 start-50 translate-middle-x"),
183 ToastPosition::TopEnd => write!(f, "top-0 end-0"),
184 ToastPosition::MiddleCenter => {
185 write!(f, "top-50 start-50 translate-middle")
186 }
187 ToastPosition::BottomStart => write!(f, "bottom-0 start-0"),
188 ToastPosition::BottomCenter => {
189 write!(f, "bottom-0 start-50 translate-middle-x")
190 }
191 ToastPosition::BottomEnd => write!(f, "bottom-0 end-0"),
192 }
193 }
194}
195
196#[component]
197pub fn ToastContainer(props: ToastContainerProps) -> Element {
198 let pos = props.position;
199 let full_class = if props.class.is_empty() {
200 format!("toast-container position-fixed p-3 {pos}")
201 } else {
202 format!("toast-container position-fixed p-3 {pos} {}", props.class)
203 };
204
205 rsx! {
206 div { class: "{full_class}", {props.children} }
207 }
208}