use crate::{Button, Validator};
use yew::prelude::*;
use yew::web_sys::HtmlInputElement;
#[derive(Clone, PartialEq, Properties)]
pub struct Props {
#[prop_or_default]
pub children: Children,
}
pub struct Form {
props: Props,
}
impl Component for Form {
type Message = ();
type Properties = Props;
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self {
Self { props }
}
fn update(&mut self, _msg: Self::Message) -> bool {
true
}
fn change(&mut self, props: Self::Properties) -> bool {
if self.props != props {
self.props = props;
true
} else {
false
}
}
fn view(&self) -> Html {
html! {
<form novalidate=true class="pf-c-form">
{ for self.props.children.iter().map(|child|{
child
}) }
</form>
}
}
}
#[derive(Clone, PartialEq, Properties)]
pub struct FormGroupProps {
pub children: Children,
#[prop_or_default]
pub label: String,
#[prop_or_default]
pub required: bool,
#[prop_or_default]
pub helper_text: String,
}
pub struct FormGroup {
props: FormGroupProps,
}
impl Component for FormGroup {
type Message = ();
type Properties = FormGroupProps;
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self {
Self { props }
}
fn update(&mut self, _msg: Self::Message) -> bool {
true
}
fn change(&mut self, props: Self::Properties) -> bool {
if self.props != props {
self.props = props;
true
} else {
false
}
}
fn view(&self) -> Html {
let classes = Classes::from("pf-c-form__group");
html! {
<div class=classes>
<div class="pf-c-form__group-label">
{if !self.props.label.is_empty() {
html!{
<div class="pf-c-form__label">
<span class="pf-c-form__label-text">{&self.props.label}</span>
{if self.props.required {
html!{
<span class="pf-c-form__label-required" aria-hidden="true">{"*"}</span>
}
} else {
html!{}
}}
</div>
}
} else {
html!{}
}}
</div>
<div class="pf-c-form__group-control">
{ for self.props.children.iter() }
{ if !self.props.helper_text.is_empty() {html!{
<p class="pf-c-form__helper-text" aria-live="polite">{ &self.props.helper_text }</p>
}} else {html!{}}}
</div>
</div>
}
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum InputState {
Default,
Success,
Warning,
Error,
}
impl Default for InputState {
fn default() -> Self {
Self::Default
}
}
impl InputState {
pub fn convert(&self, mut classes: Classes) -> (Classes, bool) {
let mut aria_invalid = false;
match self {
InputState::Default => {}
InputState::Success => classes.push("pf-m-success"),
InputState::Warning => classes.push("pf-m-warning"),
InputState::Error => aria_invalid = true,
};
(classes, aria_invalid)
}
}
#[derive(Copy, Clone, Eq, PartialEq)]
pub enum TextInputIcon {
None,
Calendar,
Clock,
Search,
Custom,
}
impl Default for TextInputIcon {
fn default() -> Self {
Self::None
}
}
#[derive(Clone, PartialEq, Properties)]
pub struct TextInputProps {
#[prop_or_default]
pub name: String,
#[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 icon: TextInputIcon,
#[prop_or("text".into())]
pub r#type: String,
#[prop_or_default]
pub placeholder: String,
#[prop_or_default]
pub onchange: Callback<String>,
#[prop_or_default]
pub oninput: Callback<String>,
#[prop_or_default]
pub validator: Validator,
}
pub struct TextInput {
props: TextInputProps,
link: ComponentLink<Self>,
value: String,
input_ref: NodeRef,
}
pub enum TextInputMsg {
Changed(String),
Input(String),
}
impl Component for TextInput {
type Message = TextInputMsg;
type Properties = TextInputProps;
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
let value = props.value.clone();
Self {
props,
link,
value,
input_ref: Default::default(),
}
}
fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
TextInputMsg::Changed(data) => {
self.value = data.clone();
self.props.onchange.emit(data);
}
TextInputMsg::Input(data) => {
self.props.oninput.emit(data);
if let Some(value) = self.extract_value() {
self.value = value.clone();
self.props.onchange.emit(value);
}
return self.props.validator.is_custom();
}
}
true
}
fn change(&mut self, props: Self::Properties) -> ShouldRender {
if self.props != props {
self.props = props;
if self.props.readonly {
self.value = self.props.value.clone();
}
true
} else {
false
}
}
fn view(&self) -> Html {
let mut classes = Classes::from("pf-c-form-control");
classes = match self.props.icon {
TextInputIcon::None => classes,
TextInputIcon::Search => classes.extend("pf-m-search"),
TextInputIcon::Calendar => classes.extend(vec!["pf-m-icon", "pf-m-calendar"]),
TextInputIcon::Clock => classes.extend(vec!["pf-m-icon", "pf-m-clock"]),
TextInputIcon::Custom => classes.extend(vec!["pf-m-icon"]),
};
let (classes, aria_invalid) = self.input_state().convert(classes);
let onchange = self.link.batch_callback(|data| match data {
ChangeData::Value(data) => vec![TextInputMsg::Changed(data)],
_ => vec![],
});
let oninput = self
.link
.callback(|data: InputData| TextInputMsg::Input(data.value));
html! {
<input
ref=self.input_ref.clone()
class=classes
type=self.props.r#type
name=self.props.name
required=self.props.required
disabled=self.props.disabled
readonly=self.props.readonly
aria-invalid=aria_invalid
value=self.value
placeholder=self.props.placeholder
onchange=onchange
oninput=oninput
/>
}
}
}
impl TextInput {
fn extract_value(&self) -> Option<String> {
self.input_ref
.cast::<HtmlInputElement>()
.map(|input| input.value())
}
fn input_state(&self) -> InputState {
match &self.props.validator {
Validator::Custom(validator) => validator(&self.value),
_ => self.props.state,
}
}
}
#[derive(Clone, PartialEq, Eq)]
pub enum ResizeOrientation {
Horizontal,
Vertical,
Both,
}
impl Default for ResizeOrientation {
fn default() -> Self {
Self::Both
}
}
#[derive(Clone, PartialEq, Properties)]
pub struct TextAreaProps {
#[prop_or_default]
pub name: String,
#[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 resize: ResizeOrientation,
#[prop_or_default]
pub onchange: Callback<String>,
#[prop_or_default]
pub oninput: Callback<String>,
#[prop_or_default]
pub validator: Validator,
}
pub struct TextArea {
props: TextAreaProps,
link: ComponentLink<Self>,
value: String,
input_ref: NodeRef,
}
pub enum TextAreaMsg {
Changed(String),
Input(String),
}
impl Component for TextArea {
type Message = TextAreaMsg;
type Properties = TextAreaProps;
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
let value = props.value.clone();
Self {
props,
link,
value,
input_ref: NodeRef::default(),
}
}
fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
TextAreaMsg::Changed(data) => {
self.value = data.clone();
self.props.onchange.emit(data);
false
}
TextAreaMsg::Input(data) => {
self.props.oninput.emit(data);
if let Some(value) = self.extract_value() {
self.value = value.clone();
self.props.onchange.emit(value);
}
false
}
}
}
fn change(&mut self, props: Self::Properties) -> ShouldRender {
if self.props != props {
self.props = props;
true
} else {
false
}
}
fn view(&self) -> Html {
let classes = Classes::from("pf-c-form-control");
let (mut classes, aria_invalid) = self.input_state().convert(classes);
match self.props.resize {
ResizeOrientation::Horizontal => classes.push("pf-m-resize-horizontal"),
ResizeOrientation::Vertical => classes.push("pf-m-resize-vertical"),
_ => {}
}
let onchange = self.link.batch_callback(|data| match data {
ChangeData::Value(data) => vec![TextAreaMsg::Changed(data)],
_ => vec![],
});
let oninput = self
.link
.callback(|data: InputData| TextAreaMsg::Input(data.value));
html! {
<textarea
ref=self.input_ref.clone()
class=classes
name=self.props.name
required=self.props.required
disabled=self.props.disabled
readonly=self.props.readonly
aria-invalid=aria_invalid
value=self.props.value
onchange=onchange
oninput=oninput
/>
}
}
}
impl TextArea {
fn extract_value(&self) -> Option<String> {
self.input_ref
.cast::<HtmlInputElement>()
.map(|input| input.value())
}
fn input_state(&self) -> InputState {
match &self.props.validator {
Validator::Custom(validator) => validator(&self.value),
_ => self.props.state,
}
}
}
#[derive(Clone, PartialEq, Properties)]
pub struct ActionGroupProps {
children: ChildrenWithProps<Button>,
}
pub struct ActionGroup {
props: ActionGroupProps,
}
impl Component for ActionGroup {
type Message = ();
type Properties = ActionGroupProps;
fn create(props: Self::Properties, _link: ComponentLink<Self>) -> Self {
Self { props }
}
fn update(&mut self, _msg: Self::Message) -> ShouldRender {
true
}
fn change(&mut self, props: Self::Properties) -> ShouldRender {
if self.props != props {
self.props = props;
true
} else {
false
}
}
fn view(&self) -> Html {
html! {
{ for self.props.children.iter() }
}
}
}