Skip to main content

things3_cloud/ui/components/
checklist_item.rs

1use iocraft::prelude::*;
2
3use crate::{common::ICONS, store::ChecklistItem, ui::components::id::Id};
4
5/// A single checklist-item row.
6///
7/// When `id` is `Some`, it is rendered in a fixed-width left column so
8/// connectors follow the ID prefix.
9///
10/// ```text
11/// M ├╴○ Confirm changelog
12/// J └╴● Tag release commit   (is_last)
13/// ```
14///
15/// When `id` is `None` (no IDs), the connector starts at column 0:
16/// ```text
17/// ├╴○ title
18/// └╴● title
19/// ```
20
21#[derive(Default, Props)]
22pub struct CheckListRowProps<'a> {
23    pub item: Option<&'a ChecklistItem>,
24    pub id_prefix_len: usize,
25    pub is_last: bool,
26}
27
28#[component]
29pub fn CheckListRow<'a>(props: &CheckListRowProps<'a>) -> impl Into<AnyElement<'a>> {
30    let Some(item) = props.item else {
31        return element!(Fragment).into_any();
32    };
33
34    let connector = if props.is_last { "└╴" } else { "├╴" };
35
36    let id = if props.id_prefix_len > 0 {
37        element!(Id(id: &item.uuid, length: props.id_prefix_len)).into_any()
38    } else {
39        element!(Fragment).into_any()
40    };
41
42    element!(View {
43        View(flex_direction: FlexDirection::Row, gap: 1) {
44            #(id)
45            Text(content: connector, color: Color::DarkGrey)
46        }
47        View(flex_direction: FlexDirection::Row, gap: 1) {
48            Text(content: checklist_icon(item), color: Color::DarkGrey)
49            Text(content: item.title.clone())
50        }
51    })
52    .into_any()
53}
54
55fn checklist_icon(item: &ChecklistItem) -> &'static str {
56    if item.is_completed() {
57        ICONS.checklist_done
58    } else if item.is_canceled() {
59        ICONS.checklist_canceled
60    } else {
61        ICONS.checklist_open
62    }
63}