table_rs/dioxus/
header.rs

1use crate::dioxus::types::Column;
2use crate::dioxus::types::SortOrder;
3use crate::dioxus::types::TableClasses;
4use dioxus::prelude::*;
5
6/// A table header component that renders sortable column headers for use within the `Table` component.
7///
8/// This component produces the `<thead>` section of a table using the provided column definitions,
9/// handling rendering, sorting indicators (`aria-sort`), and user interaction to trigger sort changes.
10///
11/// # Props
12/// - `columns`: A `Vec<Column>` defining the columns to display in the header. Each `Column` may be sortable and have optional styles or class overrides.
13/// - `sort_column`: A `Signal<Option<&'static str>>` indicating which column (if any) is currently being sorted.
14/// - `sort_order`: A `Signal<SortOrder>` indicating the current sort direction (`Asc` or `Desc`).
15/// - `on_sort_column`: An `EventHandler<&'static str>` triggered when a sortable header cell is clicked. The column ID is passed as the event payload.
16/// - `classes`: A `TableClasses` struct allowing custom class names for `<thead>`, `<tr>`, and `<th>` elements.
17///
18/// # Behavior
19/// - Sortable columns show proper `aria-sort` attributes for accessibility (`ascending`, `descending`, or `none`).
20/// - Clicking a sortable column emits an event to update sort state.
21/// - Each column can override default styles and classes via `Column::style` and `Column::class`.
22///
23/// # Returns
24/// Returns a `Dioxus` `Element` containing the `<thead>` with all column headers rendered as `<th>` elements.
25///
26/// # Example
27/// ```rust
28/// use dioxus::prelude::*;
29/// use maplit::hashmap;
30/// use table_rs::dioxus::table::Table;
31/// use table_rs::dioxus::types::{Column, TableClasses, SortOrder};
32/// use table_rs::dioxus::header::TableHeader;
33///
34///
35/// fn App() -> Element {
36///     let columns = vec![
37///         Column { id: "name", header: "Name", sortable: true, ..Default::default() },
38///         Column { id: "email", header: "Email", sortable: false, ..Default::default() },
39///     ];
40///
41///     let sort_column = use_signal(|| Some("name"));
42///     let sort_order = use_signal(|| SortOrder::Asc);
43///
44///     rsx! {
45///         TableHeader {
46///             columns: columns,
47///             sort_column: sort_column,
48///             sort_order: sort_order,
49///             on_sort_column: move |col_id| println!("Sort column changed: {}", col_id),
50///             classes: TableClasses::default(),
51///         }
52///     }
53/// }
54/// ```
55///
56/// # See Also
57/// - [MDN `<thead>` Element](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/thead)
58#[component]
59pub fn TableHeader(
60    columns: Vec<Column>,
61    sort_column: Signal<Option<&'static str>>,
62    sort_order: Signal<SortOrder>,
63    on_sort_column: EventHandler<&'static str>,
64    classes: TableClasses,
65) -> Element {
66    let header_cells = columns.iter().map(|col| {
67        let col_id = col.id;
68        let is_sorted = sort_column() == Some(col_id);
69        let aria_sort = if is_sorted {
70            match sort_order() {
71                SortOrder::Asc => "ascending",
72                SortOrder::Desc => "descending",
73            }
74        } else {
75            "none"
76        };
77
78        let class = format!("{} {}", classes.header_cell, col.class.unwrap_or_default());
79        let style = col.style.unwrap_or_default();
80        let header = col.header;
81
82        let onclick = if col.sortable {
83            Callback::new(move |_| on_sort_column.call(col_id))
84        } else {
85            Callback::new(|_| {})
86        };
87
88        rsx! {
89            th {
90                key: "{col_id}",
91                role: "columnheader",
92                class: "{class}",
93                style: "{style}",
94                aria_sort: "{aria_sort}",
95                onclick: onclick,
96                "{header}"
97            }
98        }
99    });
100
101    rsx! {
102        thead { class: "{classes.thead}",
103            tr { class: "{classes.row}", role: "row",
104                {header_cells}
105            }
106        }
107    }
108}