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 {
47            context.theme.primary()
48        } else {
49            context.theme.text_primary()
50        };
51        vec![Line::styled(display, style)]
52    }
53}
54
55#[cfg(test)]
56mod tests {
57    use super::*;
58    use crossterm::event::{KeyEvent, KeyModifiers};
59
60    fn key(code: KeyCode) -> KeyEvent {
61        KeyEvent::new(code, KeyModifiers::NONE)
62    }
63
64    #[tokio::test]
65    async fn space_toggles() {
66        let mut cb = Checkbox::new(false);
67        cb.on_event(&Event::Key(key(KeyCode::Char(' ')))).await;
68        assert!(cb.checked);
69        cb.on_event(&Event::Key(key(KeyCode::Char(' ')))).await;
70        assert!(!cb.checked);
71    }
72
73    #[test]
74    fn to_json_returns_bool() {
75        assert_eq!(Checkbox::new(true).to_json(), serde_json::json!(true));
76        assert_eq!(Checkbox::new(false).to_json(), serde_json::json!(false));
77    }
78
79    #[tokio::test]
80    async fn other_keys_are_ignored() {
81        let mut cb = Checkbox::new(false);
82        let outcome = cb.on_event(&Event::Key(key(KeyCode::Char('a')))).await;
83        assert!(outcome.is_none());
84        assert!(!cb.checked);
85    }
86}