patternfly_yew/components/button/
mod.rs

1//! Button
2
3use crate::ouia;
4use crate::prelude::{AsClasses, Icon, Spinner, SpinnerSize};
5use crate::utils::{Ouia, OuiaComponentType, OuiaSafe};
6use web_sys::HtmlElement;
7use yew::html::IntoPropValue;
8use yew::prelude::*;
9use yew::virtual_dom::AttrValue;
10
11const OUIA: Ouia = ouia!("Button");
12
13#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
14pub enum ButtonVariant {
15    #[default]
16    None,
17    Primary,
18    Secondary,
19    Tertiary,
20    Warning,
21    Danger,
22    DangerSecondary,
23    Link,
24    InlineLink,
25    Control,
26    Plain,
27}
28
29impl ButtonVariant {
30    pub fn as_classes(&self) -> Vec<&'static str> {
31        match self {
32            Self::None => vec![],
33            Self::Primary => vec!["pf-m-primary"],
34            Self::Secondary => vec!["pf-m-secondary"],
35            Self::Tertiary => vec!["pf-m-tertiary"],
36            Self::Warning => vec!["pf-m-warning"],
37            Self::Danger => vec!["pf-m-danger"],
38            Self::DangerSecondary => vec!["pf-m-danger", "pf-m-secondary"],
39            Self::Link => vec!["pf-m-link"],
40            Self::InlineLink => vec!["pf-m-link", "pf-m-inline"],
41            Self::Control => vec!["pf-m-control"],
42            Self::Plain => vec!["pf-m-plain"],
43        }
44    }
45}
46
47#[derive(Clone, Copy, Default, Eq, PartialEq, Debug)]
48pub enum Align {
49    #[default]
50    Start,
51    End,
52}
53
54/// Button Type.
55///
56/// See: <https://developer.mozilla.org/en-US/docs/Web/HTML/Element/button#attr-type>
57#[derive(Clone, Copy, Default, Eq, PartialEq, Debug)]
58pub enum ButtonType {
59    #[default]
60    Button,
61    Reset,
62    Submit,
63}
64
65impl IntoPropValue<Option<AttrValue>> for ButtonType {
66    fn into_prop_value(self) -> Option<AttrValue> {
67        Some(AttrValue::Static(match self {
68            Self::Submit => "submit",
69            Self::Reset => "reset",
70            Self::Button => "button",
71        }))
72    }
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
76pub enum ButtonSize {
77    Small,
78    #[default]
79    Medium,
80    Large,
81}
82
83impl AsClasses for ButtonSize {
84    fn extend_classes(&self, classes: &mut Classes) {
85        match self {
86            ButtonSize::Small => classes.push("pf-m-small"),
87            ButtonSize::Medium => (),
88            ButtonSize::Large => classes.push("pf-m-display-lg"),
89        };
90    }
91}
92
93/// Properties for [`Button`]
94#[derive(Clone, PartialEq, Properties)]
95pub struct ButtonProperties {
96    #[prop_or_default]
97    pub id: Option<AttrValue>,
98    #[prop_or_default]
99    pub class: Classes,
100    #[prop_or_default]
101    pub style: Option<AttrValue>,
102
103    #[prop_or_default]
104    pub label: String,
105    #[prop_or_default]
106    pub onclick: Callback<MouseEvent>,
107    #[prop_or_default]
108    pub variant: ButtonVariant,
109    #[prop_or_default]
110    pub icon: Option<Icon>,
111    #[prop_or_default]
112    pub align: Align,
113
114    #[prop_or_default]
115    pub disabled: bool,
116
117    #[prop_or_default]
118    pub block: bool,
119
120    #[prop_or_default]
121    pub loading: bool,
122
123    #[prop_or_default]
124    pub aria_label: Option<AttrValue>,
125    #[prop_or_default]
126    pub aria_labelledby: Option<AttrValue>,
127    #[prop_or_default]
128    pub aria_haspopup: Option<AttrValue>,
129    #[prop_or_default]
130    pub aria_expanded: Option<AttrValue>,
131    #[prop_or_default]
132    pub aria_controls: Option<AttrValue>,
133
134    #[prop_or_default]
135    pub r#type: ButtonType,
136
137    #[prop_or_default]
138    pub form: Option<AttrValue>,
139    #[prop_or_default]
140    pub formaction: Option<AttrValue>,
141
142    #[prop_or_default]
143    pub role: Option<AttrValue>,
144
145    #[prop_or_default]
146    pub expanded: bool,
147
148    #[prop_or_default]
149    pub children: Html,
150
151    #[prop_or_default]
152    pub tabindex: Option<isize>,
153
154    #[prop_or_default]
155    pub r#ref: Option<NodeRef>,
156
157    #[prop_or_default]
158    pub size: ButtonSize,
159
160    /// OUIA Component id
161    #[prop_or_default]
162    pub ouia_id: Option<String>,
163    /// OUIA Component Type
164    #[prop_or(OUIA.component_type())]
165    pub ouia_type: OuiaComponentType,
166    /// OUIA Component Safe
167    #[prop_or(OuiaSafe::TRUE)]
168    pub ouia_safe: OuiaSafe,
169}
170
171/// Button component
172///
173/// > A **button** is a box area or text that communicates and triggers user actions when clicked or selected. Buttons can be used to communicate and immediately trigger actions a user can take in an application, like submitting a form, canceling a process, or creating a new object. Buttons can also be used to take a user to a new location, like another page inside of a web application, or an external site such as help or documentation.
174///
175/// See: <https://www.patternfly.org/components/button>
176///
177/// ## Properties
178///
179/// Defined by [`ButtonProperties`].
180#[function_component(Button)]
181pub fn button(props: &ButtonProperties) -> Html {
182    let ouia_id = use_memo(props.ouia_id.clone(), |id| {
183        id.clone().unwrap_or(OUIA.generated_id())
184    });
185    let node_ref = use_node_ref();
186    let node_ref = props.r#ref.as_ref().unwrap_or(&node_ref);
187
188    let mut classes: Classes = classes!(
189        "pf-v5-c-button",
190        props.class.clone(),
191        props.variant.as_classes(),
192        props.size.as_classes(),
193    );
194
195    if props.expanded {
196        classes.push("pf-m-expanded");
197    }
198    if props.block {
199        classes.push("pf-m-block");
200    }
201    if props.loading {
202        classes.push("pf-m-progress pf-m-in-progress")
203    }
204
205    let label = use_memo(
206        (props.label.clone(), props.icon, props.align),
207        |(label, icon, align)| {
208            let mut classes = Classes::from("pf-v5-c-button__icon");
209
210            match align {
211                Align::Start => classes.push("pf-m-start"),
212                Align::End => classes.push("pf-m-end"),
213            }
214
215            let icon = match icon {
216                Some(i) => html! (
217                    <span class={classes}>
218                        { *i }
219                    </span>
220                ),
221                None => html!(),
222            };
223
224            let label = html!(label);
225
226            match align {
227                Align::Start => vec![icon, label],
228                Align::End => vec![label, icon],
229            }
230        },
231    );
232
233    let onclick = {
234        let onclick = props.onclick.clone();
235        let node_ref = node_ref.clone();
236        Callback::from(move |evt| {
237            // Blur (loose focus) on the button element, to remove the focus after clicking
238            if let Some(node) = node_ref.cast::<HtmlElement>() {
239                node.blur().ok();
240            }
241            onclick.emit(evt);
242        })
243    };
244    let tabindex: Option<AttrValue> = props.tabindex.map(|i| i.to_string().into());
245
246    html! (
247         <button
248            ref={node_ref}
249            id={props.id.clone()}
250            class={classes}
251            style={props.style.clone()}
252            disabled={props.disabled}
253            type={props.r#type}
254            {onclick}
255            role={props.role.clone()}
256            form={props.form.clone()}
257            formaction={props.formaction.clone()}
258            aria-label={props.aria_label.clone()}
259            aria-labelledby={&props.aria_labelledby}
260            aria-haspopup={&props.aria_haspopup}
261            aria-expanded={&props.aria_expanded}
262            aria-controls={&props.aria_controls}
263            {tabindex}
264            data-ouia-component-id={(*ouia_id).clone()}
265            data-ouia-component-type={props.ouia_type}
266            data-ouia-safe={props.ouia_safe}
267         >
268             if props.loading {
269                 <span class="pf-v5-c-button__progress">
270                     <Spinner size={SpinnerSize::Md} />
271                 </span>
272             }
273
274             { (*label).clone() }
275             { props.children.clone() }
276
277         </button>
278    )
279}