Skip to main content

tablo_core/column/
boolean.rs

1//! [`BooleanColumn`]: a `bool` field rendered as an icon.
2
3use derive_where::derive_where;
4use toasty::stmt::OrderByExpr;
5use topcoat::{context::Cx, icon::icon, view::*};
6
7use super::{Column, ColumnBase, ColumnWidth, sort_order};
8use crate::Lens;
9
10/// A column of a `bool` field rendered as an icon.
11///
12/// ```rust
13/// # #[derive(Debug, Clone, toasty::Model, tablo_core::ModelColumns)]
14/// # struct Post { #[key] #[auto] id: uuid::Uuid, featured: bool }
15/// tablo_core::BooleanColumn::new(tablo_core::lens!(Post.featured)).sortable();
16/// ```
17#[derive_where(Clone, Debug)]
18pub struct BooleanColumn<M> {
19    lens: Lens<M, bool>,
20    base: ColumnBase,
21    sortable: bool,
22    labels: (crate::Text, crate::Text),
23}
24
25impl<M> BooleanColumn<M>
26where
27    M: toasty::schema::Model,
28{
29    /// Bind the column to the `bool` field `lens` reads.
30    pub fn new(lens: Lens<M, bool>) -> Self {
31        Self {
32            base: ColumnBase::new(lens.path(), ColumnWidth::Auto),
33            lens,
34            sortable: false,
35            labels: (
36                crate::Text::message("tablo-yes"),
37                crate::Text::message("tablo-no"),
38            ),
39        }
40    }
41
42    base_builders!(sortable);
43
44    /// The words for `true` and `false`.
45    pub fn labels(mut self, yes: impl Into<crate::Text>, no: impl Into<crate::Text>) -> Self {
46        self.labels = (yes.into(), no.into());
47        self
48    }
49}
50
51impl<M> Column<M> for BooleanColumn<M>
52where
53    M: toasty::schema::Model + Send + Sync + 'static,
54{
55    base_column_methods!(binding);
56
57    fn text(&self, cx: &Cx, row: &M) -> String {
58        if *self.lens.read(row) {
59            self.labels.0.localize(cx)
60        } else {
61            self.labels.1.localize(cx)
62        }
63    }
64
65    fn cell<'a>(&self, cx: &'a Cx, row: &M) -> BoxView<'a> {
66        let value = *self.lens.read(row);
67        let text = self.text(cx, row);
68        // A no is the common case in most columns, so it stays quiet and the yeses stand out.
69        let (data, class) = if value {
70            (tablo_ui::icons::CIRCLE_CHECK, "size-4 text-success")
71        } else {
72            (tablo_ui::icons::MINUS, "size-4 text-muted-foreground/60")
73        };
74        view! {
75            cx =>
76            <span class="inline-flex items-center" data-boolean=(value.to_string())>
77                icon(
78                    data: data,
79                    attrs: attributes! { class=(class) aria-hidden="true" }
80                )
81                <span class="sr-only">(text)</span>
82            </span>
83        }
84        .boxed()
85    }
86
87    fn is_sortable(&self) -> bool {
88        self.sortable
89    }
90
91    fn order_by(&self, descending: bool) -> Option<OrderByExpr> {
92        self.sortable
93            .then(|| sort_order(self.lens.path(), descending))
94    }
95}