Skip to main content

tablo_ui/components/primitives/
table.rs

1// SYNC: topcoat-ui-registry@0.12.0 sha256:d68f9e91742366229dde0fb512b0eea9aa71b05b713dc4dbd9ef2ad7b78dea1f — do not hand-edit. Sync via `cargo xtask sync-topcoat-ui` (ADR-0007).
2use topcoat::{
3    Result,
4    context::Cx,
5    view::{Attributes, Child, View, class, component, view},
6};
7
8/// A table inside a horizontally scrollable container.
9///
10/// Extra padding on the left and right aligns the cells with a card's content.
11/// Top and bottom borders separate the table from neighboring elements. A border
12/// is omitted when the table is the first or last child on that side.
13///
14/// Pass table sections as children. `attrs` are forwarded to the `<table>`, with extra
15/// classes added to its classes.
16///
17/// ```ignore
18/// view! {
19///     table(
20///         table_header(
21///             table_row(
22///                 table_head("Environment")
23///                 table_head("Status")
24///             )
25///         )
26///         table_body(
27///             table_row(
28///                 table_cell("production")
29///                 table_cell("Live")
30///             )
31///         )
32///     )
33/// }
34/// ```
35#[component]
36pub async fn table(
37    cx: &Cx,
38    #[default] mut attrs: Attributes,
39    #[default] child: Child<'_>,
40) -> Result<impl View> {
41    Ok(view! {
42        <div data-slot="table" class="w-full overflow-x-auto">
43            // Put borders on the table so the caption stays outside them.
44            <table
45                class=(class!(
46                    "w-full caption-bottom border-collapse border-border text-sm",
47                    "[:not(:first-child)>&]:border-t [:not(:last-child)>&]:border-b",
48                    attrs.remove("class"),
49                ))
50                (attrs)
51            >
52                (child)
53            </table>
54        </div>
55    })
56}
57
58/// The heading section of a [`table`], holding the row of column headers.
59#[component]
60pub async fn table_header(
61    cx: &Cx,
62    #[default] mut attrs: Attributes,
63    #[default] child: Child<'_>,
64) -> Result<impl View> {
65    Ok(view! {
66        <thead
67            class=(class!("bg-foreground/5 [&_tr]:border-b", attrs.remove("class")))
68            (attrs)
69        >
70            (child)
71        </thead>
72    })
73}
74
75/// The body of a table, containing data rows.
76#[component]
77pub async fn table_body(
78    cx: &Cx,
79    #[default] mut attrs: Attributes,
80    #[default] child: Child<'_>,
81) -> Result<impl View> {
82    Ok(view! {
83        <tbody
84            class=(class!("[&_tr:last-child]:border-0", attrs.remove("class")))
85            (attrs)
86        >
87            (child)
88        </tbody>
89    })
90}
91
92/// The closing section of a [`table`], for totals and other summaries.
93#[component]
94pub async fn table_footer(
95    cx: &Cx,
96    #[default] mut attrs: Attributes,
97    #[default] child: Child<'_>,
98) -> Result<impl View> {
99    Ok(view! {
100        <tfoot
101            class=(class!(
102                "border-t border-border bg-foreground/5 font-medium [&>tr]:last:border-b-0",
103                attrs.remove("class"),
104            ))
105            (attrs)
106        >
107            (child)
108        </tfoot>
109    })
110}
111
112/// A table row with a separator and hover styling.
113#[component]
114pub async fn table_row(
115    cx: &Cx,
116    #[default] mut attrs: Attributes,
117    #[default] child: Child<'_>,
118) -> Result<impl View> {
119    Ok(view! {
120        <tr
121            class=(class!(
122                "border-b border-border transition-colors hover:bg-foreground/5",
123                attrs.remove("class"),
124            ))
125            (attrs)
126        >
127            (child)
128        </tr>
129    })
130}
131
132/// A column header in a [`table_header`]'s row.
133#[component]
134pub async fn table_head(
135    cx: &Cx,
136    #[default] mut attrs: Attributes,
137    #[default] child: Child<'_>,
138) -> Result<impl View> {
139    Ok(view! {
140        <th
141            class=(class!(
142                "p-3 text-left align-middle font-medium whitespace-nowrap",
143                "text-muted-foreground first:pl-6 last:pr-6",
144                attrs.remove("class"),
145            ))
146            (attrs)
147        >
148            (child)
149        </th>
150    })
151}
152
153/// One cell of a [`table_row`].
154#[component]
155pub async fn table_cell(
156    cx: &Cx,
157    #[default] mut attrs: Attributes,
158    #[default] child: Child<'_>,
159) -> Result<impl View> {
160    Ok(view! {
161        <td
162            class=(class!(
163                "p-3 align-middle whitespace-nowrap first:pl-6 last:pr-6",
164                attrs.remove("class"),
165            ))
166            (attrs)
167        >
168            (child)
169        </td>
170    })
171}
172
173/// A caption describing the table.
174#[component]
175pub async fn table_caption(
176    cx: &Cx,
177    #[default] mut attrs: Attributes,
178    #[default] child: Child<'_>,
179) -> Result<impl View> {
180    Ok(view! {
181        <caption
182            class=(class!("mt-4 text-sm text-muted-foreground", attrs.remove("class")))
183            (attrs)
184        >
185            (child)
186        </caption>
187    })
188}