use std::{fmt::Display, str::FromStr};
use crate::prelude::*;
use num_traits::PrimInt;
use yew::prelude::*;
#[derive(Debug, Clone, PartialEq)]
pub enum NumberInputUnit {
Before(Html),
After(Html),
}
#[derive(Debug, Clone, PartialEq, Properties)]
pub struct NumberInputProperties<T: PrimInt + Display + FromStr + 'static> {
#[prop_or(T::zero())]
pub value: T,
#[prop_or_default]
pub class: Classes,
#[prop_or_default]
pub width_chars: Option<u8>,
#[prop_or_default]
pub disabled: bool,
#[prop_or_default]
pub onminus: Option<Callback<()>>,
#[prop_or_default]
pub onchange: Option<Callback<T>>,
#[prop_or_default]
pub onplus: Option<Callback<()>>,
#[prop_or_default]
pub unit: Option<NumberInputUnit>,
#[prop_or(T::min_value())]
pub min: T,
#[prop_or(T::max_value())]
pub max: T,
#[prop_or_default]
pub state: InputState,
#[prop_or_default]
pub input_name: Option<String>,
#[prop_or(AttrValue::from("Minus"))]
pub minus_button_aria_label: AttrValue,
#[prop_or(AttrValue::from("Plus"))]
pub plus_button_aria_label: AttrValue,
}
#[function_component(NumberInput)]
pub fn number_input<T: PrimInt + Display + FromStr + 'static>(
props: &NumberInputProperties<T>,
) -> Html {
let mut class = props.class.clone();
class.push("pf-v5-c-number-input");
if props.state != InputState::Default {
class.push("pf-m-status");
}
let width_style_name = "--pf-v5-c-number-input--c-form-control--width-chars";
let style = props
.width_chars
.map(|w| format!("{width_style_name}:{w};"));
let onminusclick = use_callback(props.onminus.clone(), |_, onminus| {
if let Some(onminus) = onminus {
onminus.emit(());
}
});
let onplusclick = use_callback(props.onplus.clone(), |_, onplus| {
if let Some(onplus) = onplus {
onplus.emit(());
}
});
let onchange = use_callback(props.onchange.clone(), |new_val: String, onchange| {
let Some(onchange) = onchange else {
return;
};
match new_val.parse::<T>() {
Ok(n) => onchange.emit(n),
Err(_) => log::warn!("[NumberInput] Failed to parse {new_val} into a number."),
};
});
html! {
<div {class} {style}>
if let Some(NumberInputUnit::Before(unit)) = &props.unit {
<Unit>{unit.clone()}</Unit>
}
<InputGroup>
<InputGroupItem>
<Button
variant={ButtonVariant::Control}
aria_label={props.minus_button_aria_label.clone()}
disabled={props.disabled || props.value <= props.min}
onclick={onminusclick}
>
<span class="pf-v5-c-number-input__icon">
{Icon::Minus}
</span>
</Button>
</InputGroupItem>
<InputGroupItem>
<TextInput
r#type={TextInputType::Number}
value={props.value.to_string()}
name={props.input_name.clone()}
disabled={props.disabled}
onchange={onchange}
state={props.state}
/>
</InputGroupItem>
<InputGroupItem>
<Button
variant={ButtonVariant::Control}
aria_label={props.plus_button_aria_label.clone()}
disabled={props.disabled || props.value >= props.max}
onclick={onplusclick}
>
<span class="pf-v5-c-number-input__icon">
{Icon::Plus}
</span>
</Button>
</InputGroupItem>
</InputGroup>
if let Some(NumberInputUnit::After(unit)) = &props.unit {
<Unit>{unit.clone()}</Unit>
}
</div>
}
}
#[derive(Debug, Clone, PartialEq, Properties)]
struct UnitProperties {
children: Html,
}
#[function_component(Unit)]
fn unit(props: &UnitProperties) -> Html {
html!(<div class="pf-v5-c-number-input__unit">{props.children.clone()}</div>)
}