├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── Dioxus.toml ├── LICENSE ├── README.md ├── examples ├── desktop.rs └── web.rs ├── src ├── assets │ └── toast.css ├── id.rs └── lib.rs └── website ├── .gitignore ├── Cargo.toml ├── Dioxus.toml └── src └── main.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Build 20 | run: cargo build --no-default-features --features web --example web --verbose 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | 13 | # Added by cargo 14 | 15 | /target 16 | 17 | /dist 18 | 19 | .DS_Store -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dioxus-toast" 3 | version = "0.6.0" 4 | authors = ["YuKun Liu "] 5 | repository = "https://github.com/mrxiaozhuox/dioxus-toast" 6 | license = "MIT" 7 | description = "Add toast support in your dioxus project" 8 | edition = "2021" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | [dependencies] 12 | chrono = { version = "0.4.38", features = ["wasmbind"] } 13 | dioxus = { version = "0.6.0" } 14 | tokio = { version = "1.39.3", features = ["time"], optional = true } 15 | gloo-timers = { version = "0.3.0", features = ["futures"], optional = true } 16 | 17 | [features] 18 | default = ["desktop"] 19 | desktop = ["dioxus/desktop", "tokio"] 20 | web = ["dioxus/web", "gloo-timers"] 21 | -------------------------------------------------------------------------------- /Dioxus.toml: -------------------------------------------------------------------------------- 1 | [application] 2 | 3 | # dioxus project name 4 | name = "dioxus-toast" 5 | 6 | # default platfrom 7 | # you can also use `dioxus serve/build --platform XXX` to use other platform 8 | # value: web | desktop 9 | default_platform = "desktop" 10 | 11 | # Web `build` & `serve` dist path 12 | out_dir = "dist" 13 | 14 | # resource (static) file folder 15 | public_dir = "public" 16 | 17 | [web.app] 18 | 19 | # HTML title tag content 20 | title = "dioxus | ⛺" 21 | 22 | [web.watcher] 23 | 24 | watch_path = ["src"] 25 | 26 | # include `assets` in web platform 27 | [web.resource] 28 | 29 | # CSS style file 30 | style = [] 31 | 32 | # Javascript code file 33 | script = [] 34 | 35 | [web.resource.dev] 36 | 37 | # Javascript code file 38 | # serve: [dev-server] only 39 | script = [] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 YuKun Liu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

Dioxus Toast

3 |

4 |
5 | 6 | 7 |
8 |

9 | Add toast support for your dioxus project. 10 |

