1#![doc(
2 html_logo_url = "https://github.com/next-rs/yew-alert/assets/62179149/b4b97406-f4a5-4235-a255-b0ffec31a05b",
3 html_favicon_url = "https://github.com/next-rs/yew-alert/assets/62179149/03114e06-dcf8-4121-91c7-5ddc43300a43"
4)]
5
6use gloo::timers::callback::Timeout;
82use yew::prelude::*;
83
84const TITLE: &'static str = "Info";
85const ALERT_CLASS: &'static str = "w-96 h-48 text-white";
86const ICON_CLASS: &'static str = "flex justify-center";
87const CONFIRM_BUTTON_TEXT: &'static str = "Okay";
88const CANCEL_BUTTON_TEXT: &'static str = "Cancel";
89const CONFIRM_BUTTON_CLASS: &'static str =
90 "mt-2 mx-2 px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600 focus:outline-none focus:border-blue-700 focus:ring focus:ring-blue-200";
91const CANCEL_BUTTON_CLASS: &'static str =
92 "mt-2 mx-2 px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600 focus:outline-none focus:border-gray-700 focus:ring focus:ring-gray-200";
93const POSITION: &'static str = "top-right";
94const CONTAINER_CLASS: &'static str =
95 "flex items-center text-center justify-center bg-gray-800 text-white border border-gray-600";
96const TITLE_CLASS: &'static str = "dark:text-white";
97const MESSAGE_CLASS: &'static str = "dark:text-gray-300";
98const ICON_TYPE: &'static str = "success";
99const ICON_COLOR: &'static str = "";
100const ICON_WIDTH: &'static str = "50";
101
102#[derive(Debug, PartialEq, Properties, Clone)]
104pub struct AlertProps {
105 #[prop_or_default]
107 pub message: &'static str,
108
109 pub show_alert: UseStateHandle<bool>,
111
112 #[prop_or(2500)]
114 pub timeout: u32,
115
116 #[prop_or(TITLE)]
118 pub title: &'static str,
119
120 #[prop_or(ALERT_CLASS)]
122 pub alert_class: &'static str,
123
124 #[prop_or(ICON_CLASS)]
126 pub icon_class: &'static str,
127
128 #[prop_or(CONFIRM_BUTTON_TEXT)]
130 pub confirm_button_text: &'static str,
131
132 #[prop_or(CANCEL_BUTTON_TEXT)]
134 pub cancel_button_text: &'static str,
135
136 #[prop_or(CONFIRM_BUTTON_CLASS)]
138 pub confirm_button_class: &'static str,
139
140 #[prop_or(CANCEL_BUTTON_CLASS)]
142 pub cancel_button_class: &'static str,
143
144 #[prop_or(true)]
146 pub show_confirm_button: bool,
147
148 #[prop_or(true)]
150 pub show_cancel_button: bool,
151
152 #[prop_or(false)]
154 pub show_close_button: bool,
155
156 #[prop_or_default]
158 pub on_confirm: Callback<()>,
159
160 #[prop_or_default]
162 pub on_cancel: Callback<()>,
163
164 #[prop_or(POSITION)]
166 pub position: &'static str,
167
168 #[prop_or(CONTAINER_CLASS)]
170 pub container_class: &'static str,
171
172 #[prop_or(TITLE_CLASS)]
174 pub title_class: &'static str,
175
176 #[prop_or(MESSAGE_CLASS)]
178 pub message_class: &'static str,
179
180 #[prop_or(ICON_TYPE)]
182 pub icon_type: &'static str,
183
184 #[prop_or(ICON_COLOR)]
186 pub icon_color: &'static str,
187
188 #[prop_or(ICON_WIDTH)]
190 pub icon_width: &'static str,
191}
192
193#[function_component]
195pub fn Alert(props: &AlertProps) -> Html {
196 let show = *props.show_alert;
197 let timeout = props.timeout.clone();
198 let show_alert = props.show_alert.clone();
199
200 use_effect_with(show_alert.clone(), move |show_alert| {
201 if **show_alert {
202 let show_alert = show_alert.clone();
203 let handle = Timeout::new(timeout, move || show_alert.set(false)).forget();
204 let clear_handle = move || {
205 web_sys::Window::clear_timeout_with_handle(
206 &web_sys::window().unwrap(),
207 handle.as_f64().unwrap() as i32,
208 );
209 };
210
211 Box::new(clear_handle) as Box<dyn FnOnce()>
212 } else {
213 Box::new(|| {}) as Box<dyn FnOnce()>
214 }
215 });
216
217 let on_cancel = {
218 let on_cancel = props.on_cancel.clone();
219
220 Callback::from(move |_| {
221 on_cancel.emit(());
222 show_alert.set(false);
223 })
224 };
225 let on_confirm = {
226 let on_confirm = props.on_confirm.clone();
227
228 Callback::from(move |_| {
229 on_confirm.emit(());
230 })
231 };
232
233 let position_class = match props.position {
234 "top-left" => "top-0 left-0",
235 "top-right" => "top-0 right-0",
236 "middle" => "top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2",
237 "bottom" => "bottom-0 left-1/2 transform -translate-x-1/2",
238 "top" => "top-0 left-1/2 transform -translate-x-1/2",
239 "bottom-right" => "bottom-0 right-0",
240 "bottom-left" => "bottom-0 left-0",
241 _ => "top-0 right-0",
242 };
243 let mut icon_color = props.icon_type;
244 if props.icon_color.is_empty() {
245 icon_color = match &props.icon_type.to_lowercase()[..] {
246 "warning" => "#CC5500", "error" => "#EE4B2B", "success" => "lightgreen",
249 "info" => "lightblue",
250 "question" => "lightgray",
251 _ => props.icon_type,
252 };
253 }
254 let icon_path = match &props.icon_type.to_lowercase()[..] {
256 "warning" => {
257 html! {
258 <svg
259 xmlns="http://www.w3.org/2000/svg"
260 width={props.icon_width}
261 class="p-2 m-2"
262 fill={icon_color}
263 viewBox="0 0 512 512"
264 >
265 <path
266 d="M248.4 84.3c1.6-2.7 4.5-4.3 7.6-4.3s6 1.6 7.6 4.3L461.9 410c1.4 2.3 2.1 4.9 2.1 7.5c0 8-6.5 14.5-14.5 14.5H62.5c-8 0-14.5-6.5-14.5-14.5c0-2.7 .7-5.3 2.1-7.5L248.4 84.3zm-41-25L9.1 385c-6 9.8-9.1 21-9.1 32.5C0 452 28 480 62.5 480h387c34.5 0 62.5-28 62.5-62.5c0-11.5-3.2-22.7-9.1-32.5L304.6 59.3C294.3 42.4 275.9 32 256 32s-38.3 10.4-48.6 27.3zM288 368a32 32 0 1 0 -64 0 32 32 0 1 0 64 0zm-8-184c0-13.3-10.7-24-24-24s-24 10.7-24 24v96c0 13.3 10.7 24 24 24s24-10.7 24-24V184z"
267 />
268 </svg>
269 }
270 }
271 "error" => {
272 html! {
273 <svg
274 xmlns="http://www.w3.org/2000/svg"
275 width={props.icon_width}
276 class="p-2 m-2"
277 fill={icon_color}
278 viewBox="0 0 20 20"
279 >
280 <path
281 d="M12.71,7.291c-0.15-0.15-0.393-0.15-0.542,0L10,9.458L7.833,7.291c-0.15-0.15-0.392-0.15-0.542,0c-0.149,0.149-0.149,0.392,0,0.541L9.458,10l-2.168,2.167c-0.149,0.15-0.149,0.393,0,0.542c0.15,0.149,0.392,0.149,0.542,0L10,10.542l2.168,2.167c0.149,0.149,0.392,0.149,0.542,0c0.148-0.149,0.148-0.392,0-0.542L10.542,10l2.168-2.168C12.858,7.683,12.858,7.44,12.71,7.291z M10,1.188c-4.867,0-8.812,3.946-8.812,8.812c0,4.867,3.945,8.812,8.812,8.812s8.812-3.945,8.812-8.812C18.812,5.133,14.867,1.188,10,1.188z M10,18.046c-4.444,0-8.046-3.603-8.046-8.046c0-4.444,3.603-8.046,8.046-8.046c4.443,0,8.046,3.602,8.046,8.046C18.046,14.443,14.443,18.046,10,18.046z"
282 />
283 </svg>
284 }
285 }
286 "success" => {
287 html! {
288 <svg
289 xmlns="http://www.w3.org/2000/svg"
290 width={props.icon_width}
291 class="p-2 m-2"
292 fill={icon_color}
293 viewBox="0 0 512 512"
294 >
295 <path
296 d="M256 48a208 208 0 1 1 0 416 208 208 0 1 1 0-416zm0 464A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM369 209c9.4-9.4 9.4-24.6 0-33.9s-24.6-9.4-33.9 0l-111 111-47-47c-9.4-9.4-24.6-9.4-33.9 0s-9.4 24.6 0 33.9l64 64c9.4 9.4 24.6 9.4 33.9 0L369 209z"
297 />
298 </svg>
299 }
300 }
301 "info" => {
302 html! {
303 <svg
304 xmlns="http://www.w3.org/2000/svg"
305 width={props.icon_width}
306 class="p-2 m-2"
307 fill={icon_color}
308 viewBox="0 0 16 16"
309 >
310 <path
311 d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"
312 />
313 <path
314 d="m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533L8.93 6.588zM9 4.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0z"
315 />
316 </svg>
317 }
318 }
319 "question" => {
320 html! {
321 <svg
322 xmlns="http://www.w3.org/2000/svg"
323 width={props.icon_width}
324 class="p-2 m-2"
325 fill={icon_color}
326 viewBox="0 0 16 16"
327 >
328 <path
329 d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"
330 />
331 <path
332 d="M5.255 5.786a.237.237 0 0 0 .241.247h.825c.138 0 .248-.113.266-.25.09-.656.54-1.134 1.342-1.134.686 0 1.314.343 1.314 1.168 0 .635-.374.927-.965 1.371-.673.489-1.206 1.06-1.168 1.987l.003.217a.25.25 0 0 0 .25.246h.811a.25.25 0 0 0 .25-.25v-.105c0-.718.273-.927 1.01-1.486.609-.463 1.244-.977 1.244-2.056 0-1.511-1.276-2.241-2.673-2.241-1.267 0-2.655.59-2.75 2.286zm1.557 5.763c0 .533.425.927 1.01.927.609 0 1.028-.394 1.028-.927 0-.552-.42-.94-1.029-.94-.584 0-1.009.388-1.009.94z"
333 />
334 </svg>
335 }
336 }
337 _ => {
338 html! {
339 <svg
340 xmlns="http://www.w3.org/2000/svg"
341 width={props.icon_width}
342 class="p-2 m-2"
343 fill={icon_color}
344 viewBox="0 0 16 16"
345 >
346 <path
347 d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"
348 />
349 <path
350 d="M5.255 5.786a.237.237 0 0 0 .241.247h.825c.138 0 .248-.113.266-.25.09-.656.54-1.134 1.342-1.134.686 0 1.314.343 1.314 1.168 0 .635-.374.927-.965 1.371-.673.489-1.206 1.06-1.168 1.987l.003.217a.25.25 0 0 0 .25.246h.811a.25.25 0 0 0 .25-.25v-.105c0-.718.273-.927 1.01-1.486.609-.463 1.244-.977 1.244-2.056 0-1.511-1.276-2.241-2.673-2.241-1.267 0-2.655.59-2.75 2.286zm1.557 5.763c0 .533.425.927 1.01.927.609 0 1.028-.394 1.028-.927 0-.552-.42-.94-1.029-.94-.584 0-1.009.388-1.009.94z"
351 />
352 </svg>
353 }
354 }
355 };
356
357 html! {
358 if show {
359 <div
360 class={format!("top-0 left-0 fixed p-3 z-10 transition duration-300 ease-in bg-gray-900 bg-opacity-75 w-screen h-screen {}", props.container_class)}
361 >
362 <div
363 class={format!("absolute items-center {} {}", props.alert_class, position_class)}
364 >
365 { if props.show_close_button {
366 html! {
367 <button type="button" class="absolute top-0 right-0 p-2 m-4 text-white bg-black border rounded-xl border-2" onclick={on_cancel.clone()}>{"x"}</button>
368 }
369 } else {
370 html! {}
371 } }
372 <div class={props.icon_class}>{ icon_path }</div>
373 <strong class={props.title_class}>{ props.title }</strong>
374 <hr class="my-2 border-gray-600" />
375 <p class={props.message_class}>{ props.message }</p>
376 { if props.show_confirm_button {
377 html! {
378 <button class={props.confirm_button_class} onclick={on_confirm}>
379 {props.confirm_button_text}
380 </button>
381 }
382 } else {
383 html! {}
384 } }
385 { if props.show_cancel_button {
386 html! {
387 <button class={props.cancel_button_class} onclick={on_cancel.clone()}>
388 {props.cancel_button_text}
389 </button>
390 }
391 } else {
392 html! {}
393 } }
394 </div>
395 </div>
396 }
397 }
398}