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-v5-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
91#[derive(Debug, Clone, PartialEq, Properties)]
92pub struct CaptionProperties {
93    #[prop_or_default]
94    pub children: Html,
95}
96
97#[function_component(Caption)]
98pub fn caption(props: &CaptionProperties) -> Html {
99    html! {
100        <caption class="pf-v5-c-table__caption">{props.children.clone()}</caption>
101    }
102}
103
104#[derive(Debug, Clone, PartialEq, Properties)]
105pub struct TableBodyProperties {
106    #[prop_or_default]
107    pub class: Classes,
108    #[prop_or_default]
109    pub children: Html,
110    #[prop_or_default]
111    pub expanded: bool,
112}
113
114#[function_component(TableBody)]
115pub fn table_body(props: &TableBodyProperties) -> Html {
116    let mut class = classes!("pf-v5-c-table__tbody", props.class.clone());
117    if props.expanded {
118        class.push("pf-m-expanded");
119    }
120    html! {
121        <tbody {class} role="rowgroup">
122            {props.children.clone()}
123        </tbody>
124    }
125}
126
127#[derive(Debug, Clone, PartialEq, Properties)]
128pub struct TableRowProperties {
129    #[prop_or_default]
130    pub class: Classes,
131    #[prop_or_default]
132    pub children: Html,
133    #[prop_or_default]
134    pub onclick: Option<Callback<MouseEvent>>,
135    #[prop_or_default]
136    pub selected: bool,
137    #[prop_or_default]
138    pub expandable: bool,
139    #[prop_or_default]
140    pub expanded: bool,
141    #[prop_or_default]
142    pub control_row: bool,
143}
144
145#[function_component(TableRow)]
146pub fn table_row(props: &TableRowProperties) -> Html {
147    let mut class = classes!("pf-v5-c-table__tr", props.class.clone());
148    if props.onclick.is_some() {
149        class.push("pf-m-clickable");
150    }
151    if props.selected {
152        class.push("pf-m-selected");
153    }
154    if props.expanded {
155        class.push("pf-m-expanded");
156    }
157    if props.expandable {
158        class.push("pf-v5-c-table__expandable-row");
159    }
160    if props.control_row {
161        class.push("pf-v5-c-table__control-row");
162    }
163    html! {
164        <tr class={class.clone()} role="row" onclick={props.onclick.clone()}>
165            {props.children.clone()}
166        </tr>
167    }
168}
169
170#[derive(Debug, Clone, PartialEq, Properties)]
171pub struct ExpandableRowContentProperties {
172    #[prop_or_default]
173    pub children: Html,
174    #[prop_or_default]
175    pub class: Classes,
176}
177
178#[function_component(ExpandableRowContent)]
179pub fn expandable_row_content(props: &ExpandableRowContentProperties) -> Html {
180    let class = classes!("pf-v5-c-table__expandable-row-content", props.class.clone());
181    html! {
182        <div {class}>
183            { props.children.clone() }
184        </div>
185    }
186}
187
188#[derive(Debug, Clone, PartialEq)]
189pub struct ExpandParams {
190    pub r#type: ExpandType,
191    pub expanded: bool,
192    pub ontoggle: Callback<()>,
193}
194
195#[derive(Debug, Clone, Copy, PartialEq, Eq)]
196pub enum ExpandType {
197    Row,
198    Column,
199}
200
201#[derive(Debug, Clone, PartialEq, Properties)]
202pub struct TableDataProperties {
203    #[prop_or_default]
204    pub class: Classes,
205    #[prop_or_default]
206    pub children: Html,
207    #[prop_or_default]
208    pub center: bool,
209    #[prop_or_default]
210    pub text_modifier: Option<TextModifier>,
211    #[prop_or_default]
212    pub expandable: Option<ExpandParams>,
213    #[prop_or_default]
214    pub data_label: Option<AttrValue>,
215    #[prop_or_default]
216    pub span_modifiers: Vec<SpanModifiers>,
217    #[prop_or_default]
218    pub colspan: Option<usize>,
219    #[prop_or_default]
220    pub action: bool,
221}
222
223#[function_component(TableData)]
224pub fn table_data(props: &TableDataProperties) -> Html {
225    let mut class = classes!("pf-v5-c-table__td", props.class.clone());
226    if props.center {
227        class.push(classes!("pf-m-center"))
228    }
229    if props.action {
230        class.push("pf-v5-c-table__action");
231    }
232    class.extend_from(&props.text_modifier);
233    class.extend_from(&props.span_modifiers);
234
235    let mut content = props.children.clone();
236    if let Some(expandable) = props.expandable.as_ref() {
237        let onclick = {
238            let ontoggle = expandable.ontoggle.clone();
239            Callback::from(move |_| ontoggle.emit(()))
240        };
241        class.push(match expandable.r#type {
242            ExpandType::Column => "pf-v5-c-table__compound-expansion-toggle",
243            ExpandType::Row => "pf-v5-c-table__toggle",
244        });
245        content = match expandable.r#type {
246            ExpandType::Column => {
247                if expandable.expanded {
248                    class.push("pf-m-expanded");
249                }
250                html! {
251                    <button class="pf-v5-c-table__button" {onclick}>
252                        <span class="pf-v5-c-table__text">
253                            { content }
254                        </span>
255                    </button>
256                }
257            }
258            ExpandType::Row => {
259                let mut button_class = classes!();
260                if expandable.expanded {
261                    button_class.push("pf-m-expanded");
262                }
263                html! {
264                    <Button
265                        variant={ButtonVariant::Plain}
266                        class={button_class}
267                        {onclick}
268                        aria_expanded={expandable.expanded.to_string()}
269                    >
270                        <div class="pf-v5-c-table__toggle-icon">
271                            { Icon::AngleDown }
272                        </div>
273                    </Button>
274                }
275            }
276        };
277    }
278
279    let colspan = props.colspan.as_ref().map(|cols| cols.to_string());
280    html! {
281        <td {class} role="cell" data-label={props.data_label.clone()} {colspan}>
282            { content }
283        </td>
284    }
285}