use dioxus::prelude::*; use dioxus_free_icons::icons::io_icons::IoEye; use dioxus_free_icons::icons::io_icons::IoEyeOff; use dioxus_free_icons::Icon; use super::icons::Pyramid; turf::style_sheet!("src/ui/components/text_input.scss"); pub trait InputPropsData {} #[derive(Props, Clone, PartialEq)] pub struct InputProps { value: Option, placeholder: Option, oninput: Option>>, state: Option>, } #[derive(Clone, PartialEq)] pub struct TextInputState { pub is_valid: bool, pub helper_text: Option, } impl TextInputState { pub fn new() -> Self { TextInputState { is_valid: true, helper_text: None, } } pub fn reset(&mut self) { self.is_valid = true; self.helper_text = None; } pub fn invalidate(&mut self, helper_text: String) { self.is_valid = false; self.helper_text = Some(helper_text); } } impl Default for TextInputState { fn default() -> Self { Self::new() } } impl InputPropsData for TextInputState {} pub fn TextInput(props: InputProps) -> Element { let mut criticity_class = ""; let mut helper_text = "".to_string(); if let Some(state) = props.state { let state = state.read(); if !state.is_valid { criticity_class = ClassName::INVALID; } if let Some(text) = &state.helper_text { helper_text = text.to_string(); } } let input_classes_str = [ClassName::TEXT_INPUT_INPUT, criticity_class].join(" "); rsx! { style { {STYLE_SHEET} } div { class: ClassName::TEXT_INPUT, input { class: "{input_classes_str}", r#type: "text", placeholder: props.placeholder, value: props.value, oninput: move |evt| { if let Some(cb) = &props.oninput { cb.call(evt); } }, } div { class: ClassName::TEXT_INPUT_HELPER_TEXT, p { class: criticity_class, {helper_text} } } } } } #[derive(Props, Clone, PartialEq)] pub struct PasswordInputState { text_input_state: TextInputState, #[props(default = 0.0)] pub score: f64, } impl PasswordInputState { pub fn new() -> Self { PasswordInputState { text_input_state: TextInputState::new(), score: 0.0, } } pub fn reset(&mut self) { self.text_input_state.reset() } pub fn invalidate(&mut self, helper_text: String) { self.text_input_state.invalidate(helper_text) } } impl Default for PasswordInputState { fn default() -> Self { Self::new() } } impl InputPropsData for PasswordInputState {} pub fn PasswordTextInput(props: InputProps) -> Element { let mut criticity_class = ""; let mut helper_text: String = "".to_string(); let mut score: Option = None; let mut show_password = use_signal(|| false); if let Some(state) = props.state { let state = state.read(); if !state.text_input_state.is_valid { criticity_class = ClassName::INVALID; } if let Some(text) = &state.text_input_state.helper_text { helper_text = text.to_string(); } score = Some(state.score); } let text_input_classes = [ ClassName::PASSWORD_TEXT_INPUT, if score.is_none() { ClassName::NO_STRENGTH } else { "" }, ] .join(" "); let input_classes = [ClassName::PASSWORD_TEXT_INPUT_INPUT, criticity_class].join(" "); rsx! { style { {STYLE_SHEET} } div { class: "{text_input_classes}", input { class: "{input_classes}", r#type: if *show_password.read() { "text" } else { "password" }, placeholder: props.placeholder, value: props.value, oninput: move |evt| { if let Some(cb) = &props.oninput { cb.call(evt); } }, } if let Some(score) = score { div { class: ClassName::PASSWORD_TEXT_INPUT_STRENGTH_LEVEL, Pyramid { ratio: score, } } } div { class: ClassName::PASSWORD_TEXT_INPUT_SHOW_TOGGLE, onclick: move |_| { let current_state = *show_password.read(); show_password.set(!current_state); }, if *show_password.read() { Icon { icon: IoEyeOff, } } else { Icon { icon: IoEye, } } } div { class: ClassName::PASSWORD_TEXT_INPUT_HELPER_TEXT, p { class: criticity_class, {helper_text} } } } } }