1use 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 cx.use_command(
26 KeyBinding::key(KeyCode::F(1)),
27 with!(show_help => move || show_help.update(|v| *v = !*v)),
28 );
29
30 let selected = state!(cx, || 0usize);
32
33 let sort_state = state!(cx, || None::<(usize, bool)>);
35
36 let base_data = vec![
38 vec![
39 "nginx-pod".to_string(),
40 "Running".to_string(),
41 "12%".to_string(),
42 "256Mi".to_string(),
43 "2h".to_string(),
44 ],
45 vec![
46 "redis-cache".to_string(),
47 "Running".to_string(),
48 "8%".to_string(),
49 "128Mi".to_string(),
50 "5d".to_string(),
51 ],
52 vec![
53 "api-server".to_string(),
54 "Running".to_string(),
55 "45%".to_string(),
56 "512Mi".to_string(),
57 "1h".to_string(),
58 ],
59 vec![
60 "db-postgres".to_string(),
61 "Running".to_string(),
62 "23%".to_string(),
63 "1Gi".to_string(),
64 "3d".to_string(),
65 ],
66 vec![
67 "worker-1".to_string(),
68 "Pending".to_string(),
69 "0%".to_string(),
70 "0Mi".to_string(),
71 "5m".to_string(),
72 ],
73 vec![
74 "worker-2".to_string(),
75 "Running".to_string(),
76 "67%".to_string(),
77 "384Mi".to_string(),
78 "45m".to_string(),
79 ],
80 vec![
81 "frontend".to_string(),
82 "Running".to_string(),
83 "5%".to_string(),
84 "64Mi".to_string(),
85 "12h".to_string(),
86 ],
87 vec![
88 "metrics".to_string(),
89 "CrashLoop".to_string(),
90 "0%".to_string(),
91 "32Mi".to_string(),
92 "2m".to_string(),
93 ],
94 ];
95
96 let mut rows = base_data.clone();
98 if let Some((col, ascending)) = sort_state.get() {
99 rows.sort_by(|a, b| {
100 let a_val = a.get(col).map(|s| s.as_str()).unwrap_or("");
101 let b_val = b.get(col).map(|s| s.as_str()).unwrap_or("");
102 if ascending {
103 a_val.cmp(b_val)
104 } else {
105 b_val.cmp(a_val)
106 }
107 });
108 }
109
110 let on_select = with!(selected => move |idx: usize| {
111 selected.set(idx);
112 });
113
114 let on_sort = with!(sort_state => move |col: usize, asc: bool| {
115 sort_state.set(Some((col, asc)));
116 });
117
118 let on_activate = with!(selected => move |idx: usize| {
119 selected.set(idx);
121 });
122
123 let selected_name = rows
124 .get(selected.get())
125 .and_then(|r| r.first())
126 .map(|s| s.as_str())
127 .unwrap_or("None");
128
129 let sort_info = match sort_state.get() {
130 Some((col, asc)) => {
131 let col_name = match col {
132 0 => "NAME",
133 1 => "STATUS",
134 2 => "CPU",
135 3 => "MEMORY",
136 4 => "AGE",
137 _ => "?",
138 };
139 format!(
140 "Sorted by {} ({})",
141 col_name,
142 if asc { "asc" } else { "desc" }
143 )
144 }
145 None => "Unsorted".to_string(),
146 };
147
148 View::vstack()
149 .child(
150 View::styled_text("Pod Dashboard")
151 .color(Color::Cyan)
152 .bold()
153 .build(),
154 )
155 .child(
156 View::styled_text(format!("Selected: {} | {}", selected_name, sort_info))
157 .dim()
158 .build(),
159 )
160 .child(
161 View::boxed()
162 .flex(1)
163 .border(true)
164 .child(
165 View::table()
166 .column("NAME")
167 .column_with(TableColumn::new("STATUS").width(ColumnWidth::Fixed(12)))
168 .column_with(
169 TableColumn::new("CPU")
170 .width(ColumnWidth::Fixed(8))
171 .align(TextAlign::Right),
172 )
173 .column_with(
174 TableColumn::new("MEMORY")
175 .width(ColumnWidth::Fixed(10))
176 .align(TextAlign::Right),
177 )
178 .column_with(
179 TableColumn::new("AGE")
180 .width(ColumnWidth::Fixed(8))
181 .align(TextAlign::Right),
182 )
183 .rows(rows)
184 .selected(selected.get())
185 .sort(sort_state.get())
186 .on_select(on_select)
187 .on_sort(on_sort)
188 .on_activate(on_activate)
189 .build(),
190 )
191 .build(),
192 )
193 .child(
194 View::styled_text("↑↓/jk: navigate | Enter: activate | F1 help | Ctrl+Q: quit")
195 .dim()
196 .build(),
197 )
198 .child(
199 View::modal()
200 .visible(show_help.get())
201 .title("Example 17: Table")
202 .on_dismiss(with!(show_help => move || show_help.set(false)))
203 .child(
204 View::vstack()
205 .child(View::styled_text("What you're seeing").bold().build())
206 .child(View::text("• Data table with sortable columns"))
207 .child(View::text("• Row selection and activation"))
208 .child(View::text("• Fixed and flexible column widths"))
209 .child(View::gap(1))
210 .child(View::styled_text("Key concepts").bold().build())
211 .child(View::text("• View::table() for tabular data"))
212 .child(View::text("• TableColumn for column config"))
213 .child(View::text("• ColumnWidth::Fixed or ColumnWidth::Flex"))
214 .child(View::text("• on_sort callback for sorting"))
215 .child(View::gap(1))
216 .child(View::styled_text("Try this").bold().build())
217 .child(View::text("• Navigate rows with arrow keys"))
218 .child(View::text("• Press Enter to activate a row"))
219 .child(View::text("• Sorting is managed via on_sort"))
220 .child(View::gap(1))
221 .child(View::styled_text("Next up").bold().build())
222 .child(View::text("→ 18_progress_bar: progress indicators"))
223 .child(View::gap(1))
224 .child(View::styled_text("Press Escape to close").dim().build())
225 .build(),
226 )
227 .build(),
228 )
229 .build()
230 }
231}