Skip to main content

tui/components/
checkbox.rs

1use crossterm::event::KeyCode;
2
3use crate::components::{Component, Event, ViewContext};
4use crate::line::Line;
5use crate::rendering::frame::Frame;
6
7/// Boolean toggle rendered as `[x]` / `[ ]`.
8pub struct Checkbox {
9    pub checked: bool,
10}
11
12impl Checkbox {
13    pub fn new(checked: bool) -> Self {
14        Self { checked }
15    }
16
17    pub fn to_json(&self) -> serde_json::Value {
18        serde_json::Value::Bool(self.checked)
19    }
20}
21
22impl Component for Checkbox {
23    type Message = ();
24
25    async fn on_event(&mut self, event: &Event) -> Option<Vec<Self::Message>> {
26        let Event::Key(key) = event else {
27            return None;
28        };
29        match key.code {
30            KeyCode::Char(' ') => {
31                self.checked = !self.checked;
32                Some(vec![])
33            }
34            _ => None,
35        }
36    }
37
38    fn render(&mut self, context: &ViewContext) -> Frame {
39        Frame::new(self.render_field(context, true))
40    }
41}
42
43impl Checkbox {
44    pub fn render_field(&self, context: &ViewContext, focused: bool) -> Vec<Line> {
45        let display = if self.checked { "[x]" } else { "[ ]" };
46        let style = if focused { context.theme.primary() } else { context.theme.text_primary() };
47        vec![Line::styled(display, style)]
48    }
49}
50
51#[cfg(test)]
52mod tests {
53    use super::*;
54    use crossterm::event::{KeyEvent, KeyModifiers};
55
56    fn key(code: KeyCode) -> KeyEvent {
57        KeyEvent::new(code, KeyModifiers::NONE)
58    }
59
60    #[tokio::test]
61    async fn space_toggles() {
62        let mut cb = Checkbox::new(false);
63        cb.on_event(&Event::Key(key(KeyCode::Char(' ')))).await;
64        assert!(cb.checked);
65        cb.on_event(&Event::Key(key(KeyCode::Char(' ')))).await;
66        assert!(!cb.checked);
67    }
68
69    #[test]
70    fn to_json_returns_bool() {
71        assert_eq!(Checkbox::new(true).to_json(), serde_json::json!(true));
72        assert_eq!(Checkbox::new(false).to_json(), serde_json::json!(false));
73    }
74
75    #[tokio::test]
76    async fn other_keys_are_ignored() {
77        let mut cb = Checkbox::new(false);
78        let outcome = cb.on_event(&Event::Key(key(KeyCode::Char('a')))).await;
79        assert!(outcome.is_none());
80        assert!(!cb.checked);
81    }
82}