Skip to main content

05_todo_list/
05_todo_list.rs

1//! Example 05: Todo List
2//!
3//! Demonstrates TextInput, List, and state management patterns.
4//!
5//! Run with: cargo run -p telex-tui --example 05_todo_list
6
7use crossterm::event::KeyCode;
8use crossterm::style::Color;
9use telex::prelude::*;
10
11telex::require_api!(0, 2);
12
13fn main() {
14    telex::run_with_theme(App, telex::theme::Theme::nord()).unwrap();
15}
16
17struct App;
18
19impl Component for App {
20    fn render(&self, cx: Scope) -> View {
21        let items = state!(cx, || {
22            vec![
23                "Learn Telex".to_string(),
24                "Build something cool".to_string(),
25            ]
26        });
27        let input_value = state!(cx, String::new);
28        let selected = state!(cx, || 0usize);
29        let show_help = state!(cx, || false);
30
31        // F1 toggles help
32        cx.use_command(
33            KeyBinding::key(KeyCode::F(1)),
34            with!(show_help => move || show_help.update(|v| *v = !*v)),
35        );
36
37        // Add new item on submit
38        let on_submit = with!(items, input_value => move || {
39            let text = input_value.get();
40            if !text.is_empty() {
41                items.update(|v| v.push(text));
42                input_value.set(String::new());
43            }
44        });
45
46        // Handle input changes
47        let on_change = with!(input_value => move |text: String| {
48            input_value.set(text);
49        });
50
51        // Delete selected item
52        let on_delete = with!(items, selected => move || {
53            let idx = selected.get();
54            items.update(|v| {
55                if idx < v.len() {
56                    v.remove(idx);
57                    // Adjust selection if needed
58                    if idx > 0 && idx >= v.len() {
59                        selected.set(idx - 1);
60                    }
61                }
62            });
63        });
64
65        // Track selection
66        let on_select = with!(selected => move |idx: usize| {
67            selected.set(idx);
68        });
69
70        let item_count = items.get().len();
71
72        View::vstack()
73            .child(
74                View::styled_text("Todo List")
75                    .color(Color::Cyan)
76                    .bold()
77                    .build(),
78            )
79            .child(View::gap(1))
80            .child(
81                View::text_input()
82                    .value(input_value.get())
83                    .placeholder("Type something to add...")
84                    .on_change(on_change)
85                    .on_submit(on_submit)
86                    .build(),
87            )
88            .child(View::gap(1))
89            .child(if item_count > 0 {
90                View::list()
91                    .items(items.get())
92                    .selected(selected.get())
93                    .on_select(on_select)
94                    .build()
95            } else {
96                View::styled_text("No items yet").dim().build()
97            })
98            .child(View::gap(1))
99            .child(
100                View::hstack()
101                    .child(View::button().label("Delete").on_press(on_delete).build())
102                    .build(),
103            )
104            .child(View::gap(1))
105            .child(
106                View::styled_text("Tab navigate • Enter add/select • F1 help • Ctrl+Q quit")
107                    .dim()
108                    .build(),
109            )
110            .child(
111                View::modal()
112                    .visible(show_help.get())
113                    .title("Example 05: Todo List")
114                    .on_dismiss(with!(show_help => move || show_help.set(false)))
115                    .child(
116                        View::vstack()
117                            .child(View::styled_text("What you're seeing").bold().build())
118                            .child(View::text("• View::text_input() for text entry"))
119                            .child(View::text("• View::list() for displaying items"))
120                            .child(View::text("• on_submit callback for Enter key"))
121                            .child(View::gap(1))
122                            .child(View::styled_text("Key concepts").bold().build())
123                            .child(View::text("• Controlled input: value + on_change"))
124                            .child(View::text("• Vec<String> state for list items"))
125                            .child(View::text("• Conditional rendering (empty state)"))
126                            .child(View::gap(1))
127                            .child(View::styled_text("Try this").bold().build())
128                            .child(View::text("• Type something and press Enter to add"))
129                            .child(View::text("• Use ↑/↓ to select, then Delete button"))
130                            .child(View::text("• Delete all items to see empty state"))
131                            .child(View::gap(1))
132                            .child(View::styled_text("Next up").bold().build())
133                            .child(View::text("→ 06_log_viewer: streaming text content"))
134                            .child(View::gap(1))
135                            .child(View::styled_text("Press Escape to close").dim().build())
136                            .build(),
137                    )
138                    .build(),
139            )
140            .build()
141    }
142}