05_todo_list/
05_todo_list.rs1use 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 cx.use_command(
33 KeyBinding::key(KeyCode::F(1)),
34 with!(show_help => move || show_help.update(|v| *v = !*v)),
35 );
36
37 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 let on_change = with!(input_value => move |text: String| {
48 input_value.set(text);
49 });
50
51 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 if idx > 0 && idx >= v.len() {
59 selected.set(idx - 1);
60 }
61 }
62 });
63 });
64
65 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}