Skip to main content

21_toasts/
21_toasts.rs

1//! Example 21: Toast Notifications
2//!
3//! Demonstrates ephemeral toast notifications that appear in the corner
4//! of the screen and auto-dismiss after a duration.
5//!
6//! Run with: `cargo run -p telex-tui --example 21_toasts`
7
8use crossterm::event::KeyCode;
9use std::time::Duration;
10use telex::prelude::*;
11use telex::Color;
12
13telex::require_api!(0, 2);
14
15fn main() {
16    telex::run_with_theme(App, telex::theme::Theme::nord()).unwrap();
17}
18
19struct App;
20
21impl Component for App {
22    fn render(&self, cx: Scope) -> View {
23        let show_help = state!(cx, || false);
24
25        // F1 toggles help
26        cx.use_command(
27            KeyBinding::key(KeyCode::F(1)),
28            with!(show_help => move || show_help.update(|v| *v = !*v)),
29        );
30
31        // Create a toast queue with 3 second default duration
32        let toasts = state!(cx, || ToastQueue::with_duration(Duration::from_secs(3)));
33        let position = state!(cx, || ToastPosition::BottomRight);
34
35        // Buttons to trigger different toast types
36        let show_info = with!(toasts => move || {
37            toasts.get().info("This is an informational message");
38        });
39
40        let show_success = with!(toasts => move || {
41            toasts.get().success("Operation completed successfully!");
42        });
43
44        let show_warning = with!(toasts => move || {
45            toasts.get().warning("Warning: This action cannot be undone");
46        });
47
48        let show_error = with!(toasts => move || {
49            toasts.get().error("Error: Connection failed");
50        });
51
52        let show_long_error = with!(toasts => move || {
53            toasts.get().error_long("Critical Error: Server not responding. Check your network.");
54        });
55
56        let clear_all = with!(toasts => move || {
57            toasts.get().clear();
58        });
59
60        // Position cycling
61        let cycle_position = with!(position => move || {
62            let next = match position.get() {
63                ToastPosition::TopRight => ToastPosition::TopLeft,
64                ToastPosition::TopLeft => ToastPosition::BottomLeft,
65                ToastPosition::BottomLeft => ToastPosition::BottomRight,
66                ToastPosition::BottomRight => ToastPosition::TopRight,
67            };
68            position.set(next);
69        });
70
71        let position_name = match position.get() {
72            ToastPosition::TopRight => "Top Right",
73            ToastPosition::TopLeft => "Top Left",
74            ToastPosition::BottomLeft => "Bottom Left",
75            ToastPosition::BottomRight => "Bottom Right",
76        };
77
78        View::vstack()
79            .spacing(1)
80            .child(
81                // Header
82                View::boxed()
83                    .border(true)
84                    .padding(1)
85                    .child(
86                        View::vstack()
87                            .child(View::styled_text("Toast Notifications Demo").bold().build())
88                            .child(
89                                View::styled_text("Click buttons to show different toast types")
90                                    .dim()
91                                    .build(),
92                            )
93                            .build(),
94                    )
95                    .build(),
96            )
97            .child(
98                // Main content
99                View::boxed()
100                    .flex(1)
101                    .border(true)
102                    .padding(1)
103                    .child(
104                        View::vstack()
105                            .spacing(1)
106                            .child(View::text("Toast Types:"))
107                            .child(
108                                View::hstack()
109                                    .spacing(2)
110                                    .child(View::button().label("Info").on_press(show_info).build())
111                                    .child(
112                                        View::button()
113                                            .label("Success")
114                                            .on_press(show_success)
115                                            .build(),
116                                    )
117                                    .child(
118                                        View::button()
119                                            .label("Warning")
120                                            .on_press(show_warning)
121                                            .build(),
122                                    )
123                                    .child(
124                                        View::button().label("Error").on_press(show_error).build(),
125                                    )
126                                    .build(),
127                            )
128                            .child(View::gap(1))
129                            .child(View::text("Other Actions:"))
130                            .child(
131                                View::hstack()
132                                    .spacing(2)
133                                    .child(
134                                        View::button()
135                                            .label("Long Error")
136                                            .on_press(show_long_error)
137                                            .build(),
138                                    )
139                                    .child(
140                                        View::button()
141                                            .label("Clear All")
142                                            .on_press(clear_all)
143                                            .build(),
144                                    )
145                                    .build(),
146                            )
147                            .child(View::gap(1))
148                            .child(View::text(format!("Position: {}", position_name)))
149                            .child(
150                                View::button()
151                                    .label("Change Position")
152                                    .on_press(cycle_position)
153                                    .build(),
154                            )
155                            .child(View::spacer())
156                            .child(
157                                View::styled_text(format!("Active toasts: {}", toasts.get().len()))
158                                    .color(Color::Yellow)
159                                    .build(),
160                            )
161                            .child(
162                                View::styled_text("Toasts auto-dismiss after 3 seconds")
163                                    .dim()
164                                    .build(),
165                            )
166                            .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
167                            .build(),
168                    )
169                    .build(),
170            )
171            .child(
172                // Toast container - renders the toast stack
173                View::toast_container()
174                    .from_queue(&toasts.get())
175                    .position(position.get())
176                    .max_visible(5)
177                    .width(40)
178                    .build(),
179            )
180            .child(
181                View::modal()
182                    .visible(show_help.get())
183                    .title("Example 21: Toasts")
184                    .on_dismiss(with!(show_help => move || show_help.set(false)))
185                    .child(
186                        View::vstack()
187                            .child(View::styled_text("What you're seeing").bold().build())
188                            .child(View::text("• Toast notifications in corner"))
189                            .child(View::text("• Auto-dismiss after 3 seconds"))
190                            .child(View::text(
191                                "• Multiple toast types (info/success/warn/error)",
192                            ))
193                            .child(View::gap(1))
194                            .child(View::styled_text("Key concepts").bold().build())
195                            .child(View::text("• ToastQueue manages notifications"))
196                            .child(View::text("• View::toast_container() renders them"))
197                            .child(View::text("• .position() controls corner placement"))
198                            .child(View::text("• .max_visible() limits shown toasts"))
199                            .child(View::gap(1))
200                            .child(View::styled_text("Try this").bold().build())
201                            .child(View::text("• Click different toast type buttons"))
202                            .child(View::text("• Change position to see toasts move"))
203                            .child(View::text("• Spam buttons to stack toasts"))
204                            .child(View::gap(1))
205                            .child(View::styled_text("Next up").bold().build())
206                            .child(View::text("→ 22_forms: form validation"))
207                            .child(View::gap(1))
208                            .child(View::styled_text("Press Escape to close").dim().build())
209                            .build(),
210                    )
211                    .build(),
212            )
213            .build()
214    }
215}