use yew::prelude::*;
use super::*;
#[derive(Clone, PartialEq)]
pub enum FormControlValidation {
None,
Valid(Option<AttrValue>),
Invalid(AttrValue),
}
#[derive(Properties, Clone, PartialEq)]
pub struct FormControlProps {
pub ctype: FormControlType,
pub id: AttrValue,
#[prop_or_default]
pub class: Classes,
#[prop_or_default]
pub label: Option<AttrValue>,
#[prop_or_default]
pub placeholder: Option<AttrValue>,
#[prop_or_default]
pub help: Option<AttrValue>,
#[prop_or(FormAutocompleteType::Off)]
pub autocomplete: FormAutocompleteType,
#[prop_or_default]
pub name: AttrValue,
#[prop_or_default]
pub value: AttrValue,
#[prop_or_default]
pub required: bool,
#[prop_or_default]
pub checked: bool,
#[prop_or_default]
pub disabled: bool,
#[prop_or_default]
pub floating: bool,
#[prop_or_default]
pub multiple: bool,
#[prop_or_default]
pub children: Children,
#[prop_or(FormControlValidation::None)]
pub validation: FormControlValidation,
#[prop_or_default]
pub onchange: Callback<Event>,
#[prop_or_default]
pub oninput: Callback<InputEvent>,
#[prop_or_default]
pub onclick: Callback<MouseEvent>,
#[prop_or_default]
pub node_ref: NodeRef,
}
fn convert_to_string_option<T>(value: &Option<T>) -> Option<AttrValue>
where T: std::fmt::Display {
value.as_ref().map(|v| AttrValue::from(v.to_string()))
}
#[function_component]
pub fn FormControl(props: &FormControlProps) -> Html {
let label = match props.label.clone() {
None => None,
Some(text) => {
let class = if props.floating { None } else { Some("form-label") };
Some(html! {
<label for={ props.id.clone() } class={ class }>{ text.clone() }</label>
})
}
};
let help = props.help.as_ref().map(|text| html! {
<div class="form-text">{ text.clone() }</div>
});
let (validation, validation_class) = match props.validation.clone() {
FormControlValidation::None => (None, None),
FormControlValidation::Valid(None) => (None, Some("is-valid")),
FormControlValidation::Valid(Some(text)) => (Some(html! {
<div class="valid-feedback"> { text.clone() }</div>
}), Some("is-valid")),
FormControlValidation::Invalid(text) => (Some(html! {
<div class="invalid-feedback"> { text.clone() }</div>
}), Some("is-invalid")),
};
let pattern = match &props.ctype {
FormControlType::Email{ pattern } => pattern,
FormControlType::Url{ pattern } => pattern,
_ => &None,
};
let mut placeholder = props.placeholder.clone();
if props.floating && placeholder.is_none() {
placeholder = Some(props.label.clone().expect("When floating is set, label cannot be None"));
}
match &props.ctype {
FormControlType::TextArea { cols, rows } => {
let mut classes = classes!(props.class.clone());
if props.floating {
classes.push("form-floating");
}
let input_classes = classes!("form-control", validation_class);
let cols_str = convert_to_string_option(cols);
let rows_str = convert_to_string_option(rows);
let (label_before, label_after) =
if props.floating { (None, label) } else { (label, None) };
html! {
<div class={ classes }>
{ label_before }
<textarea
class={ input_classes }
id={ props.id.clone() }
name={ props.name.clone() }
cols={ cols_str }
rows={ rows_str }
placeholder={ placeholder }
value={ props.value.clone() }
disabled={ props.disabled }
oninput={props.oninput.clone() }
onchange={ props.onchange.clone() }
onclick={ props.onclick.clone() }
required={ props.required }
autocomplete={ props.autocomplete.to_str() }
ref={ props.node_ref.clone() }
/>
{ label_after }
{ help }
{ validation }
</div>
}
},
FormControlType::Select => {
let mut classes = classes!(props.class.clone());
if props.floating {
classes.push("form-floating");
}
let input_classes = classes!("form-select", validation_class);
let (label_before, label_after) =
if props.floating { (None, label) } else { (label, None) };
html! {
<div class={ classes }>
{ label_before }
<select
class={ input_classes }
id={ props.id.clone()}
name={ props.name.clone() }
disabled={ props.disabled }
onchange={ props.onchange.clone() }
onclick={ props.onclick.clone() }
required={ props.required }
ref={ props.node_ref.clone() }
>
{ for props.children.clone() }
</select>
{ label_after }
{ help }
{ validation }
</div>
}
},
FormControlType::Checkbox | FormControlType::Radio => {
let mut classes = classes!("form-check");
classes.push(props.class.clone());
let input_classes = classes!("form-check-input", validation_class);
html! {
<div class={ classes }>
<input
type={ props.ctype.to_str() }
class={ input_classes }
id={ props.id.clone() }
name={ props.name.clone() }
checked={ props.checked }
disabled={ props.disabled }
value={ props.value.clone() }
onchange={ props.onchange.clone() }
onclick={ props.onclick.clone() }
required={ props.required }
ref={ props.node_ref.clone() }
/>
{ label }
{ help }
{ validation}
</div>
}
},
_ => {
let mut min_str = None;
let mut max_str = None;
let mut step_str = None;
let mut accept_str = None;
match &props.ctype {
FormControlType::Number { min, max } => {
min_str = convert_to_string_option(min);
max_str = convert_to_string_option(max);
},
FormControlType::Range { min, max, step } => {
min_str = Some(AttrValue::from(min.to_string()));
max_str = Some(AttrValue::from(max.to_string()));
step_str = convert_to_string_option(step);
},
FormControlType::DateMinMax { min, max } |
FormControlType::DatetimeMinMax { min, max } |
FormControlType::TimeMinMax { min, max } => {
min_str = min.clone();
max_str = max.clone();
},
FormControlType::File { accept } => {
let accept_vec : Vec<String> = accept.clone().iter().cloned().map(
move |value| { value.to_string() }
).collect();
accept_str = Some(accept_vec.join(", "));
}
_ => ()
}
let mut classes = classes!(props.class.clone());
if props.floating {
classes.push("form-floating");
}
let input_classes = classes!("form-control", validation_class);
let (label_before, label_after) =
if props.floating { (None, label) } else { (label, None) };
html! {
<div class={ classes }>
{ label_before }
<input
type={ props.ctype.to_str() }
class={ input_classes }
id={ props.id.clone() }
name={ props.name.clone() }
value={ props.value.clone() }
pattern={ pattern }
accept={ accept_str }
placeholder={ placeholder }
min={ min_str }
max={ max_str }
step={ step_str }
disabled={ props.disabled }
onchange={ props.onchange.clone() }
onclick={ props.onclick.clone() }
oninput={ props.oninput.clone() }
required={ props.required }
autocomplete={ props.autocomplete.to_str() }
ref={ props.node_ref.clone() }
/>
{ label_after }
{ help }
{ validation }
</div>
}
}
}
}