11 |
12 | 13 | ```rust 14 | use dioxus::prelude::*; 15 | use dioxus_toast::{ToastInfo, ToastManager}; 16 | 17 | fn main() { 18 | launch(app) 19 | } 20 | 21 | fn app() -> Element { 22 | std::panic::set_hook(Box::new(|info| { 23 | println!("Panic: {}", info); 24 | })); 25 | 26 | let mut toast = use_signal(|| ToastManager::default()); 27 | 28 | rsx! { 29 | dioxus_toast::ToastFrame { 30 | manager: toast 31 | } 32 | div { 33 | button { 34 | onclick: move |_| { 35 | let _id = toast.write().popup(ToastInfo::simple("hello world")); 36 | println!("New Toast ID: {}", _id); 37 | }, 38 | "Normal Toast" 39 | } 40 | button { 41 | onclick: move |_| { 42 | let _id = toast.write().popup(ToastInfo::success("Hello World!", "Success")); 43 | println!("New Toast ID: {}", _id); 44 | }, 45 | "Success Toast" 46 | } 47 | button { 48 | onclick: move |_| { 49 | let _id = toast.write().popup(ToastInfo { 50 | heading: Some("top-right".into()), 51 | context: "Top Right Toast".into(), 52 | allow_toast_close: true, 53 | position: dioxus_toast::Position::TopRight, 54 | icon: None, 55 | hide_after: None 56 | }); 57 | }, 58 | "Top Right" 59 | } 60 | } 61 | } 62 | } 63 | 64 | ``` 65 | 66 | ## Use Toast for different component 67 | 68 | ```rust 69 | use dioxus::prelude::*; 70 | 71 | fn main() { 72 | launch(app) 73 | } 74 | 75 | fn app() -> Element { 76 | let toast = use_context_provider(|| Signal::new(ToastManager::default())); 77 | rsx! { 78 | ToastFrame { manager: toast } 79 | div { 80 | hello {} 81 | } 82 | } 83 | } 84 | 85 | #[component] 86 | fn hello() -> Element { 87 | // use_context can help you pass toast-manager to different components 88 | let mut toast: Signal = use_context(); 89 | rsx! { 90 | button { 91 | onclick: move |_| { 92 | let _ = toast.write().popup(ToastInfo::simple("hello world")); 93 | } 94 | "Click here!" 95 | } 96 | } 97 | } 98 | ``` 99 | -------------------------------------------------------------------------------- /examples/desktop.rs: -------------------------------------------------------------------------------- 1 | use dioxus::prelude::*; 2 | use dioxus_toast::{ToastInfo, ToastManager}; 3 | 4 | fn main() { 5 | launch(app) 6 | } 7 | 8 | fn app() -> Element { 9 | std::panic::set_hook(Box::new(|info| { 10 | println!("Panic: {}", info); 11 | })); 12 | 13 | let mut toast = use_signal(|| ToastManager::default()); 14 | 15 | rsx! { 16 | dioxus_toast::ToastFrame { manager: toast } 17 | div { 18 | button { 19 | onclick: move |_| { 20 | // let _id = toast.write().popup(ToastInfo { 21 | // heading:Some("Hello Dioxus".into()), 22 | // context:"hello world: Dioxus".into(), 23 | // allow_toast_close:true, 24 | // position:dioxus_toast::Position::BottomLeft, 25 | // icon: None, 26 | // hide_after: Some(5), 27 | // }); 28 | let _id = toast.write().popup(ToastInfo::simple("hello world")); 29 | println!("New Toast ID: {}", _id); 30 | }, 31 | "Normal Toast" 32 | } 33 | button { 34 | onclick: move |_| { 35 | let _id = toast.write().popup(ToastInfo::success("Hello World!", "Success")); 36 | println!("New Toast ID: {}", _id); 37 | }, 38 | "Success Toast" 39 | } 40 | button { 41 | onclick: move |_| { 42 | let _id = toast 43 | .write() 44 | .popup(ToastInfo { 45 | heading: Some("top-right".into()), 46 | context: "Top Right Toast".into(), 47 | allow_toast_close: true, 48 | position: dioxus_toast::Position::TopRight, 49 | icon: None, 50 | hide_after: None, 51 | }); 52 | }, 53 | "Top Right" 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /examples/web.rs: -------------------------------------------------------------------------------- 1 | use dioxus::prelude::*; 2 | use dioxus_toast::{ToastInfo, ToastManager}; 3 | 4 | fn main() { 5 | launch(app) 6 | } 7 | 8 | fn app() -> Element { 9 | std::panic::set_hook(Box::new(|info| { 10 | println!("Panic: {}", info); 11 | })); 12 | 13 | let mut toast = use_signal(|| ToastManager::default()); 14 | 15 | rsx! { 16 | dioxus_toast::ToastFrame { manager: toast } 17 | div { 18 | button { 19 | onclick: move |_| { 20 | // let _id = toast.write().popup(ToastInfo { 21 | // heading:Some("Hello Dioxus".into()), 22 | // context:"hello world: Dioxus".into(), 23 | // allow_toast_close:true, 24 | // position:dioxus_toast::Position::BottomLeft, 25 | // icon: None, 26 | // hide_after: Some(5), 27 | // }); 28 | let _id = toast.write().popup(ToastInfo::simple("hello world")); 29 | println!("New Toast ID: {}", _id); 30 | }, 31 | "Normal Toast" 32 | } 33 | button { 34 | onclick: move |_| { 35 | let _id = toast.write().popup(ToastInfo::success("Hello World!", "Success")); 36 | println!("New Toast ID: {}", _id); 37 | }, 38 | "Success Toast" 39 | } 40 | button { 41 | onclick: move |_| { 42 | let _id = toast 43 | .write() 44 | .popup(ToastInfo { 45 | heading: Some("top-right".into()), 46 | context: "Top Right Toast".into(), 47 | allow_toast_close: true, 48 | position: dioxus_toast::Position::TopRight, 49 | icon: None, 50 | hide_after: None, 51 | }); 52 | }, 53 | "Top Right" 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/assets/toast.css: -------------------------------------------------------------------------------- 1 | /* 2 | style from jquery-toast-plugin: 3 | https://github.com/kamranahmedse/jquery-toast-plugin 4 | */ 5 | 6 | .toast-wrap { 7 | display: block; 8 | position: fixed; 9 | width: 250px; 10 | pointer-events: none !important; 11 | margin: 0; 12 | padding: 0; 13 | letter-spacing: normal; 14 | z-index: 9000 !important; 15 | } 16 | 17 | .toast-wrap * { 18 | margin: 0; 19 | padding: 0; 20 | } 21 | 22 | .toast-wrap.bottom-left { 23 | bottom: 20px; 24 | left: 20px; 25 | } 26 | 27 | .toast-wrap.bottom-right { 28 | bottom: 20px; 29 | right: 40px; 30 | } 31 | 32 | .toast-wrap.top-left { 33 | top: 20px; 34 | left: 20px; 35 | } 36 | 37 | .toast-wrap.top-right { 38 | top: 20px; 39 | right: 40px; 40 | } 41 | 42 | .toast-single { 43 | display: block; 44 | width: 100%; 45 | padding: 10px; 46 | margin: 0px 0px 5px; 47 | border-radius: 4px; 48 | font-size: 12px; 49 | font-family: arial, sans-serif; 50 | line-height: 17px; 51 | position: relative; 52 | pointer-events: all !important; 53 | background-color: #444444; 54 | color: white; 55 | } 56 | 57 | .toast-single h2 { 58 | font-family: arial, sans-serif; 59 | font-size: 14px; 60 | margin: 0px 0px 7px; 61 | background: none; 62 | color: inherit; 63 | line-height: inherit; 64 | letter-spacing: normal; 65 | } 66 | 67 | .toast-single a { 68 | color: #eee; 69 | text-decoration: none; 70 | font-weight: bold; 71 | border-bottom: 1px solid white; 72 | padding-bottom: 3px; 73 | font-size: 12px; 74 | } 75 | 76 | .toast-single ul { 77 | margin: 0px 0px 0px 15px; 78 | background: none; 79 | padding: 0px; 80 | } 81 | 82 | .toast-single ul li { 83 | list-style-type: disc !important; 84 | line-height: 17px; 85 | background: none; 86 | margin: 0; 87 | padding: 0; 88 | letter-spacing: normal; 89 | } 90 | 91 | .close-toast-single { 92 | position: absolute; 93 | top: 3px; 94 | right: 7px; 95 | font-size: 14px; 96 | cursor: pointer; 97 | } 98 | 99 | .toast-loader { 100 | display: block; 101 | position: absolute; 102 | top: -2px; 103 | height: 5px; 104 | width: 0%; 105 | left: 0; 106 | border-radius: 5px; 107 | background: red; 108 | } 109 | 110 | .toast-loaded { 111 | width: 100%; 112 | } 113 | 114 | .has-icon { 115 | padding: 10px 10px 10px 50px; 116 | background-repeat: no-repeat; 117 | background-position: 10px; 118 | } 119 | 120 | .icon-info { 121 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII='); 122 | background-color: #31708f; 123 | color: #d9edf7; 124 | border-color: #bce8f1; 125 | } 126 | 127 | .icon-warning { 128 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII='); 129 | background-color: #8a6d3b; 130 | color: #fcf8e3; 131 | border-color: #faebcc; 132 | } 133 | 134 | .icon-error { 135 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII='); 136 | background-color: #a94442; 137 | color: #f2dede; 138 | border-color: #ebccd1; 139 | } 140 | 141 | .icon-success { 142 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg=='); 143 | color: #dff0d8; 144 | background-color: #3c763d; 145 | border-color: #d6e9c6; 146 | } -------------------------------------------------------------------------------- /src/id.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | #[derive(Debug, Clone)] 4 | pub struct ID(usize); 5 | 6 | impl ID { 7 | pub fn new() -> Self { 8 | Self(100000) 9 | } 10 | 11 | pub fn add(&mut self) -> usize { 12 | let current = self.0; 13 | if self.0 == usize::MAX { 14 | self.0 = 100000; 15 | } else { 16 | self.0 += 1; 17 | } 18 | 19 | current 20 | } 21 | } 22 | 23 | impl Display for ID { 24 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 25 | f.write_fmt(format_args!("{}", self.0)) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_snake_case)] 2 | 3 | mod id; 4 | 5 | use std::collections::BTreeMap; 6 | 7 | use dioxus::prelude::*; 8 | use id::ID; 9 | 10 | #[derive(Debug, Clone)] 11 | struct ToastManagerItem { 12 | info: ToastInfo, 13 | hide_after: Option, 14 | } 15 | 16 | #[derive(Debug)] 17 | pub struct ToastManager { 18 | list: BTreeMap, 19 | maximum_toast: u8, 20 | id_manager: ID, 21 | } 22 | 23 | impl ToastManager { 24 | pub fn new(maximum_toast: u8) -> Self { 25 | Self { 26 | list: BTreeMap::new(), 27 | maximum_toast, 28 | id_manager: ID::new(), 29 | } 30 | } 31 | 32 | pub fn popup(&mut self, info: ToastInfo) -> usize { 33 | let toast_id = self.id_manager.add(); 34 | 35 | if self.list.len() >= self.maximum_toast.into() { 36 | if let Some(result) = self.list.first_key_value() { 37 | let id = *result.0; 38 | println!("Deleted Toast ID: {:?}", id); 39 | self.list.remove(&id); 40 | } 41 | } 42 | 43 | let hide_after = info 44 | .hide_after 45 | .map(|duration| chrono::Local::now().timestamp() + duration as i64); 46 | 47 | self.list 48 | .insert(toast_id, ToastManagerItem { info, hide_after }); 49 | 50 | toast_id 51 | } 52 | 53 | pub fn remove(&mut self, id: usize) { 54 | self.list.remove(&id); 55 | } 56 | 57 | pub fn clear(&mut self) { 58 | self.list.clear(); 59 | } 60 | } 61 | 62 | impl Default for ToastManager { 63 | fn default() -> Self { 64 | Self { 65 | list: Default::default(), 66 | maximum_toast: 6, 67 | id_manager: ID::new(), 68 | } 69 | } 70 | } 71 | 72 | #[derive(Debug, PartialEq, Eq, Clone)] 73 | pub enum Position { 74 | BottomLeft, 75 | BottomRight, 76 | TopLeft, 77 | TopRight, 78 | } 79 | 80 | #[derive(Debug, PartialEq, Eq, Clone)] 81 | pub enum Icon { 82 | Success, 83 | Warning, 84 | Error, 85 | Info, 86 | } 87 | 88 | #[derive(Debug, Clone)] 89 | pub struct ToastInfo { 90 | pub heading: Option, 91 | pub context: String, 92 | pub allow_toast_close: bool, 93 | pub position: Position, 94 | pub icon: Option, 95 | pub hide_after: Option, 96 | } 97 | 98 | impl ToastInfo { 99 | pub fn simple(text: &str) -> Self { 100 | Self { 101 | heading: None, 102 | context: text.to_string(), 103 | allow_toast_close: true, 104 | position: Position::BottomLeft, 105 | icon: None, 106 | hide_after: Some(6), 107 | } 108 | } 109 | 110 | pub fn success(text: &str, heading: &str) -> Self { 111 | Self { 112 | heading: Some(heading.to_string()), 113 | context: text.to_string(), 114 | allow_toast_close: true, 115 | position: Position::BottomLeft, 116 | icon: Some(Icon::Success), 117 | hide_after: Some(6), 118 | } 119 | } 120 | 121 | pub fn warning(text: &str, heading: &str) -> Self { 122 | Self { 123 | heading: Some(heading.to_string()), 124 | context: text.to_string(), 125 | allow_toast_close: true, 126 | position: Position::BottomLeft, 127 | icon: Some(Icon::Warning), 128 | hide_after: Some(6), 129 | } 130 | } 131 | 132 | pub fn info(text: &str, heading: &str) -> Self { 133 | Self { 134 | heading: Some(heading.to_string()), 135 | context: text.to_string(), 136 | allow_toast_close: true, 137 | position: Position::BottomLeft, 138 | icon: Some(Icon::Info), 139 | hide_after: Some(6), 140 | } 141 | } 142 | 143 | pub fn error(text: &str, heading: &str) -> Self { 144 | Self { 145 | heading: Some(heading.to_string()), 146 | context: text.to_string(), 147 | allow_toast_close: true, 148 | position: Position::BottomLeft, 149 | icon: Some(Icon::Error), 150 | hide_after: Some(6), 151 | } 152 | } 153 | } 154 | 155 | #[derive(Props, Clone, PartialEq)] 156 | pub struct ToastFrameProps { 157 | manager: Signal, 158 | style: Option<&'static str>, 159 | } 160 | 161 | pub fn ToastFrame(props: ToastFrameProps) -> Element { 162 | let mut manager = props.manager; 163 | 164 | let toast_list = &manager.read().list; 165 | 166 | let mut bottom_left_ele: Vec = vec![]; 167 | let mut bottom_right_ele: Vec = vec![]; 168 | let mut top_left_ele: Vec = vec![]; 169 | let mut top_right_ele: Vec = vec![]; 170 | 171 | for (id, item) in toast_list.iter() { 172 | let current_id = *id; 173 | 174 | let icon_class = if let Some(icon) = &item.info.icon { 175 | let mut class = String::from("has-icon "); 176 | 177 | match icon { 178 | Icon::Success => class.push_str("icon-success"), 179 | Icon::Warning => class.push_str("icon-warning"), 180 | Icon::Error => class.push_str("icon-error"), 181 | Icon::Info => class.push_str("icon-info"), 182 | } 183 | 184 | class 185 | } else { 186 | String::new() 187 | }; 188 | 189 | let element = rsx! { 190 | div { class: "toast-single {icon_class}", id: "{id}", 191 | if item.info.allow_toast_close { 192 | div { 193 | class: "close-toast-single", 194 | onclick: move |_| { 195 | manager.write().list.remove(¤t_id); 196 | }, 197 | "×" 198 | } 199 | } else { 200 | div {} 201 | } 202 | if let Some(v) = &item.info.heading { 203 | h2 { class: "toast-heading", "{v}" } 204 | } else { 205 | div {} 206 | } 207 | 208 | span { dangerous_inner_html: "{item.info.context}" } 209 | } 210 | }; 211 | 212 | if item.info.position == Position::BottomLeft { 213 | bottom_left_ele.push(element?); 214 | } else if item.info.position == Position::BottomRight { 215 | bottom_right_ele.push(element?); 216 | } else if item.info.position == Position::TopLeft { 217 | top_left_ele.push(element?); 218 | } else if item.info.position == Position::TopRight { 219 | top_right_ele.push(element?); 220 | } 221 | } 222 | 223 | let _ = use_resource(move || async move { 224 | loop { 225 | let now = chrono::Local::now().timestamp(); 226 | manager.write().list.retain(|_, item| { 227 | if let Some(hide_after) = item.hide_after { 228 | now < hide_after 229 | } else { 230 | true 231 | } 232 | }); 233 | time_sleep(100).await; 234 | } 235 | }); 236 | 237 | let style = props.style.unwrap_or(include_str!("./assets/toast.css")); 238 | 239 | rsx! { 240 | div { class: "toast-scope", 241 | style { { style } } 242 | div { class: "toast-wrap bottom-left", id: "wrap-bottom-left", 243 | { bottom_left_ele.into_iter() } 244 | } 245 | div { class: "toast-wrap bottom-right", id: "wrap-bottom-right", 246 | { bottom_right_ele.into_iter() } 247 | } 248 | div { class: "toast-wrap top-left", id: "wrap-top-left", { top_left_ele.into_iter() } } 249 | div { class: "toast-wrap top-right", id: "wrap-top-right", { top_right_ele.into_iter() } } 250 | } 251 | } 252 | } 253 | 254 | #[cfg(feature = "web")] 255 | async fn time_sleep(interval: usize) { 256 | gloo_timers::future::TimeoutFuture::new(interval as u32).await; 257 | } 258 | 259 | #[cfg(feature = "desktop")] 260 | async fn time_sleep(interval: usize) { 261 | tokio::time::sleep(tokio::time::Duration::from_millis(interval as u64)).await; 262 | } 263 | -------------------------------------------------------------------------------- /website/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | 13 | # Added by cargo 14 | 15 | /target 16 | 17 | /dist 18 | 19 | .DS_Store -------------------------------------------------------------------------------- /website/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "website" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | dioxus = { version = "0.6.0", features = ["web"]} 10 | dioxus-toast = { path = "../", default-features = false, features = ["web"]} -------------------------------------------------------------------------------- /website/Dioxus.toml: -------------------------------------------------------------------------------- 1 | [application] 2 | 3 | # dioxus project name 4 | name = "toast-website" 5 | 6 | # default platfrom 7 | # you can also use `dioxus serve/build --platform XXX` to use other platform 8 | # value: web | desktop 9 | default_platform = "web" 10 | 11 | # Web `build` & `serve` dist path 12 | out_dir = "dist" 13 | 14 | # resource (static) file folder 15 | public_dir = "public" 16 | 17 | [web.app] 18 | 19 | # HTML title tag content 20 | title = "dioxus | ⛺" 21 | 22 | [web.watcher] 23 | 24 | watch_path = ["src"] 25 | 26 | # include `assets` in web platform 27 | [web.resource] 28 | 29 | # CSS style file 30 | style = [] 31 | 32 | # Javascript code file 33 | script = [] 34 | 35 | [web.resource.dev] 36 | 37 | # Javascript code file 38 | # serve: [dev-server] only 39 | script = [] -------------------------------------------------------------------------------- /website/src/main.rs: -------------------------------------------------------------------------------- 1 | use dioxus::prelude::*; 2 | use dioxus_toast::{ToastFrame, ToastInfo, ToastManager}; 3 | 4 | fn main() { 5 | launch(app); 6 | } 7 | 8 | fn app() -> Element { 9 | let mut toast = use_signal(|| ToastManager::default()); 10 | 11 | rsx! { 12 | ToastFrame { manager: toast } 13 | button { 14 | onclick: move |_| { 15 | toast.write().popup(ToastInfo::simple("123")); 16 | }, 17 | "T" 18 | } 19 | } 20 | } 21 | --------------------------------------------------------------------------------