pub mod countries;
use crate::countries::COUNTRY_CODES;
use web_sys::HtmlInputElement;
use yew::prelude::*;
#[derive(Properties, PartialEq, Clone)]
pub struct Props {
#[prop_or("text")]
pub input_type: &'static str,
#[prop_or_default]
pub label: &'static str,
#[prop_or_default]
pub name: &'static str,
#[prop_or_default]
pub required: bool,
pub input_ref: NodeRef,
#[prop_or_default]
pub error_message: &'static str,
#[prop_or_default]
pub form_input_class: &'static str,
#[prop_or_default]
pub form_input_field_class: &'static str,
#[prop_or_default]
pub form_input_label_class: &'static str,
#[prop_or_default]
pub form_input_input_class: &'static str,
#[prop_or_default]
pub form_input_error_class: &'static str,
#[prop_or_default]
pub icon_class: &'static str,
pub input_handle: UseStateHandle<String>,
pub input_valid_handle: UseStateHandle<bool>,
pub validate_function: Callback<String, bool>,
#[prop_or("fa fa-eye")]
pub eye_active: &'static str,
#[prop_or("fa fa-eye-slash")]
pub eye_disabled: &'static str,
#[prop_or_default]
pub input_id: &'static str,
#[prop_or_default]
pub input_placeholder: &'static str,
#[prop_or_default]
pub aria_label: &'static str,
#[prop_or("true")]
pub aria_required: &'static str,
#[prop_or("true")]
pub aria_invalid: &'static str,
#[prop_or_default]
pub aria_describedby: &'static str,
}
#[function_component(CustomInput)]
pub fn custom_input(props: &Props) -> Html {
let eye_active_handle = use_state(|| false);
let eye_active = *eye_active_handle;
let input_country_ref = use_node_ref();
let country_handle = use_state(String::default);
let country = (*country_handle).clone();
let password_type_handle = use_state(|| "password");
let password_type = *password_type_handle;
let input_valid = *props.input_valid_handle;
let aria_invalid = props.aria_invalid;
let eye_icon_active = props.eye_active;
let eye_icon_disabled = props.eye_disabled;
let aria_required = props.aria_required;
let input_type = props.input_type;
let onchange = {
let input_ref = props.input_ref.clone();
let input_handle = props.input_handle.clone();
let input_valid_handle = props.input_valid_handle.clone();
let validate_function = props.validate_function.clone();
Callback::from(move |_| {
if let Some(input) = input_ref.cast::<HtmlInputElement>() {
let value = input.value();
input_handle.set(value);
input_valid_handle.set(validate_function.emit(input.value()));
}
})
};
let on_select_change = {
let input_country_ref = input_country_ref.clone();
let input_handle = props.input_handle.clone();
let country_handle = country_handle.clone();
Callback::from(move |_| {
if let Some(input) = input_country_ref.cast::<HtmlInputElement>() {
let value = input.value();
country_handle.set(value);
input_handle.set(input.value());
}
})
};
let on_phone_number_input = {
let input_ref = props.input_ref.clone();
let input_handle = props.input_handle.clone();
let country_handle = country_handle;
Callback::from(move |_| {
if let Some(input) = input_ref.cast::<HtmlInputElement>() {
for (code, _, _, _, _, _) in &COUNTRY_CODES {
if code.starts_with(&input.value()) {
country_handle.set(input.value());
break;
}
}
let numeric_value: String =
input.value().chars().filter(|c| c.is_numeric()).collect();
input_handle.set('+'.to_string() + &numeric_value);
}
})
};
let on_toggle_password = {
Callback::from(move |_| {
if eye_active {
password_type_handle.set("password")
} else {
password_type_handle.set("text")
}
eye_active_handle.set(!eye_active);
})
};
let input_tag = match (*input_type).into() {
"password" => html! {
<>
<input
type={password_type}
class={props.form_input_input_class}
id={props.input_id}
name={props.name}
value={(*props.input_handle).clone()}
ref={props.input_ref.clone()}
placeholder={props.input_placeholder}
aria-label={props.aria_label}
aria-required={aria_required}
aria-invalid={aria_invalid}
aria-describedby={props.aria_describedby}
oninput={onchange}
required={props.required}
/>
<span
class={format!("toggle-button {}", if eye_active { eye_icon_active } else { eye_icon_disabled })}
onclick={on_toggle_password}
/>
</>
},
"textarea" => html! {
<textarea
class={props.form_input_input_class}
id={props.input_id}
name={props.name}
value={(*props.input_handle).clone()}
ref={props.input_ref.clone()}
placeholder={props.input_placeholder}
aria-label={props.aria_label}
aria-required={aria_required}
aria-invalid={aria_invalid}
aria-describedby={props.aria_describedby}
oninput={onchange}
required={props.required}
/>
},
"tel" => html! {
<>
<select ref={input_country_ref} onchange={on_select_change}>
{ for COUNTRY_CODES.iter().map(|(code, emoji, _, name, _, _)| {
let selected = *code == country;
html! {
<option value={*code} selected={selected}>{ format!("{} {} {}", emoji, name, code) }</option>
}
}) }
</select>
<input
type="tel"
id="telNo"
name="telNo"
size="20"
minlength="9"
value={(*props.input_handle).clone()}
maxlength="14"
class={props.form_input_input_class}
placeholder={props.input_placeholder}
aria-label={props.aria_label}
aria-required={aria_required}
aria-invalid={aria_invalid}
oninput={on_phone_number_input}
ref={props.input_ref.clone()}
/>
</>
},
_ => html! {
<input
type={input_type}
class={props.form_input_input_class}
id={props.input_id}
value={(*props.input_handle).clone()}
name={props.name}
ref={props.input_ref.clone()}
placeholder={props.input_placeholder}
aria-label={props.aria_label}
aria-required={aria_required}
aria-invalid={aria_invalid}
aria-describedby={props.aria_describedby}
oninput={onchange}
required={props.required}
/>
},
};
html! {
<div class={props.form_input_class}>
<label class={props.form_input_label_class} for={props.input_id}>{ props.label }</label>
<div class={props.form_input_field_class}>
{ input_tag }
<span class={props.icon_class} />
</div>
if !input_valid {
<div class={props.form_input_error_class} id={props.aria_describedby}>
{ &props.error_message }
</div>
}
</div>
}
}