dioxus_tabular/
components.rs

1use crate::{Columns, Row, RowData, TableContext, TableData};
2use dioxus::prelude::*;
3
4/// Creates a reactive table with the given columns and rows.
5///
6/// This is the main hook for setting up a table. It returns a [`TableData`] that can be used
7/// with [`TableHeaders`] and [`TableCells`] components to render the table.
8///
9/// # Parameters
10///
11/// - `columns`: A tuple of columns implementing [`TableColumn`](crate::TableColumn)
12/// - `rows`: A reactive signal containing the row data
13///
14/// # Example
15///
16/// ```
17/// use dioxus::prelude::*;
18/// use dioxus_tabular::*;
19///
20/// # #[derive(Clone, PartialEq)]
21/// # struct User { id: u32, name: String }
22/// # impl Row for User {
23/// #     fn key(&self) -> impl Into<String> { self.id.to_string() }
24/// # }
25/// # #[derive(Clone, PartialEq)]
26/// # struct NameColumn;
27/// # #[derive(Clone, PartialEq)]
28/// # struct UserName(String);
29/// # impl GetRowData<UserName> for User {
30/// #     fn get(&self) -> UserName { UserName(self.name.clone()) }
31/// # }
32/// # impl<R: Row + GetRowData<UserName>> TableColumn<R> for NameColumn {
33/// #     fn column_name(&self) -> String { "name".into() }
34/// #     fn render_header(&self, _: ColumnContext, _: Vec<Attribute>) -> Element { rsx! { th {} } }
35/// #     fn render_cell(&self, _: ColumnContext, row: &R, _: Vec<Attribute>) -> Element {
36/// #         rsx! { td { "{row.get().0}" } }
37/// #     }
38/// # }
39/// fn app() -> Element {
40///     let users = use_signal(|| vec![
41///         User { id: 1, name: "Alice".to_string() },
42///         User { id: 2, name: "Bob".to_string() },
43///     ]);
44///
45///     // Create table with single column
46///     let data = use_tabular((NameColumn,), users.into());
47///
48///     rsx! {
49///         table {
50///             thead { tr { TableHeaders { data } } }
51///             tbody {
52///                 for row in data.rows() {
53///                     tr { key: "{row.key()}", TableCells { row } }
54///                 }
55///             }
56///         }
57///     }
58/// }
59/// ```
60///
61/// # Multiple Columns
62///
63/// ```
64/// # use dioxus::prelude::*;
65/// # use dioxus_tabular::*;
66/// # #[derive(Clone, PartialEq)]
67/// # struct User { id: u32, name: String }
68/// # impl Row for User {
69/// #     fn key(&self) -> impl Into<String> { self.id.to_string() }
70/// # }
71/// # #[derive(Clone, PartialEq)]
72/// # struct NameColumn;
73/// # #[derive(Clone, PartialEq)]
74/// # struct IdColumn;
75/// # #[derive(Clone, PartialEq)]
76/// # struct UserName(String);
77/// # #[derive(Clone, PartialEq)]
78/// # struct UserId(u32);
79/// # impl GetRowData<UserName> for User {
80/// #     fn get(&self) -> UserName { UserName(self.name.clone()) }
81/// # }
82/// # impl GetRowData<UserId> for User {
83/// #     fn get(&self) -> UserId { UserId(self.id) }
84/// # }
85/// # impl<R: Row + GetRowData<UserName>> TableColumn<R> for NameColumn {
86/// #     fn column_name(&self) -> String { "name".into() }
87/// #     fn render_header(&self, _: ColumnContext, _: Vec<Attribute>) -> Element { rsx! { th {} } }
88/// #     fn render_cell(&self, _: ColumnContext, row: &R, _: Vec<Attribute>) -> Element {
89/// #         rsx! { td { "{row.get().0}" } }
90/// #     }
91/// # }
92/// # impl<R: Row + GetRowData<UserId>> TableColumn<R> for IdColumn {
93/// #     fn column_name(&self) -> String { "id".into() }
94/// #     fn render_header(&self, _: ColumnContext, _: Vec<Attribute>) -> Element { rsx! { th {} } }
95/// #     fn render_cell(&self, _: ColumnContext, row: &R, _: Vec<Attribute>) -> Element {
96/// #         rsx! { td { "{row.get().0}" } }
97/// #     }
98/// # }
99/// # fn app() -> Element {
100/// #     let users = use_signal(|| vec![
101/// #         User { id: 1, name: "Alice".to_string() },
102/// #     ]);
103/// // Use tuple for multiple columns (supports up to 12 columns)
104/// let data = use_tabular((IdColumn, NameColumn), users.into());
105/// #     rsx! { table {} }
106/// # }
107/// ```
108pub fn use_tabular<C: Columns<R>, R: Row>(
109    columns: C,
110    rows: ReadOnlySignal<Vec<R>>,
111) -> TableData<C, R> {
112    let context = TableContext::use_table_context(columns);
113    context.table_data(rows)
114}
115
116/// Renders table headers for all visible columns.
117///
118/// This component iterates through the columns and renders each header.
119/// It automatically handles column reordering and visibility.
120///
121/// # Props
122///
123/// - `data`: The table data from [`use_tabular`]
124/// - Additional HTML attributes can be spread onto each `<th>` element
125///
126/// # Example
127///
128/// ```
129/// # use dioxus::prelude::*;
130/// # use dioxus_tabular::*;
131/// # #[derive(Clone, PartialEq)]
132/// # struct User { id: u32 }
133/// # impl Row for User {
134/// #     fn key(&self) -> impl Into<String> { self.id.to_string() }
135/// # }
136/// # #[derive(Clone, PartialEq)]
137/// # struct Col;
138/// # impl TableColumn<User> for Col {
139/// #     fn column_name(&self) -> String { "col".into() }
140/// #     fn render_header(&self, _: ColumnContext, _: Vec<Attribute>) -> Element { rsx! { th {} } }
141/// #     fn render_cell(&self, _: ColumnContext, _: &User, _: Vec<Attribute>) -> Element { rsx! { td {} } }
142/// # }
143/// # fn app() -> Element {
144/// #     let users = use_signal(|| vec![User { id: 1 }]);
145/// #     let data = use_tabular((Col,), users.into());
146/// rsx! {
147///     table {
148///         thead {
149///             tr {
150///                 // Renders all column headers
151///                 TableHeaders { data }
152///             }
153///         }
154///     }
155/// }
156/// # }
157/// ```
158///
159/// # With Custom Attributes
160///
161/// ```
162/// # use dioxus::prelude::*;
163/// # use dioxus_tabular::*;
164/// # #[derive(Clone, PartialEq)]
165/// # struct User { id: u32 }
166/// # impl Row for User {
167/// #     fn key(&self) -> impl Into<String> { self.id.to_string() }
168/// # }
169/// # #[derive(Clone, PartialEq)]
170/// # struct Col;
171/// # impl TableColumn<User> for Col {
172/// #     fn column_name(&self) -> String { "col".into() }
173/// #     fn render_header(&self, _: ColumnContext, _: Vec<Attribute>) -> Element { rsx! { th {} } }
174/// #     fn render_cell(&self, _: ColumnContext, _: &User, _: Vec<Attribute>) -> Element { rsx! { td {} } }
175/// # }
176/// # fn app() -> Element {
177/// #     let users = use_signal(|| vec![User { id: 1 }]);
178/// #     let data = use_tabular((Col,), users.into());
179/// rsx! {
180///     tr {
181///         TableHeaders {
182///             data,
183///             class: "header-cell",
184///             style: "font-weight: bold;"
185///         }
186///     }
187/// }
188/// # }
189/// ```
190#[component]
191pub fn TableHeaders<C: Columns<R>, R: Row>(
192    data: TableData<C, R>,
193    #[props(extends = GlobalAttributes)] attributes: Vec<Attribute>,
194) -> Element {
195    rsx! {
196        for header in data.context.headers() {
197            Fragment { key: "{header.key()}", {header.render(attributes.clone())} }
198        }
199    }
200}
201
202/// Renders table cells for a single row across all visible columns.
203///
204/// This component iterates through the columns and renders each cell for the given row.
205/// It automatically handles column reordering and visibility.
206///
207/// # Props
208///
209/// - `row`: A row from iterating over `data.rows()`
210/// - Additional HTML attributes can be spread onto each `<td>` element
211///
212/// # Example
213///
214/// ```
215/// # use dioxus::prelude::*;
216/// # use dioxus_tabular::*;
217/// # #[derive(Clone, PartialEq)]
218/// # struct User { id: u32 }
219/// # impl Row for User {
220/// #     fn key(&self) -> impl Into<String> { self.id.to_string() }
221/// # }
222/// # #[derive(Clone, PartialEq)]
223/// # struct Col;
224/// # impl TableColumn<User> for Col {
225/// #     fn column_name(&self) -> String { "col".into() }
226/// #     fn render_header(&self, _: ColumnContext, _: Vec<Attribute>) -> Element { rsx! { th {} } }
227/// #     fn render_cell(&self, _: ColumnContext, _: &User, _: Vec<Attribute>) -> Element { rsx! { td {} } }
228/// # }
229/// # fn app() -> Element {
230/// #     let users = use_signal(|| vec![User { id: 1 }]);
231/// #     let data = use_tabular((Col,), users.into());
232/// rsx! {
233///     table {
234///         tbody {
235///             for row in data.rows() {
236///                 tr {
237///                     key: "{row.key()}",
238///                     // Renders all cells for this row
239///                     TableCells { row }
240///                 }
241///             }
242///         }
243///     }
244/// }
245/// # }
246/// ```
247///
248/// # With Custom Attributes
249///
250/// ```
251/// # use dioxus::prelude::*;
252/// # use dioxus_tabular::*;
253/// # #[derive(Clone, PartialEq)]
254/// # struct User { id: u32 }
255/// # impl Row for User {
256/// #     fn key(&self) -> impl Into<String> { self.id.to_string() }
257/// # }
258/// # #[derive(Clone, PartialEq)]
259/// # struct Col;
260/// # impl TableColumn<User> for Col {
261/// #     fn column_name(&self) -> String { "col".into() }
262/// #     fn render_header(&self, _: ColumnContext, _: Vec<Attribute>) -> Element { rsx! { th {} } }
263/// #     fn render_cell(&self, _: ColumnContext, _: &User, _: Vec<Attribute>) -> Element { rsx! { td {} } }
264/// # }
265/// # fn app() -> Element {
266/// #     let users = use_signal(|| vec![User { id: 1 }]);
267/// #     let data = use_tabular((Col,), users.into());
268/// rsx! {
269///     for row in data.rows() {
270///         tr {
271///             key: "{row.key()}",
272///             TableCells {
273///                 row,
274///                 class: "data-cell",
275///                 style: "padding: 8px;"
276///             }
277///         }
278///     }
279/// }
280/// # }
281/// ```
282#[component]
283pub fn TableCells<C: Columns<R>, R: Row>(
284    row: RowData<C, R>,
285    #[props(extends = GlobalAttributes)] attributes: Vec<Attribute>,
286) -> Element {
287    rsx! {
288        for cell in row.cells() {
289            Fragment { key: "{cell.key()}", {cell.render(attributes.clone())} }
290        }
291    }
292}