patternfly_yew/components/button/
mod.rs1use crate::ouia;
4use crate::prelude::{AsClasses, 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<Html>,
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-v6-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 has_text = !props.label.is_empty() || props.children != Html::default();
206
207 let icon = match &props.icon {
208 Some(icon) => {
209 let mut classes = Classes::from("pf-v6-c-button__icon");
210
211 if has_text {
213 match props.align {
214 Align::Start => classes.push("pf-m-start"),
215 Align::End => classes.push("pf-m-end"),
216 }
217 }
218
219 html!(<span class={classes}>{ icon.clone() }</span>)
220 }
221 None => html!(),
222 };
223
224 let text = match has_text {
225 true => html!(
226 <span class="pf-v6-c-button__text">
227 { &props.label }
228 { props.children.clone() }
229 </span>
230 ),
231 false => html!(),
232 };
233
234 let content = match props.align {
235 Align::Start => html!(<>{ icon }{ text }</>),
236 Align::End => html!(<>{ text }{ icon }</>),
237 };
238
239 let onclick = {
240 let onclick = props.onclick.clone();
241 let node_ref = node_ref.clone();
242 Callback::from(move |evt| {
243 if let Some(node) = node_ref.cast::<HtmlElement>() {
245 node.blur().ok();
246 }
247 onclick.emit(evt);
248 })
249 };
250 let tabindex: Option<AttrValue> = props.tabindex.map(|i| i.to_string().into());
251
252 html! (
253 <button
254 ref={node_ref}
255 id={props.id.clone()}
256 class={classes}
257 style={props.style.clone()}
258 disabled={props.disabled}
259 type={props.r#type}
260 {onclick}
261 role={props.role.clone()}
262 form={props.form.clone()}
263 formaction={props.formaction.clone()}
264 aria-label={props.aria_label.clone()}
265 aria-labelledby={&props.aria_labelledby}
266 aria-haspopup={&props.aria_haspopup}
267 aria-expanded={&props.aria_expanded}
268 aria-controls={&props.aria_controls}
269 {tabindex}
270 data-ouia-component-id={(*ouia_id).clone()}
271 data-ouia-component-type={props.ouia_type}
272 data-ouia-safe={props.ouia_safe}
273 >
274 if props.loading {
275 <span class="pf-v6-c-button__progress">
276 <Spinner size={SpinnerSize::Md} />
277 </span>
278 }
279 { content }
280 </button>
281 )
282}