Skip to main content

things3_cloud/ui/components/
checklist_item.rs

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