patternfly_yew/components/form/
input.rs1use crate::ouia;
2use crate::prelude::{
3 focus, use_on_text_change, Icon, InputState, ValidatingComponent,
4 ValidatingComponentProperties, ValidationContext,
5};
6use crate::utils::{Ouia, OuiaComponentType, OuiaSafe};
7use yew::html::IntoPropValue;
8use yew::prelude::*;
9use yew::virtual_dom::VNode;
10
11const OUIA: Ouia = ouia!("TextInput");
12
13#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
14pub enum TextInputType {
15 Date,
16 DateTimeLocal,
17 Email,
18 Month,
19 Number,
20 Password,
21 Search,
22 #[default]
23 Text,
24 Tel,
25 Time,
26 Url,
27}
28
29impl IntoPropValue<Option<AttrValue>> for TextInputType {
30 fn into_prop_value(self) -> Option<AttrValue> {
31 Some(AttrValue::Static(match self {
32 Self::Date => "date",
33 Self::DateTimeLocal => "datetime-local",
34 Self::Email => "email",
35 Self::Month => "month",
36 Self::Number => "number",
37 Self::Password => "password",
38 Self::Search => "search",
39 Self::Text => "text",
40 Self::Tel => "tel",
41 Self::Time => "time",
42 Self::Url => "url",
43 }))
44 }
45}
46
47#[derive(Clone, PartialEq, Properties)]
49pub struct TextInputProperties {
50 #[prop_or_default]
51 pub class: Classes,
52 #[prop_or_default]
53 pub name: Option<AttrValue>,
54 #[prop_or_default]
55 pub id: Option<AttrValue>,
56 #[prop_or_default]
57 pub value: AttrValue,
58 #[prop_or_default]
59 pub size: Option<AttrValue>,
60 #[prop_or_default]
61 pub required: bool,
62 #[prop_or_default]
63 pub disabled: bool,
64 #[prop_or_default]
65 pub readonly: bool,
66 #[prop_or_default]
67 pub state: InputState,
68 #[prop_or_default]
69 pub icon: Option<Icon>,
70 #[prop_or_default]
71 pub r#type: TextInputType,
72 #[prop_or_default]
73 pub placeholder: Option<AttrValue>,
74 #[prop_or_default]
75 pub autofocus: bool,
76 #[prop_or_default]
77 pub form: Option<AttrValue>,
78 #[prop_or_default]
79 pub autocomplete: Option<AttrValue>,
80 #[prop_or_default]
81 pub inputmode: Option<AttrValue>,
82 #[prop_or_default]
83 pub enterkeyhint: Option<AttrValue>,
84 #[prop_or_default]
85 pub aria_describedby: Option<AttrValue>,
86
87 #[prop_or_default]
92 pub onchange: Callback<String>,
93 #[prop_or_default]
97 pub oninput: Callback<InputEvent>,
98
99 #[prop_or_default]
101 pub onvalidate: Callback<ValidationContext<String>>,
102
103 #[prop_or_default]
104 pub onkeydown: Callback<KeyboardEvent>,
105
106 #[prop_or_default]
107 pub onblur: Callback<FocusEvent>,
108
109 #[prop_or_default]
110 pub r#ref: NodeRef,
111
112 #[prop_or_default]
114 pub ouia_id: Option<String>,
115 #[prop_or(OUIA.component_type())]
117 pub ouia_type: OuiaComponentType,
118 #[prop_or(OuiaSafe::TRUE)]
120 pub ouia_safe: OuiaSafe,
121}
122
123impl ValidatingComponent for TextInput {
124 type Value = String;
125}
126
127impl ValidatingComponentProperties<String> for TextInputProperties {
128 fn set_onvalidate(&mut self, onvalidate: Callback<ValidationContext<String>>) {
129 self.onvalidate = onvalidate;
130 }
131
132 fn set_input_state(&mut self, state: InputState) {
133 self.state = state;
134 }
135}
136
137#[function_component(TextInput)]
171pub fn text_input(props: &TextInputProperties) -> Html {
172 let ouia_id = use_memo(props.ouia_id.clone(), |id| {
173 id.clone().unwrap_or(OUIA.generated_id())
174 });
175 let input_ref = props.r#ref.clone();
176 let mut classes = classes!("pf-v5-c-form-control", props.class.clone());
177
178 if props.disabled {
179 classes.push("pf-m-disabled")
180 }
181
182 if props.readonly {
183 classes.push("pf-m-readonly")
184 }
185
186 if props.icon.is_some() {
187 classes.push("pf-m-icon");
188 }
189
190 {
192 let value = props.value.to_string();
193 let onvalidate = props.onvalidate.clone();
194 use_effect_with((), move |()| {
195 onvalidate.emit(ValidationContext {
196 value,
197 initial: true,
198 });
199 });
200 }
201
202 let (classes, aria_invalid) = props.state.convert(classes);
203
204 {
207 let input_ref = input_ref.clone();
208 use_effect_with(props.autofocus, move |autofocus| {
209 if *autofocus {
210 focus(&input_ref)
211 }
212 });
213 }
214
215 let onchange = use_callback(
217 (props.onchange.clone(), props.onvalidate.clone()),
218 |new_value: String, (onchange, onvalidate)| {
219 onchange.emit(new_value.clone());
220 onvalidate.emit(new_value.into());
221 },
222 );
223 let oninput = use_on_text_change(input_ref.clone(), props.oninput.clone(), onchange);
224
225 let icon_html = props.icon.map(|icon| {
226 html!(
227 <div class="pf-v5-c-form-control__icon">
228 { icon }
229 </div>
230 )
231 });
232
233 let status_html = if props.state != InputState::Default {
234 Some(html!(
235 <div class="pf-v5-c-form-control__icon pf-m-status">
236 {props.state.icon()}
237 </div>
238 ))
239 } else {
240 None
241 };
242
243 html! (
244 <div class={classes}>
245 <input
246 ref={input_ref}
247 type={props.r#type}
248 name={&props.name}
249 id={&props.id}
250 size={&props.size}
251 required={props.required}
252 disabled={props.disabled}
253 readonly={props.readonly}
254 aria-describedby={&props.aria_describedby}
255 aria-invalid={aria_invalid.to_string()}
256 value={props.value.clone()}
257 placeholder={&props.placeholder}
258 form={&props.form}
259 autocomplete={&props.autocomplete}
260 {oninput}
261 onkeydown={&props.onkeydown}
262 onblur={&props.onblur}
263 inputmode={&props.inputmode}
264 enterkeyhint={&props.enterkeyhint}
265 data-ouia-component-id={(*ouia_id).clone()}
266 data-ouia-component-type={props.ouia_type}
267 data-ouia-safe={props.ouia_safe}
268 />
269
270 { None::<VNode> }
271
272 if icon_html.is_some() || status_html.is_some() {
273 <div class="pf-v5-c-form-control__utilities"> { icon_html }
275 { status_html }
276 </div>
277 }
278 </div>
279 )
280}