freya_components/
snackbar.rs

1use dioxus::prelude::*;
2use freya_elements as dioxus_elements;
3use freya_hooks::{
4    use_animation,
5    use_applied_theme,
6    AnimNum,
7    Ease,
8    Function,
9    SnackBarTheme,
10    SnackBarThemeWith,
11};
12
13/// `SnackBar` component. Use in combination with other components.
14///
15/// # Styling
16/// Inherits the [`SnackBarTheme`](freya_hooks::SnackBarTheme) theme.
17///
18/// # Example
19///
20/// ```rust
21/// # use freya::prelude::*;
22/// fn app() -> Element {
23///     let mut open = use_signal(|| false);
24///
25///     rsx!(
26///         rect {
27///             height: "100%",
28///             width: "100%",
29///             Button {
30///                 onpress: move |_| open.toggle(),
31///                 label { "Open" }
32///             }
33///             SnackBar {
34///                 open,
35///                 label {
36///                     "Hello, World!"
37///                 }
38///             }
39///         }
40///     )
41/// }
42/// # use freya_testing::prelude::*;
43/// # launch_doc_with_utils(|| {
44/// #   rsx!(
45/// #       Preview {
46/// #           SnackBar {
47/// #               open: true,
48/// #               label {
49/// #                   "Hello, World!"
50/// #               }
51/// #           }
52/// #       }
53/// #   )
54/// # }, (250., 250.).into(), |mut utils| async move {
55/// #   utils.wait_for_update().await;
56/// #   utils.wait_for_update().await;
57/// #   utils.wait_for_update().await;
58/// #   utils.wait_for_update().await;
59/// #   utils.wait_for_update().await;
60/// # utils.save_snapshot("./images/gallery_snackbar.png");
61/// # });
62/// ```
63///
64/// # Preview
65/// ![Snackbar Preview][snackbar]
66#[cfg_attr(feature = "docs",
67    doc = embed_doc_image::embed_image!("snackbar", "images/gallery_snackbar.png")
68)]
69#[allow(non_snake_case)]
70#[component]
71pub fn SnackBar(
72    /// Inner children of the SnackBar.
73    children: Element,
74    /// Open or not the SnackBar. You can pass a [ReadOnlySignal] as well.
75    open: ReadOnlySignal<bool>,
76    /// Theme override.
77    theme: Option<SnackBarThemeWith>,
78) -> Element {
79    let animation = use_animation(|_conf| {
80        AnimNum::new(50., 0.)
81            .time(200)
82            .ease(Ease::Out)
83            .function(Function::Expo)
84    });
85
86    use_effect(move || {
87        if open() {
88            animation.start();
89        } else if animation.peek_has_run_yet() {
90            animation.reverse();
91        }
92    });
93
94    let offset_y = animation.get().read().read();
95
96    rsx!(
97        rect {
98            width: "100%",
99            height: "40",
100            position: "absolute",
101            position_bottom: "0",
102            offset_y: "{offset_y}",
103            overflow: "clip",
104            SnackBarBox {
105                theme,
106                {children}
107            }
108        }
109    )
110}
111
112#[doc(hidden)]
113#[allow(non_snake_case)]
114#[component]
115pub fn SnackBarBox(children: Element, theme: Option<SnackBarThemeWith>) -> Element {
116    let SnackBarTheme { background, color } = use_applied_theme!(&theme, snackbar);
117
118    rsx!(
119        rect {
120            width: "fill",
121            height: "40",
122            background: "{background}",
123            overflow: "clip",
124            padding: "10",
125            color: "{color}",
126            direction: "horizontal",
127            layer: "-1000",
128            {children}
129        }
130    )
131}
132
133#[cfg(test)]
134mod test {
135    use std::time::Duration;
136
137    use dioxus::prelude::use_signal;
138    use freya::prelude::*;
139    use freya_testing::prelude::*;
140    use tokio::time::sleep;
141
142    #[tokio::test]
143    pub async fn snackbar() {
144        fn snackbar_app() -> Element {
145            let mut open = use_signal(|| false);
146
147            rsx!(
148                rect {
149                    height: "100%",
150                    width: "100%",
151                    Button {
152                        onpress: move |_|  open.toggle(),
153                        label { "Open" }
154                    }
155                    SnackBar {
156                        open,
157                        label {
158                            "Hello, World!"
159                        }
160                    }
161                }
162            )
163        }
164
165        let mut utils = launch_test(snackbar_app);
166        let root = utils.root();
167        let snackbar_box = root.get(0).get(1).get(0);
168        utils.wait_for_update().await;
169
170        // Snackbar is closed.
171        assert!(!snackbar_box.is_visible());
172
173        // Open the snackbar by clicking at the button
174        utils.click_cursor((15., 15.)).await;
175
176        // Wait a bit for the snackbar to open up
177        utils.wait_for_update().await;
178        sleep(Duration::from_millis(15)).await;
179        utils.wait_for_update().await;
180
181        // Snackbar is visible.
182        assert!(snackbar_box.is_visible());
183    }
184}