Skip to main content

patternfly_yew/components/table/
composable.rs

1use crate::prelude::{Button, ButtonVariant};
2use yew::prelude::*;
3
4use super::*;
5
6const OUIA: Ouia = ouia!("Table");
7
8/// Properties for [`ComposableTable`]
9#[derive(Debug, Clone, PartialEq, Properties)]
10pub struct ComposableTableProperties {
11    #[prop_or_default]
12    pub class: Classes,
13    #[prop_or_default]
14    pub children: Html,
15    #[prop_or_default]
16    pub mode: TableMode,
17    #[prop_or_default]
18    pub sticky_header: bool,
19    #[prop_or_default]
20    pub grid: Option<TableGridMode>,
21    #[prop_or(true)]
22    pub borders: bool,
23    #[prop_or_default]
24    pub id: AttrValue,
25    /// OUIA Component id
26    #[prop_or_default]
27    pub ouia_id: Option<String>,
28    /// OUIA Component Type
29    #[prop_or(OUIA.component_type())]
30    pub ouia_type: OuiaComponentType,
31    /// OUIA Component Safe
32    #[prop_or(OuiaSafe::TRUE)]
33    pub ouia_safe: OuiaSafe,
34}
35
36/// A table which that does not offer any of the type-safe utilities that [`Table`] offers.
37/// Using this component means managing rows and columns manually.
38/// It is recommended to use [`Table`] instead where possible.
39/// A possible reason for using [`ComposableTable`] instead of [`Table`] is because state
40/// needs to be saved per row.
41///
42/// ## Properties
43///
44/// Defined by [`ComposableTableProperties`].
45#[function_component(ComposableTable)]
46pub fn composable_table(props: &ComposableTableProperties) -> Html {
47    let ouia_id = use_memo(props.ouia_id.clone(), |id| {
48        id.clone().unwrap_or(OUIA.generated_id())
49    });
50    let mut class = classes!("pf-v6-c-table", props.class.clone());
51    if props.sticky_header {
52        class.push(classes!("pf-m-sticky-header"));
53    }
54    class.extend_from(&props.grid);
55
56    if !props.borders {
57        class.push(classes!("pf-m-no-border-rows"));
58    }
59
60    match props.mode {
61        TableMode::Compact => {
62            class.push(classes!("pf-m-compact"));
63        }
64        TableMode::CompactNoBorders => {
65            class.push(classes!("pf-m-compact", "pf-m-no-border-rows"));
66        }
67        TableMode::CompactExpandable => {
68            class.push(classes!("pf-m-compact"));
69        }
70        TableMode::Expandable => {
71            class.push(classes!("pf-m-expandable"));
72        }
73        TableMode::Default => {}
74    }
75
76    html! {
77        <table
78            id={&props.id}
79            {class}
80            role="grid"
81            data-ouia-component-id={(*ouia_id).clone()}
82            data-ouia-component-type={props.ouia_type}
83            data-ouia-safe={props.ouia_safe}
84        >
85            { props.children.clone() }
86        </table>
87    }
88}
89
90#[derive(Debug, Clone, PartialEq, Properties)]
91pub struct CaptionProperties {
92    #[prop_or_default]
93    pub children: Html,
94}
95
96#[function_component(Caption)]
97pub fn caption(props: &CaptionProperties) -> Html {
98    html! { <caption class="pf-v6-c-table__caption">{ props.children.clone() }</caption> }
99}
100
101#[derive(Debug, Clone, PartialEq, Properties)]
102pub struct TableBodyProperties {
103    #[prop_or_default]
104    pub class: Classes,
105    #[prop_or_default]
106    pub children: Html,
107    #[prop_or_default]
108    pub expanded: bool,
109}
110
111#[function_component(TableBody)]
112pub fn table_body(props: &TableBodyProperties) -> Html {
113    let mut class = classes!("pf-v6-c-table__tbody", props.class.clone());
114    if props.expanded {
115        class.push("pf-m-expanded");
116    }
117    html! { <tbody {class} role="rowgroup">{ props.children.clone() }</tbody> }
118}
119
120#[derive(Debug, Clone, PartialEq, Properties)]
121pub struct TableRowProperties {
122    #[prop_or_default]
123    pub class: Classes,
124    #[prop_or_default]
125    pub children: Html,
126    #[prop_or_default]
127    pub onclick: Option<Callback<MouseEvent>>,
128    #[prop_or_default]
129    pub selected: bool,
130    #[prop_or_default]
131    pub expandable: bool,
132    #[prop_or_default]
133    pub expanded: bool,
134    #[prop_or_default]
135    pub control_row: bool,
136}
137
138#[function_component(TableRow)]
139pub fn table_row(props: &TableRowProperties) -> Html {
140    let mut class = classes!("pf-v6-c-table__tr", props.class.clone());
141    if props.onclick.is_some() {
142        class.push("pf-m-clickable");
143    }
144    if props.selected {
145        class.push("pf-m-selected");
146    }
147    if props.expanded {
148        class.push("pf-m-expanded");
149    }
150    if props.expandable {
151        class.push("pf-v6-c-table__expandable-row");
152    }
153    if props.control_row {
154        class.push("pf-v6-c-table__control-row");
155    }
156    html! {
157        <tr class={class.clone()} role="row" onclick={props.onclick.clone()}>
158            { props.children.clone() }
159        </tr>
160    }
161}
162
163#[derive(Debug, Clone, PartialEq, Properties)]
164pub struct ExpandableRowContentProperties {
165    #[prop_or_default]
166    pub children: Html,
167    #[prop_or_default]
168    pub class: Classes,
169}
170
171#[function_component(ExpandableRowContent)]
172pub fn expandable_row_content(props: &ExpandableRowContentProperties) -> Html {
173    let class = classes!("pf-v6-c-table__expandable-row-content", props.class.clone());
174    html! { <div {class}>{ props.children.clone() }</div> }
175}
176
177#[derive(Debug, Clone, PartialEq)]
178pub struct ExpandParams {
179    pub r#type: ExpandType,
180    pub expanded: bool,
181    pub ontoggle: Callback<()>,
182}
183
184#[derive(Debug, Clone, Copy, PartialEq, Eq)]
185pub enum ExpandType {
186    Row,
187    Column,
188}
189
190#[derive(Debug, Clone, PartialEq, Properties)]
191pub struct TableDataProperties {
192    #[prop_or_default]
193    pub class: Classes,
194    #[prop_or_default]
195    pub children: Html,
196    #[prop_or_default]
197    pub center: bool,
198    #[prop_or_default]
199    pub text_modifier: Option<TextModifier>,
200    #[prop_or_default]
201    pub expandable: Option<ExpandParams>,
202    #[prop_or_default]
203    pub data_label: Option<AttrValue>,
204    #[prop_or_default]
205    pub span_modifiers: Vec<SpanModifiers>,
206    #[prop_or_default]
207    pub colspan: Option<usize>,
208    #[prop_or_default]
209    pub action: bool,
210}
211
212#[function_component(TableData)]
213pub fn table_data(props: &TableDataProperties) -> Html {
214    let mut class = classes!("pf-v6-c-table__td", props.class.clone());
215    if props.center {
216        class.push(classes!("pf-m-center"))
217    }
218    if props.action {
219        class.push("pf-v6-c-table__action");
220    }
221    class.extend_from(&props.text_modifier);
222    class.extend_from(&props.span_modifiers);
223
224    let mut content = props.children.clone();
225    if let Some(expandable) = props.expandable.as_ref() {
226        let onclick = {
227            let ontoggle = expandable.ontoggle.clone();
228            Callback::from(move |_| ontoggle.emit(()))
229        };
230        class.push(match expandable.r#type {
231            ExpandType::Column => "pf-v6-c-table__compound-expansion-toggle",
232            ExpandType::Row => "pf-v6-c-table__toggle",
233        });
234        content = match expandable.r#type {
235            ExpandType::Column => {
236                if expandable.expanded {
237                    class.push("pf-m-expanded");
238                }
239                html! {
240                    <button class="pf-v6-c-table__button" {onclick}>
241                        <span class="pf-v6-c-table__text">{ content }</span>
242                    </button>
243                }
244            }
245            ExpandType::Row => {
246                let mut button_class = classes!();
247                if expandable.expanded {
248                    button_class.push("pf-m-expanded");
249                }
250                html! {
251                    <Button
252                        variant={ButtonVariant::Plain}
253                        class={button_class}
254                        {onclick}
255                        aria_expanded={expandable.expanded.to_string()}
256                    >
257                        <div class="pf-v6-c-table__toggle-icon">{ Icon::AngleDown }</div>
258                    </Button>
259                }
260            }
261        };
262    }
263
264    let colspan = props.colspan.as_ref().map(|cols| cols.to_string());
265    html! {
266        <td {class} role="cell" data-label={props.data_label.clone()} {colspan}>{ content }</td>
267    }
268}