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!();
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        boolean_cell(cx, *self.lens.read(row), self.text(cx, row))
67    }
68
69    fn order_by(&self, descending: bool) -> Option<OrderByExpr> {
70        self.sortable
71            .then(|| sort_order(self.lens.path(), descending))
72    }
73}
74
75impl<M> crate::column::ColumnInternals<M> for BooleanColumn<M>
76where
77    M: toasty::schema::Model + Send + Sync + 'static,
78{
79    base_internals!(binding);
80}
81
82/// A `bool` as its icon, with `text` for a screen reader.
83pub(super) fn boolean_cell(cx: &Cx, value: bool, text: String) -> BoxView<'_> {
84    // A no is the common case in most columns, so it stays quiet and the yeses stand out.
85    let (data, class) = if value {
86        (tablo_ui::icons::CIRCLE_CHECK, "size-4 text-success")
87    } else {
88        (tablo_ui::icons::MINUS, "size-4 text-muted-foreground/60")
89    };
90    view! {
91        <span class="inline-flex items-center" data-boolean=(value.to_string())>
92            icon(data: data, attrs: attributes! { class=(class) aria-hidden="true" })
93            <span class="sr-only">(text)</span>
94        </span>
95    }
96    .boxed()
97}