Skip to main content

repose_material/material3/
snackbar.rs

1#![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/// Configuration for [`Snackbar`].
16#[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}