repose_material/material3/
snackbar.rs1#![allow(non_snake_case)]
2
3
4use repose_core::*;
5use repose_ui::{
6 Box, Column, Row, Spacer, Text, TextStyle,
7 ViewExt,
8 anim::animate_f32_from,
9 overlay::SnackbarAction,
10 overlay::snackbar_is_dismissing,
11};
12
13use super::*;
14
15#[derive(Clone, Debug)]
17pub struct SnackbarConfig {
18 pub modifier: Modifier,
19 pub container_color: Color,
20 pub content_color: Color,
21 pub action_color: Color,
22 pub dismiss_action_content_color: Color,
23 pub action_on_new_line: bool,
24 pub shape_radius: f32,
25 pub min_height: f32,
26 pub min_width: f32,
27 pub max_width: f32,
28}
29
30impl Default for SnackbarConfig {
31 fn default() -> Self {
32 Self {
33 modifier: Modifier::new(),
34 container_color: SnackbarDefaults::container_color(),
35 content_color: SnackbarDefaults::content_color(),
36 action_color: SnackbarDefaults::action_color(),
37 dismiss_action_content_color: SnackbarDefaults::dismiss_action_content_color(),
38 action_on_new_line: false,
39 shape_radius: SnackbarDefaults::SHAPE_RADIUS,
40 min_height: SnackbarDefaults::MIN_HEIGHT,
41 min_width: SnackbarDefaults::MIN_WIDTH,
42 max_width: SnackbarDefaults::MAX_WIDTH,
43 }
44 }
45}
46
47pub fn Snackbar(
48 message: impl Into<String>,
49 action: Option<SnackbarAction>,
50 modifier: Modifier,
51 config: SnackbarConfig,
52) -> View {
53 let msg = message.into();
54 let th = theme();
55 let bg = config.container_color;
56 let fg = config.content_color;
57 let action_color = config.action_color;
58
59 let dismissing = snackbar_is_dismissing();
60
61 let slide_target = if dismissing { 80.0 } else { 0.0 };
62 let slide = animate_f32_from("snackbar_slide", 80.0, slide_target, th.motion.overlay);
63
64 let alpha_target = if dismissing { 0.0 } else { 1.0 };
65 let alpha = animate_f32_from("snackbar_alpha", 0.0, alpha_target, th.motion.overlay);
66
67 let snackbar = Box(Modifier::new()
68 .translate(0.0, slide)
69 .alpha(alpha)
70 .min_height(48.0)
71 .min_width(280.0)
72 .max_width(600.0)
73 .background(bg)
74 .clip_rounded(config.shape_radius));
75
76 let snackbar = if config.action_on_new_line {
77 snackbar.child(
78 Column(Modifier::new().padding_values(PaddingValues {
79 left: 16.0,
80 right: 8.0,
81 top: 0.0,
82 bottom: 0.0,
83 }))
84 .child((
85 Text(msg)
86 .modifier(Modifier::new().padding_values(PaddingValues {
87 left: 0.0,
88 right: 0.0,
89 top: 14.0,
90 bottom: 14.0,
91 }))
92 .color(fg)
93 .size(th.typography.body_medium)
94 .max_lines(2)
95 .overflow_ellipsize(),
96 action
97 .map(|a| {
98 let label = a.label.clone();
99 Row(Modifier::new()
100 .fill_max_width()
101 .justify_content(repose_core::JustifyContent::END))
102 .child(TextButton(
103 Modifier::new(),
104 move || (a.on_click)(),
105 ButtonConfig::default(),
106 || {
107 Text(label)
108 .color(action_color)
109 .size(th.typography.label_large)
110 .single_line()
111 },
112 ))
113 })
114 .unwrap_or(Box(Modifier::new())),
115 )),
116 )
117 } else {
118 snackbar.child(
119 Row(Modifier::new()
120 .fill_max_width()
121 .padding_values(PaddingValues {
122 left: 16.0,
123 right: 8.0,
124 top: 0.0,
125 bottom: 0.0,
126 })
127 .align_items(repose_core::AlignItems::CENTER))
128 .child((
129 Text(msg)
130 .modifier(Modifier::new().padding_values(PaddingValues {
131 left: 0.0,
132 right: 0.0,
133 top: 14.0,
134 bottom: 14.0,
135 }))
136 .color(fg)
137 .size(th.typography.body_medium)
138 .max_lines(2)
139 .overflow_ellipsize(),
140 Spacer(),
141 action
142 .map(|a| {
143 let label = a.label.clone();
144 TextButton(
145 Modifier::new(),
146 move || (a.on_click)(),
147 ButtonConfig::default(),
148 || {
149 Text(label)
150 .color(action_color)
151 .size(th.typography.label_large)
152 .single_line()
153 },
154 )
155 })
156 .unwrap_or(Box(Modifier::new())),
157 )),
158 )
159 };
160
161 Box(Modifier::new()
162 .absolute()
163 .offset_bottom(0.0)
164 .fill_max_width()
165 .justify_content(repose_core::JustifyContent::CENTER)
166 .then(modifier))
167 .child(snackbar)
168}