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}