23_modal/23_modal.rs
1//! Example 23: Modal Dialogs
2//!
3//! Demonstrates modal overlay dialogs including confirm dialogs,
4//! alert dialogs, and custom modal content.
5//!
6//! Run with: `cargo run -p telex-tui --example 23_modal`
7
8use crossterm::event::KeyCode;
9use telex::prelude::*;
10use telex::Color;
11
12telex::require_api!(0, 2);
13
14fn main() {
15 telex::run_with_theme(App, telex::theme::Theme::nord()).unwrap();
16}
17
18struct App;
19
20impl Component for App {
21 fn render(&self, cx: Scope) -> View {
22 let show_help = state!(cx, || false);
23
24 // F1 toggles help
25 cx.use_command(
26 KeyBinding::key(KeyCode::F(1)),
27 with!(show_help => move || show_help.update(|v| *v = !*v)),
28 );
29
30 // Modal visibility states
31 let show_confirm = state!(cx, || false);
32 let show_alert = state!(cx, || false);
33 let show_custom = state!(cx, || false);
34
35 // App state that modals can modify
36 let deleted_count = state!(cx, || 0);
37 let custom_input = state!(cx, String::new);
38 let last_action = state!(cx, || "No action yet".to_string());
39
40 // Handlers
41 let open_confirm = with!(show_confirm => move || {
42 show_confirm.set(true);
43 });
44
45 let open_alert = with!(show_alert => move || {
46 show_alert.set(true);
47 });
48
49 let open_custom = with!(show_custom => move || {
50 show_custom.set(true);
51 });
52
53 let on_confirm_yes = with!(show_confirm, deleted_count, last_action => move || {
54 deleted_count.set(deleted_count.get() + 1);
55 last_action.set(format!("Deleted item #{}", deleted_count.get()));
56 show_confirm.set(false);
57 });
58
59 let on_confirm_no = with!(show_confirm, last_action => move || {
60 last_action.set("Cancelled delete".to_string());
61 show_confirm.set(false);
62 });
63
64 let on_alert_dismiss = with!(show_alert, last_action => move || {
65 last_action.set("Dismissed alert".to_string());
66 show_alert.set(false);
67 });
68
69 let on_custom_save = with!(show_custom, custom_input, last_action => move || {
70 let value = custom_input.get();
71 if !value.is_empty() {
72 last_action.set(format!("Saved: {}", value));
73 custom_input.set(String::new());
74 }
75 show_custom.set(false);
76 });
77
78 let on_custom_dismiss = with!(show_custom => move || {
79 show_custom.set(false);
80 });
81
82 View::vstack()
83 .spacing(1)
84 .child(
85 // Header
86 View::boxed()
87 .border(true)
88 .padding(1)
89 .child(
90 View::vstack()
91 .child(View::styled_text("Modal Dialogs Demo").bold().build())
92 .child(
93 View::styled_text("Click buttons to open different modal types")
94 .dim()
95 .build(),
96 )
97 .build(),
98 )
99 .build(),
100 )
101 .child(
102 // Main content
103 View::boxed()
104 .flex(1)
105 .border(true)
106 .padding(1)
107 .child(
108 View::vstack()
109 .spacing(1)
110 .child(View::text("Modal Types:"))
111 .child(
112 View::hstack()
113 .spacing(2)
114 .child(
115 View::button()
116 .label("Confirm Dialog")
117 .on_press(open_confirm)
118 .build(),
119 )
120 .child(
121 View::button()
122 .label("Alert Dialog")
123 .on_press(open_alert)
124 .build(),
125 )
126 .child(
127 View::button()
128 .label("Custom Modal")
129 .on_press(open_custom)
130 .build(),
131 )
132 .build(),
133 )
134 .child(View::spacer())
135 .child(
136 View::styled_text(format!(
137 "Deleted items: {}",
138 deleted_count.get()
139 ))
140 .color(Color::Yellow)
141 .build(),
142 )
143 .child(
144 View::styled_text(format!("Last action: {}", last_action.get()))
145 .dim()
146 .build(),
147 )
148 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
149 .build(),
150 )
151 .build(),
152 )
153 // Help modal
154 .child(
155 View::modal()
156 .visible(show_help.get())
157 .title("Example 23: Modal")
158 .on_dismiss(with!(show_help => move || show_help.set(false)))
159 .child(
160 View::vstack()
161 .child(View::styled_text("What you're seeing").bold().build())
162 .child(View::text("• Confirm, alert, and custom modals"))
163 .child(View::text("• Modal focus containment"))
164 .child(View::text("• Escape to dismiss"))
165 .child(View::gap(1))
166 .child(View::styled_text("Key concepts").bold().build())
167 .child(View::text("• View::modal() creates overlay"))
168 .child(View::text("• .visible() controls show/hide"))
169 .child(View::text("• .on_dismiss() handles Escape"))
170 .child(View::text("• Focus trapped in open modal"))
171 .child(View::gap(1))
172 .child(View::styled_text("Try this").bold().build())
173 .child(View::text("• Open confirm, click Yes/No"))
174 .child(View::text("• Custom modal has text input"))
175 .child(View::text("• Press Escape to close modals"))
176 .child(View::gap(1))
177 .child(View::styled_text("Next up").bold().build())
178 .child(View::text("→ 24_async_data: async loading"))
179 .child(View::gap(1))
180 .child(View::styled_text("Press Escape to close").dim().build())
181 .build(),
182 )
183 .build(),
184 )
185 // Confirm dialog modal
186 .child(
187 View::modal()
188 .visible(show_confirm.get())
189 .title("Confirm Delete")
190 .on_dismiss(on_confirm_no.clone())
191 .width(40)
192 .height(30)
193 .child(
194 View::vstack()
195 .spacing(1)
196 .child(View::text("Are you sure you want to delete this item?"))
197 .child(View::text("This action cannot be undone."))
198 .child(View::spacer())
199 .child(
200 View::hstack()
201 .spacing(2)
202 .child(
203 View::button()
204 .label("Yes, Delete")
205 .on_press(on_confirm_yes)
206 .build(),
207 )
208 .child(
209 View::button()
210 .label("Cancel")
211 .on_press(on_confirm_no)
212 .build(),
213 )
214 .build(),
215 )
216 .build(),
217 )
218 .build(),
219 )
220 // Alert dialog modal
221 .child(
222 View::modal()
223 .visible(show_alert.get())
224 .title("Alert")
225 .on_dismiss(on_alert_dismiss.clone())
226 .width(50)
227 .height(25)
228 .child(
229 View::vstack()
230 .spacing(1)
231 .child(
232 View::styled_text("Operation completed successfully!")
233 .color(Color::Green)
234 .build(),
235 )
236 .child(View::text("Your changes have been saved."))
237 .child(View::spacer())
238 .child(
239 View::button()
240 .label("OK")
241 .on_press(on_alert_dismiss)
242 .build(),
243 )
244 .build(),
245 )
246 .build(),
247 )
248 // Custom modal with input
249 .child(
250 View::modal()
251 .visible(show_custom.get())
252 .title("Enter Details")
253 .on_dismiss(on_custom_dismiss.clone())
254 .width(50)
255 .height(40)
256 .child(
257 View::vstack()
258 .spacing(1)
259 .child(View::text("Enter a value:"))
260 .child(
261 View::text_input()
262 .value(custom_input.get())
263 .placeholder("Type something...")
264 .on_change(with!(custom_input => move |v: String| {
265 custom_input.set(v);
266 }))
267 .build(),
268 )
269 .child(View::spacer())
270 .child(
271 View::hstack()
272 .spacing(2)
273 .child(
274 View::button()
275 .label("Save")
276 .on_press(on_custom_save)
277 .build(),
278 )
279 .child(
280 View::button()
281 .label("Cancel")
282 .on_press(on_custom_dismiss)
283 .build(),
284 )
285 .build(),
286 )
287 .build(),
288 )
289 .build(),
290 )
291 .build()
292 }
293}