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}