patternfly_yew/components/form/
checkbox.rs1use crate::ouia;
2use crate::prelude::OuiaComponentType;
3use crate::utils::OuiaSafe;
4use crate::{core::OptionalHtml, hooks::id::use_prop_id, utils::Ouia};
5use web_sys::HtmlInputElement;
6use yew::html::IntoPropValue;
7use yew::prelude::*;
8
9const OUIA: Ouia = ouia!("Checkbox");
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
20pub enum CheckboxState {
21 Checked,
22 #[default]
23 Unchecked,
24 Indeterminate,
25}
26
27impl From<bool> for CheckboxState {
28 fn from(b: bool) -> Self {
29 match b {
30 true => Self::Checked,
31 false => Self::Unchecked,
32 }
33 }
34}
35
36impl From<CheckboxState> for bool {
37 fn from(value: CheckboxState) -> Self {
38 match value {
39 CheckboxState::Checked => true,
40 CheckboxState::Indeterminate | CheckboxState::Unchecked => false,
41 }
42 }
43}
44
45impl From<CheckboxState> for Option<bool> {
46 fn from(value: CheckboxState) -> Self {
47 match value {
48 CheckboxState::Checked => Some(true),
49 CheckboxState::Unchecked => Some(false),
50 CheckboxState::Indeterminate => None,
51 }
52 }
53}
54
55impl IntoPropValue<CheckboxState> for bool {
56 fn into_prop_value(self) -> CheckboxState {
57 self.into()
58 }
59}
60
61#[derive(Debug, Clone, PartialEq, Properties)]
63pub struct CheckboxProperties {
64 #[prop_or_default]
66 pub id: Option<String>,
67
68 #[prop_or_default]
70 pub name: Option<AttrValue>,
71
72 #[prop_or_default]
74 pub class: Classes,
75
76 #[prop_or_default]
78 pub input_class: Classes,
79
80 #[prop_or_default]
82 pub valid: bool,
83
84 #[prop_or_default]
86 pub disabled: bool,
87
88 #[prop_or_default]
90 pub required: bool,
91
92 #[prop_or_default]
94 pub checked: CheckboxState,
95
96 #[prop_or_default]
98 pub onchange: Callback<CheckboxState>,
99
100 #[prop_or_default]
102 pub label: OptionalHtml,
103
104 #[prop_or_default]
106 pub aria_label: AttrValue,
107
108 #[prop_or_default]
110 pub description: OptionalHtml,
111
112 #[prop_or_default]
114 pub body: Option<Html>,
115
116 #[prop_or(String::from("div"))]
118 pub component: String,
119
120 #[prop_or_default]
122 pub ouia_id: Option<String>,
123 #[prop_or(OUIA.component_type())]
125 pub ouia_type: OuiaComponentType,
126 #[prop_or(OuiaSafe::TRUE)]
128 pub ouia_safe: OuiaSafe,
129}
130
131#[function_component(Checkbox)]
141pub fn checkbox(props: &CheckboxProperties) -> Html {
142 let ouia_id = use_memo(props.ouia_id.clone(), |id| {
143 id.clone().unwrap_or(OUIA.generated_id())
144 });
145 let id = use_prop_id(props.id.clone());
146 let mut outer_class = classes!["pf-v5-c-check", props.class.clone()];
147
148 if props.label.is_none() {
149 outer_class.push("pf-m-standalone");
150 }
151
152 let node_ref = use_node_ref();
153
154 {
155 let node_ref = node_ref.clone();
156 let checked = props.checked;
157 use_effect(move || {
158 if let Some(elem) = node_ref.cast::<HtmlInputElement>() {
159 elem.set_indeterminate(checked == CheckboxState::Indeterminate)
160 }
161 });
162 }
163
164 let onchange = use_callback(
165 (props.onchange.clone(), node_ref.clone()),
166 |_: Event, (onchange, node_ref)| {
167 let checked = node_ref
168 .cast::<HtmlInputElement>()
169 .map(|input| input.checked().into())
170 .unwrap_or_default();
171 onchange.emit(checked);
172 },
173 );
174
175 let label = if let Some(label) = &props.label.0 {
176 let mut class = classes!["pf-v5-c-check__label"];
177 if props.disabled {
178 class.push("pf-m-disabled");
179 }
180 html! (
181 <label {class} for={(*id).clone()}>
182 {label.clone()}
183 if props.required {
184 <span class="pf-v5-c-check__label-required" aria-hidden="true">{"*"}</span>
185 }
186 </label>
187 )
188 } else {
189 html!()
190 };
191
192 html! (
193 <@{props.component.clone()} class={outer_class}>
194 <input
195 class={classes!["pf-v5-c-check__input", props.input_class.clone()]}
196 type="checkbox"
197 {onchange}
198 aria-invalid={props.valid.to_string()}
199 aria-label={props.aria_label.clone()}
200 disabled={props.disabled}
201 required={props.required}
202 id={(*id).clone()}
203 ref={node_ref.clone()}
204 checked={props.checked != CheckboxState::Unchecked}
205 data-ouia-component-id={(*ouia_id).clone()}
206 data-ouia-component-type={props.ouia_type}
207 data-ouia-safe={props.ouia_safe}
208 />
209 {label}
210 if let Some(description) = &props.description.0 {
211 <span class="pf-v5-c-check__description">{description.clone()}</span>
212 }
213 if let Some(body) = &props.body {
214 <span class="pf-v5-c-check__body">{body.clone()}</span>
215 }
216 </@>
217 )
218}