yew_bs/components/
toasts.rs1use yew::prelude::*;
2use web_sys::Element;
3use wasm_bindgen::JsValue;
4use crate::components::common::Variant;
5use crate::interop::BsToast;
6#[derive(Clone, Copy, PartialEq, Debug)]
7pub enum ToastPlacement {
8 TopStart,
9 TopCenter,
10 TopEnd,
11 MiddleStart,
12 MiddleCenter,
13 MiddleEnd,
14 BottomStart,
15 BottomCenter,
16 BottomEnd,
17}
18impl ToastPlacement {
19 pub fn as_str(&self) -> &'static str {
20 match self {
21 ToastPlacement::TopStart => "top-0 start-0",
22 ToastPlacement::TopCenter => "top-0 start-50 translate-middle-x",
23 ToastPlacement::TopEnd => "top-0 end-0",
24 ToastPlacement::MiddleStart => "top-50 start-0 translate-middle-y",
25 ToastPlacement::MiddleCenter => "top-50 start-50 translate-middle",
26 ToastPlacement::MiddleEnd => "top-50 end-0 translate-middle-y",
27 ToastPlacement::BottomStart => "bottom-0 start-0",
28 ToastPlacement::BottomCenter => "bottom-0 start-50 translate-middle-x",
29 ToastPlacement::BottomEnd => "bottom-0 end-0",
30 }
31 }
32}
33#[derive(Properties, PartialEq)]
35pub struct ToastProps {
36 #[prop_or_default]
38 pub header: Children,
39 #[prop_or_default]
41 pub children: Children,
42 #[prop_or(true)]
44 pub show: bool,
45 #[prop_or(true)]
47 pub dismissible: bool,
48 #[prop_or_default]
50 pub variant: Option<Variant>,
51 #[prop_or(5000)]
53 pub delay: u32,
54 #[prop_or_default]
56 pub on_show: Option<Callback<()>>,
57 #[prop_or_default]
59 pub on_hide: Option<Callback<()>>,
60 #[prop_or_default]
62 pub class: Option<AttrValue>,
63 #[prop_or_default]
65 pub node_ref: NodeRef,
66}
67#[derive(Properties, PartialEq)]
69pub struct ToastContainerProps {
70 #[prop_or(ToastPlacement::TopEnd)]
72 pub placement: ToastPlacement,
73 #[prop_or_default]
75 pub children: Children,
76 #[prop_or_default]
78 pub class: Option<AttrValue>,
79 #[prop_or_default]
81 pub node_ref: NodeRef,
82}
83#[function_component(Toast)]
85pub fn toast(props: &ToastProps) -> Html {
86 let mut classes = Classes::new();
87 classes.push("toast");
88 if let Some(variant) = &props.variant {
89 classes.push(format!("text-bg-{}", variant.as_str()));
90 }
91 if let Some(class) = &props.class {
92 classes.push(class.to_string());
93 }
94 let on_show = props.on_show.clone();
95 let on_hide = props.on_hide.clone();
96 let node_ref = props.node_ref.clone();
97 let dismissible = props.dismissible;
98 let options = {
99 let opts = js_sys::Object::new();
100 js_sys::Reflect::set(&opts, &"autohide".into(), &JsValue::from(props.delay > 0))
101 .unwrap();
102 if props.delay > 0 {
103 js_sys::Reflect::set(&opts, &"delay".into(), &JsValue::from(props.delay))
104 .unwrap();
105 }
106 JsValue::from(opts)
107 };
108 {
109 let node_ref = node_ref.clone();
110 let on_show = on_show.clone();
111 let on_hide = on_hide.clone();
112 use_effect_with(
113 (props.show, options.clone()),
114 move |(show, options)| {
115 if let Some(element) = node_ref.cast::<Element>() {
116 let bs_toast = BsToast::new(&element, Some(&options));
117 if *show {
118 bs_toast.show();
119 if let Some(on_show) = &on_show {
120 on_show.emit(());
121 }
122 } else {
123 bs_toast.hide();
124 if let Some(on_hide) = &on_hide {
125 on_hide.emit(());
126 }
127 }
128 }
129 || ()
130 },
131 );
132 }
133 let close_button_onclick = Callback::from(move |e: MouseEvent| {
134 e.prevent_default();
135 if let Some(element) = node_ref.cast::<Element>() {
136 let bs_toast = BsToast::new(&element, Some(&options));
137 bs_toast.hide();
138 if let Some(on_hide) = on_hide.as_ref() {
139 on_hide.emit(());
140 }
141 }
142 });
143 html! {
144 < div class = { classes } role = "alert" aria - live = "assertive" aria - atomic
145 = "true" ref = { props.node_ref.clone() } > if ! props.header.is_empty() { < div
146 class = "toast-header" > { for props.header.iter() } if dismissible { < button
147 type = "button" class = "btn-close" data - bs - dismiss = "toast" aria - label =
148 "Close" onclick = { close_button_onclick.clone() } /> } </ div > } < div class =
149 "toast-body" > { for props.children.iter() } if props.header.is_empty() &&
150 dismissible { < button type = "button" class = "btn-close ms-auto" data - bs -
151 dismiss = "toast" aria - label = "Close" onclick = { close_button_onclick } /> }
152 </ div > </ div >
153 }
154}
155#[function_component(ToastContainer)]
157pub fn toast_container(props: &ToastContainerProps) -> Html {
158 let mut classes = Classes::new();
159 classes.push("toast-container");
160 classes.push("position-fixed");
161 classes.push("p-3");
162 for class in props.placement.as_str().split(' ') {
163 classes.push(class);
164 }
165 if let Some(class) = &props.class {
166 classes.push(class.to_string());
167 }
168 html! {
169 < div class = { classes } ref = { props.node_ref.clone() } > { for props.children
170 .iter() } </ div >
171 }
172}