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>(columns: C, rows: ReadSignal<Vec<R>>) -> TableData<C, R> {
109 let context = TableContext::use_table_context(columns);
110 context.table_data(rows)
111}
112
113/// Renders table headers for all visible columns.
114///
115/// This component iterates through the columns and renders each header.
116/// It automatically handles column reordering and visibility.
117///
118/// # Props
119///
120/// - `data`: The table data from [`use_tabular`]
121/// - Additional HTML attributes can be spread onto each `<th>` element
122///
123/// # Example
124///
125/// ```
126/// # use dioxus::prelude::*;
127/// # use dioxus_tabular::*;
128/// # #[derive(Clone, PartialEq)]
129/// # struct User { id: u32 }
130/// # impl Row for User {
131/// # fn key(&self) -> impl Into<String> { self.id.to_string() }
132/// # }
133/// # #[derive(Clone, PartialEq)]
134/// # struct Col;
135/// # impl TableColumn<User> for Col {
136/// # fn column_name(&self) -> String { "col".into() }
137/// # fn render_header(&self, _: ColumnContext, _: Vec<Attribute>) -> Element { rsx! { th {} } }
138/// # fn render_cell(&self, _: ColumnContext, _: &User, _: Vec<Attribute>) -> Element { rsx! { td {} } }
139/// # }
140/// # fn app() -> Element {
141/// # let users = use_signal(|| vec![User { id: 1 }]);
142/// # let data = use_tabular((Col,), users.into());
143/// rsx! {
144/// table {
145/// thead {
146/// tr {
147/// // Renders all column headers
148/// TableHeaders { data }
149/// }
150/// }
151/// }
152/// }
153/// # }
154/// ```
155///
156/// # With Custom Attributes
157///
158/// ```
159/// # use dioxus::prelude::*;
160/// # use dioxus_tabular::*;
161/// # #[derive(Clone, PartialEq)]
162/// # struct User { id: u32 }
163/// # impl Row for User {
164/// # fn key(&self) -> impl Into<String> { self.id.to_string() }
165/// # }
166/// # #[derive(Clone, PartialEq)]
167/// # struct Col;
168/// # impl TableColumn<User> for Col {
169/// # fn column_name(&self) -> String { "col".into() }
170/// # fn render_header(&self, _: ColumnContext, _: Vec<Attribute>) -> Element { rsx! { th {} } }
171/// # fn render_cell(&self, _: ColumnContext, _: &User, _: Vec<Attribute>) -> Element { rsx! { td {} } }
172/// # }
173/// # fn app() -> Element {
174/// # let users = use_signal(|| vec![User { id: 1 }]);
175/// # let data = use_tabular((Col,), users.into());
176/// rsx! {
177/// tr {
178/// TableHeaders {
179/// data,
180/// class: "header-cell",
181/// style: "font-weight: bold;"
182/// }
183/// }
184/// }
185/// # }
186/// ```
187#[component]
188pub fn TableHeaders<C: Columns<R>, R: Row>(
189 data: TableData<C, R>,
190 #[props(extends = GlobalAttributes)] attributes: Vec<Attribute>,
191) -> Element {
192 rsx! {
193 for header in data.context.headers() {
194 Fragment { key: "{header.key()}", {header.render(attributes.clone())} }
195 }
196 }
197}
198
199/// Renders table cells for a single row across all visible columns.
200///
201/// This component iterates through the columns and renders each cell for the given row.
202/// It automatically handles column reordering and visibility.
203///
204/// # Props
205///
206/// - `row`: A row from iterating over `data.rows()`
207/// - Additional HTML attributes can be spread onto each `<td>` element
208///
209/// # Example
210///
211/// ```
212/// # use dioxus::prelude::*;
213/// # use dioxus_tabular::*;
214/// # #[derive(Clone, PartialEq)]
215/// # struct User { id: u32 }
216/// # impl Row for User {
217/// # fn key(&self) -> impl Into<String> { self.id.to_string() }
218/// # }
219/// # #[derive(Clone, PartialEq)]
220/// # struct Col;
221/// # impl TableColumn<User> for Col {
222/// # fn column_name(&self) -> String { "col".into() }
223/// # fn render_header(&self, _: ColumnContext, _: Vec<Attribute>) -> Element { rsx! { th {} } }
224/// # fn render_cell(&self, _: ColumnContext, _: &User, _: Vec<Attribute>) -> Element { rsx! { td {} } }
225/// # }
226/// # fn app() -> Element {
227/// # let users = use_signal(|| vec![User { id: 1 }]);
228/// # let data = use_tabular((Col,), users.into());
229/// rsx! {
230/// table {
231/// tbody {
232/// for row in data.rows() {
233/// tr {
234/// key: "{row.key()}",
235/// // Renders all cells for this row
236/// TableCells { row }
237/// }
238/// }
239/// }
240/// }
241/// }
242/// # }
243/// ```
244///
245/// # With Custom Attributes
246///
247/// ```
248/// # use dioxus::prelude::*;
249/// # use dioxus_tabular::*;
250/// # #[derive(Clone, PartialEq)]
251/// # struct User { id: u32 }
252/// # impl Row for User {
253/// # fn key(&self) -> impl Into<String> { self.id.to_string() }
254/// # }
255/// # #[derive(Clone, PartialEq)]
256/// # struct Col;
257/// # impl TableColumn<User> for Col {
258/// # fn column_name(&self) -> String { "col".into() }
259/// # fn render_header(&self, _: ColumnContext, _: Vec<Attribute>) -> Element { rsx! { th {} } }
260/// # fn render_cell(&self, _: ColumnContext, _: &User, _: Vec<Attribute>) -> Element { rsx! { td {} } }
261/// # }
262/// # fn app() -> Element {
263/// # let users = use_signal(|| vec![User { id: 1 }]);
264/// # let data = use_tabular((Col,), users.into());
265/// rsx! {
266/// for row in data.rows() {
267/// tr {
268/// key: "{row.key()}",
269/// TableCells {
270/// row,
271/// class: "data-cell",
272/// style: "padding: 8px;"
273/// }
274/// }
275/// }
276/// }
277/// # }
278/// ```
279#[component]
280pub fn TableCells<C: Columns<R>, R: Row>(
281 row: RowData<C, R>,
282 #[props(extends = GlobalAttributes)] attributes: Vec<Attribute>,
283) -> Element {
284 rsx! {
285 for cell in row.cells() {
286 Fragment { key: "{cell.key()}", {cell.render(attributes.clone())} }
287 }
288 }
289}