repose_material/material3/
snackbar.rs1#![allow(non_snake_case)]
2
3use std::rc::Rc;
4
5use crate::{Icon, Symbol};
6use repose_core::*;
7use repose_ui::{
8 Box, Column, Row, Spacer, Text, TextStyle, ViewExt, anim::animate_f32_from,
9 overlay::SnackbarAction, overlay::snackbar_is_dismissing,
10};
11
12use super::*;
13
14#[derive(Clone)]
16pub struct SnackbarConfig {
17 pub modifier: Modifier,
18 pub container_color: Color,
19 pub content_color: Color,
20 pub action_color: Color,
21 pub dismiss_action_content_color: Color,
22 pub action_on_new_line: bool,
23 pub show_dismiss_action: bool,
25 pub on_dismiss: Option<Rc<dyn Fn()>>,
27 pub shape_radius: f32,
28 pub min_height: f32,
29 pub min_width: f32,
30 pub max_width: f32,
31}
32
33impl std::fmt::Debug for SnackbarConfig {
34 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35 f.debug_struct("SnackbarConfig")
36 .field("modifier", &self.modifier)
37 .field("container_color", &self.container_color)
38 .field("content_color", &self.content_color)
39 .field("action_color", &self.action_color)
40 .field(
41 "dismiss_action_content_color",
42 &self.dismiss_action_content_color,
43 )
44 .field("action_on_new_line", &self.action_on_new_line)
45 .field("show_dismiss_action", &self.show_dismiss_action)
46 .field("on_dismiss", &self.on_dismiss.as_ref().map(|_| ".."))
47 .field("shape_radius", &self.shape_radius)
48 .field("min_height", &self.min_height)
49 .field("min_width", &self.min_width)
50 .field("max_width", &self.max_width)
51 .finish()
52 }
53}
54
55impl Default for SnackbarConfig {
56 fn default() -> Self {
57 Self {
58 modifier: Modifier::new(),
59 container_color: SnackbarDefaults::container_color(),
60 content_color: SnackbarDefaults::content_color(),
61 action_color: SnackbarDefaults::action_color(),
62 dismiss_action_content_color: SnackbarDefaults::dismiss_action_content_color(),
63 action_on_new_line: false,
64 show_dismiss_action: false,
65 on_dismiss: None,
66 shape_radius: SnackbarDefaults::SHAPE_RADIUS,
67 min_height: SnackbarDefaults::MIN_HEIGHT,
68 min_width: SnackbarDefaults::MIN_WIDTH,
69 max_width: SnackbarDefaults::MAX_WIDTH,
70 }
71 }
72}
73
74pub fn Snackbar(
75 message: impl Into<String>,
76 action: Option<SnackbarAction>,
77 modifier: Modifier,
78 config: SnackbarConfig,
79) -> View {
80 let msg = message.into();
81 let th = theme();
82 let bg = config.container_color;
83 let fg = config.content_color;
84 let action_color = config.action_color;
85
86 let dismissing = snackbar_is_dismissing();
87
88 let slide_target = if dismissing { 80.0 } else { 0.0 };
89 let slide = animate_f32_from("snackbar_slide", 80.0, slide_target, th.motion.overlay);
90
91 let alpha_target = if dismissing { 0.0 } else { 1.0 };
92 let alpha = animate_f32_from("snackbar_alpha", 0.0, alpha_target, th.motion.overlay);
93
94 let snackbar = Box(Modifier::new()
95 .translate(0.0, slide)
96 .alpha(alpha)
97 .min_height(config.min_height)
98 .min_width(config.min_width)
99 .max_width(config.max_width)
100 .background(bg)
101 .clip_rounded(config.shape_radius)
102 .shadow(th.elevation.level3, 0.0));
103
104 let dismiss_btn = if config.show_dismiss_action {
105 let d = config.on_dismiss.clone();
106 Some(IconButton(
107 Icon(Symbol::new("close", '\u{E5CD}')).size(24.0),
108 move || {
109 if let Some(cb) = &d {
110 cb();
111 }
112 },
113 IconButtonConfig {
114 colors: IconButtonColors {
115 container_color: Color::TRANSPARENT,
116 content_color: config.dismiss_action_content_color,
117 disabled_container_color: Color::TRANSPARENT,
118 disabled_content_color: config
119 .dismiss_action_content_color
120 .with_alpha_f32(0.38),
121 },
122 container_size: Some(48.0),
123 ..Default::default()
124 },
125 ))
126 } else {
127 None
128 };
129
130 let content = if config.action_on_new_line {
131 Column(Modifier::new().padding_values(PaddingValues {
132 left: 16.0,
133 right: 8.0,
134 top: 0.0,
135 bottom: 0.0,
136 }))
137 .child((
138 Text(msg)
139 .modifier(Modifier::new().padding_values(PaddingValues {
140 left: 0.0,
141 right: 0.0,
142 top: 14.0,
143 bottom: 14.0,
144 }))
145 .color(fg)
146 .size(th.typography.body_medium)
147 .max_lines(2)
148 .overflow_ellipsize(),
149 action
150 .map(|a| {
151 let label = a.label.clone();
152 Row(Modifier::new()
153 .fill_max_width()
154 .justify_content(repose_core::JustifyContent::END))
155 .child(TextButton(
156 Modifier::new(),
157 move || (a.on_click)(),
158 ButtonConfig {
159 colors: Some(ButtonColors {
160 container_color: Color::TRANSPARENT,
161 content_color: action_color,
162 disabled_container_color: Color::TRANSPARENT,
163 disabled_content_color: action_color.with_alpha_f32(0.38),
164 }),
165 ..Default::default()
166 },
167 || Text(label).size(th.typography.label_large).single_line(),
168 ))
169 })
170 .unwrap_or(Box(Modifier::new())),
171 dismiss_btn.unwrap_or(Box(Modifier::new())),
172 ))
173 } else {
174 Row(Modifier::new()
175 .fill_max_width()
176 .padding_values(PaddingValues {
177 left: 16.0,
178 right: 8.0,
179 top: 0.0,
180 bottom: 0.0,
181 })
182 .align_items(repose_core::AlignItems::CENTER))
183 .child((
184 Text(msg)
185 .modifier(Modifier::new().padding_values(PaddingValues {
186 left: 0.0,
187 right: 0.0,
188 top: 14.0,
189 bottom: 14.0,
190 }))
191 .color(fg)
192 .size(th.typography.body_medium)
193 .max_lines(2)
194 .overflow_ellipsize(),
195 Spacer(),
196 action
197 .map(|a| {
198 let label = a.label.clone();
199 TextButton(
200 Modifier::new(),
201 move || (a.on_click)(),
202 ButtonConfig {
203 colors: Some(ButtonColors {
204 container_color: Color::TRANSPARENT,
205 content_color: action_color,
206 disabled_container_color: Color::TRANSPARENT,
207 disabled_content_color: action_color.with_alpha_f32(0.38),
208 }),
209 ..Default::default()
210 },
211 || Text(label).size(th.typography.label_large).single_line(),
212 )
213 })
214 .unwrap_or(Box(Modifier::new())),
215 dismiss_btn.unwrap_or(Box(Modifier::new())),
216 ))
217 };
218 let snackbar = snackbar.child(with_content_color(fg, move || content));
219
220 Box(Modifier::new()
221 .absolute()
222 .offset_bottom(0.0)
223 .fill_max_width()
224 .justify_content(repose_core::JustifyContent::CENTER)
225 .then(modifier))
226 .child(snackbar)
227}