use crate::prelude::{
focus, use_on_text_change, AsClasses, ExtendClasses, InputState, ValidatingComponent,
ValidatingComponentProperties, ValidationContext,
};
use std::fmt::{Display, Formatter};
use yew::prelude::*;
#[derive(Clone, Default, PartialEq, Eq)]
pub enum ResizeOrientation {
Horizontal,
Vertical,
#[default]
Both,
}
impl AsClasses for ResizeOrientation {
fn extend_classes(&self, classes: &mut Classes) {
match self {
ResizeOrientation::Horizontal => classes.push("pf-m-resize-horizontal"),
ResizeOrientation::Vertical => classes.push("pf-m-resize-vertical"),
ResizeOrientation::Both => classes.push("pf-m-resize-both"),
}
}
}
#[derive(Clone, Default, PartialEq, Eq)]
pub enum Wrap {
Hard,
#[default]
Soft,
Off,
}
impl Display for Wrap {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::Off => f.write_str("off"),
Self::Soft => f.write_str("soft"),
Self::Hard => f.write_str("hard"),
}
}
}
#[derive(Clone, PartialEq, Properties)]
pub struct TextAreaProperties {
#[prop_or_default]
pub name: Option<AttrValue>,
#[prop_or_default]
pub id: Option<AttrValue>,
#[prop_or_default]
pub value: String,
#[prop_or_default]
pub required: bool,
#[prop_or_default]
pub disabled: bool,
#[prop_or_default]
pub readonly: bool,
#[prop_or_default]
pub state: InputState,
#[prop_or_default]
pub placeholder: Option<AttrValue>,
#[prop_or_default]
pub autofocus: bool,
#[prop_or_default]
pub form: Option<AttrValue>,
#[prop_or_default]
pub autocomplete: Option<AttrValue>,
#[prop_or_default]
pub spellcheck: Option<AttrValue>,
#[prop_or_default]
pub wrap: Wrap,
#[prop_or_default]
pub rows: Option<usize>,
#[prop_or_default]
pub cols: Option<usize>,
#[prop_or_default]
pub resize: ResizeOrientation,
#[prop_or_default]
pub onchange: Callback<String>,
#[prop_or_default]
pub oninput: Callback<InputEvent>,
#[prop_or_default]
pub onvalidate: Callback<ValidationContext<String>>,
#[prop_or_default]
pub r#ref: NodeRef,
}
impl ValidatingComponent for TextArea {
type Value = String;
}
impl ValidatingComponentProperties<String> for TextAreaProperties {
fn set_onvalidate(&mut self, onvalidate: Callback<ValidationContext<String>>) {
self.onvalidate = onvalidate;
}
fn set_input_state(&mut self, state: InputState) {
self.state = state;
}
}
#[function_component(TextArea)]
pub fn text_area(props: &TextAreaProperties) -> Html {
let input_ref = props.r#ref.clone();
let mut classes = classes!("pf-v5-c-form-control");
classes.extend_from(&props.resize);
if props.readonly {
classes.push("pf-m-readonly");
}
{
let value = props.value.clone();
let onvalidate = props.onvalidate.clone();
use_effect_with((), move |()| {
onvalidate.emit(ValidationContext {
value,
initial: true,
});
});
}
let (classes, aria_invalid) = props.state.convert(classes);
{
let input_ref = input_ref.clone();
use_effect_with(props.autofocus, move |autofocus| {
if *autofocus {
focus(&input_ref)
}
});
}
let onchange = use_callback(
(props.onchange.clone(), props.onvalidate.clone()),
|new_value: String, (onchange, onvalidate)| {
onchange.emit(new_value.clone());
onvalidate.emit(new_value.into());
},
);
let oninput = use_on_text_change(input_ref.clone(), props.oninput.clone(), onchange);
html!(
<div class={classes}>
<textarea
ref={input_ref}
name={&props.name}
id={&props.id}
required={props.required}
disabled={props.disabled}
readonly={props.readonly}
aria-invalid={aria_invalid.to_string()}
value={props.value.clone()}
placeholder={&props.placeholder}
form={&props.form}
autocomplete={&props.autocomplete}
cols={props.cols.as_ref().map(|v|v.to_string())}
rows={props.rows.as_ref().map(|v|v.to_string())}
wrap={props.wrap.to_string()}
spellcheck={&props.spellcheck}
{oninput}
/>
if props.state != InputState::Default {
<div class="pf-v5-c-form-control__utilities">
<div class="pf-v5-c-form-control__icon pf-m-status">
{props.state.icon()}
</div>
</div>
}
</div>
)
}