Skip to main content

repose_material/material3/
snackbar.rs

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