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