table_rs/dioxus/body.rs
1use crate::dioxus::types::Column;
2use crate::dioxus::types::TableClasses;
3use crate::dioxus::types::TableTexts;
4use dioxus::prelude::*;
5use std::collections::HashMap;
6
7/// A table body component that renders rows of data, along with loading and empty states.
8///
9/// This component is responsible for rendering the `<tbody>` section of a table in a Dioxus application.
10/// It dynamically displays row data, a loading message, or an empty state message based on the provided props.
11///
12/// # Props
13/// - `columns`: A `Vec<Column>` defining which fields to render in each table row. Each column corresponds to a key in the row data.
14/// - `rows`: A `Vec<HashMap<&'static str, String>>` representing the data for each row, where keys match column IDs.
15/// - `loading`: A `bool` flag that, when true, displays a loading message instead of data rows.
16/// - `classes`: A `TableClasses` struct for customizing the CSS class names of the body, rows, and cells.
17/// - `texts`: A `TableTexts` struct that provides custom text for the loading and empty states.
18///
19/// # Behavior
20/// - If `loading` is `true`, a single row with a loading message is shown spanning all columns.
21/// - If `rows` is empty and not loading, an empty message row is displayed.
22/// - Otherwise, each data row is rendered in a `<tr>`, with one `<td>` per column.
23///
24/// # Returns
25/// A Dioxus `Element` representing the `<tbody>` of a table, with dynamic row content.
26///
27/// # Example
28/// ```rust
29/// use dioxus::prelude::*;
30/// use maplit::hashmap;
31/// use table_rs::dioxus::table::Table;
32/// use table_rs::dioxus::types::{Column, TableClasses, TableTexts};
33/// use table_rs::dioxus::body::TableBody;
34///
35///
36/// fn App() -> Element {
37/// let rows = vec![
38/// hashmap! { "name" => "Ferris".to_string(), "email" => "ferris@opensass.org".to_string() },
39/// hashmap! { "name" => "Rustacean".to_string(), "email" => "rust@opensass.org".to_string() },
40/// ];
41///
42/// let columns = vec![
43/// Column { id: "name", header: "Name", ..Default::default() },
44/// Column { id: "email", header: "Email", ..Default::default() },
45/// ];
46///
47/// rsx! {
48/// TableBody {
49/// columns: columns,
50/// rows: rows,
51/// loading: false,
52/// classes: TableClasses::default(),
53/// texts: TableTexts::default(),
54/// }
55/// }
56/// }
57/// ```
58///
59/// # See Also
60/// - [MDN `<tbody>` Element](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/tbody)
61#[component]
62pub fn TableBody(
63 columns: Vec<Column>,
64 rows: Vec<HashMap<&'static str, String>>,
65 loading: bool,
66 classes: TableClasses,
67 texts: TableTexts,
68) -> Element {
69 let content = if loading {
70 rsx! {
71 tr { class: "{classes.loading_row}",
72 td {
73 colspan: "{columns.len()}",
74 "{texts.loading}"
75 }
76 }
77 }
78 } else if rows.is_empty() {
79 rsx! {
80 tr { class: "{classes.empty_row}",
81 td {
82 colspan: "{columns.len()}",
83 "{texts.empty}"
84 }
85 }
86 }
87 } else {
88 rsx! {
89 for row in rows.iter() {
90 tr { class: "{classes.row}", role: "row",
91 for col in columns.iter() {
92 td { class: "{classes.body_cell}", role: "cell",
93 BodyCell {
94 column: col.clone(),
95 content: row.get(col.id).unwrap_or(&String::new()),
96 }
97 }
98 }
99 }
100 }
101 }
102 };
103
104 rsx! {
105 tbody { class: "{classes.tbody}",
106 {content}
107 }
108 }
109}
110
111#[component]
112fn BodyCell(column: Column, content: String) -> Element {
113 if let Some(cb) = column.cell {
114 cb(content)
115 } else {
116 rsx! {
117 "{content}"
118 }
119 }
120}