patternfly_yew/components/button/
mod.rs1use 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#[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#[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 #[prop_or_default]
162 pub ouia_id: Option<String>,
163 #[prop_or(OUIA.component_type())]
165 pub ouia_type: OuiaComponentType,
166 #[prop_or(OuiaSafe::TRUE)]
168 pub ouia_safe: OuiaSafe,
169}
170
171#[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 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}