✨ Add PasswordTextInput component
The TextInput component has been reworked to factorize some pieces of code with PasswordTextInput.
This commit is contained in:
@@ -1,8 +1,23 @@
|
||||
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/components/text_input.scss");
|
||||
|
||||
#[derive(Debug)]
|
||||
pub trait InputPropsData {}
|
||||
|
||||
#[derive(Props)]
|
||||
pub struct InputProps<'a, D: InputPropsData + 'a> {
|
||||
value: Option<&'a str>,
|
||||
placeholder: Option<&'a str>,
|
||||
oninput: Option<EventHandler<'a, Event<FormData>>>,
|
||||
state: Option<&'a UseRef<D>>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub struct TextInputState {
|
||||
pub is_valid: bool,
|
||||
pub helper_text: Option<String>,
|
||||
@@ -27,32 +42,27 @@ impl TextInputState {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Props)]
|
||||
pub struct TextInputProps<'a> {
|
||||
id: Option<&'a str>,
|
||||
r#type: Option<&'a str>,
|
||||
value: Option<&'a str>,
|
||||
placeholder: Option<&'a str>,
|
||||
oninput: Option<EventHandler<'a, Event<FormData>>>,
|
||||
state: Option<&'a UseRef<TextInputState>>,
|
||||
}
|
||||
impl InputPropsData for TextInputState {}
|
||||
|
||||
pub fn TextInput<'a>(cx: Scope<'a, TextInputProps<'a>>) -> Element<'a> {
|
||||
let mut level_class = "";
|
||||
let mut helper_text: String = "".to_string();
|
||||
pub fn TextInput<'a>(cx: Scope<'a, InputProps<'a, TextInputState>>) -> Element<'a> {
|
||||
let mut criticity_class = "";
|
||||
let mut helper_text = "".to_string();
|
||||
|
||||
match cx.props.state {
|
||||
Some(state) => {
|
||||
if !state.read().is_valid {
|
||||
level_class = ClassName::INVALID;
|
||||
let state = state.read();
|
||||
if !state.is_valid {
|
||||
criticity_class = ClassName::INVALID;
|
||||
}
|
||||
if let Some(text) = &state.read().helper_text {
|
||||
if let Some(text) = &state.helper_text {
|
||||
helper_text = text.to_string();
|
||||
}
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
|
||||
let input_classes_str = [ClassName::TEXT_INPUT_INPUT, criticity_class].join(" ");
|
||||
|
||||
cx.render(rsx! {
|
||||
style { STYLE_SHEET },
|
||||
|
||||
@@ -60,29 +70,148 @@ pub fn TextInput<'a>(cx: Scope<'a, TextInputProps<'a>>) -> Element<'a> {
|
||||
class: ClassName::TEXT_INPUT,
|
||||
|
||||
input {
|
||||
class: level_class,
|
||||
|
||||
id: cx.props.id.unwrap_or(""),
|
||||
class: "{input_classes_str}",
|
||||
r#type: "text",
|
||||
placeholder: cx.props.placeholder,
|
||||
value: cx.props.value,
|
||||
|
||||
oninput: move |evt| {
|
||||
if let Some(cb) = &cx.props.oninput {
|
||||
cb.call(evt);
|
||||
}
|
||||
},
|
||||
r#type: cx.props.r#type,
|
||||
placeholder: cx.props.placeholder,
|
||||
value: cx.props.value,
|
||||
},
|
||||
|
||||
div {
|
||||
class: ClassName::HELPER_TEXT,
|
||||
class: ClassName::TEXT_INPUT_HELPER_TEXT,
|
||||
|
||||
p {
|
||||
class: level_class,
|
||||
|
||||
class: criticity_class,
|
||||
helper_text
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Props)]
|
||||
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 InputPropsData for PasswordInputState {}
|
||||
|
||||
pub fn PasswordTextInput<'a>(cx: Scope<'a, InputProps<'a, PasswordInputState>>) -> Element<'a> {
|
||||
let mut criticity_class = "";
|
||||
let mut helper_text: String = "".to_string();
|
||||
let mut score: Option<f64> = None;
|
||||
|
||||
let show_password = use_state(cx, || false);
|
||||
|
||||
match cx.props.state {
|
||||
Some(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);
|
||||
}
|
||||
None => {}
|
||||
}
|
||||
|
||||
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(" ");
|
||||
|
||||
cx.render(rsx! {
|
||||
style { STYLE_SHEET },
|
||||
|
||||
div {
|
||||
class: "{text_input_classes}",
|
||||
|
||||
input {
|
||||
class: "{input_classes}",
|
||||
r#type: if **show_password { "text" } else { "password" },
|
||||
placeholder: cx.props.placeholder,
|
||||
value: cx.props.value,
|
||||
|
||||
oninput: move |evt| {
|
||||
if let Some(cb) = &cx.props.oninput {
|
||||
cb.call(evt);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
if let Some(score) = score {
|
||||
rsx!(
|
||||
div {
|
||||
class: ClassName::PASSWORD_TEXT_INPUT_STRENGTH_LEVEL,
|
||||
Pyramid {
|
||||
ratio: score,
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
div {
|
||||
class: ClassName::PASSWORD_TEXT_INPUT_SHOW_TOGGLE,
|
||||
onclick: move |_| {
|
||||
show_password.set(!**show_password);
|
||||
},
|
||||
|
||||
if **show_password {
|
||||
rsx!(
|
||||
Icon {
|
||||
icon: IoEyeOff,
|
||||
}
|
||||
)
|
||||
}
|
||||
else {
|
||||
rsx!(
|
||||
Icon {
|
||||
icon: IoEye,
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
|
||||
div {
|
||||
class: ClassName::PASSWORD_TEXT_INPUT_HELPER_TEXT,
|
||||
|
||||
p {
|
||||
class: criticity_class,
|
||||
helper_text
|
||||
}
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@@ -1,47 +1,130 @@
|
||||
@import "../_base.scss"
|
||||
|
||||
.root {
|
||||
%base-text-input {
|
||||
$horizontal-padding: 1vw;
|
||||
|
||||
height: 100%;
|
||||
width: calc(100% - (2 * $horizontal-padding));
|
||||
|
||||
input {
|
||||
$horizontal-padding: 1vw;
|
||||
border: $border-normal;
|
||||
border-color: $color-primary-90;
|
||||
border-radius: $border-radius;
|
||||
|
||||
padding-left: $horizontal-padding;
|
||||
padding-right: $horizontal-padding;
|
||||
padding-top: 0px;
|
||||
padding-bottom: 0px;
|
||||
padding-left: $horizontal-padding;
|
||||
padding-right: $horizontal-padding;
|
||||
padding-top: 0px;
|
||||
padding-bottom: 0px;
|
||||
}
|
||||
|
||||
height: calc(100% - (2 * ($border-normal-width)));
|
||||
width: calc(100% - (2 * ($border-normal-width + $horizontal-padding)));
|
||||
%base-input {
|
||||
$horizontal-padding: 1vw;
|
||||
|
||||
margin: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
border: $border-normal;
|
||||
border-color: $color-primary-90;
|
||||
border-radius: $border-radius;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
|
||||
font-size: 2vh;
|
||||
font-size: 2vh;
|
||||
|
||||
&.invalid {
|
||||
border-color: $color-critical;
|
||||
}
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.helper-text {
|
||||
&.invalid {
|
||||
border-color: $color-critical;
|
||||
}
|
||||
}
|
||||
|
||||
%base-helper-text {
|
||||
margin: 0;
|
||||
margin-top: 0.3vh;
|
||||
|
||||
font-size: 1.2vh;
|
||||
|
||||
color: $color-primary-90;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
margin-top: 0.3vh;
|
||||
|
||||
font-size: 1.2vh;
|
||||
|
||||
color: $color-primary-90;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
padding-left: 1vw;
|
||||
|
||||
&.invalid {
|
||||
color: $color-critical;
|
||||
}
|
||||
&.invalid {
|
||||
color: $color-critical;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.text-input {
|
||||
@extend %base-text-input;
|
||||
|
||||
&__input {
|
||||
@extend %base-input;
|
||||
}
|
||||
|
||||
&__helper-text {
|
||||
@extend %base-helper-text;
|
||||
}
|
||||
}
|
||||
|
||||
.password-text-input {
|
||||
@extend %base-text-input;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: auto 7.5% 1% 7.5%;
|
||||
grid-template-rows: 100%;
|
||||
grid-template-areas:
|
||||
"input strength . toggle"
|
||||
"helper helper helper helper"
|
||||
;
|
||||
|
||||
transition: $transition-duration;
|
||||
|
||||
&.no-strength {
|
||||
grid-template-columns: auto 0% 0% 7.5%;
|
||||
}
|
||||
|
||||
&__input {
|
||||
@extend %base-input;
|
||||
|
||||
grid-area: input;
|
||||
}
|
||||
|
||||
%inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&__strength-level {
|
||||
@extend %inner;
|
||||
|
||||
grid-area: strength;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
|
||||
font-size: 2vh;
|
||||
}
|
||||
}
|
||||
|
||||
&__show-toggle {
|
||||
@extend %inner;
|
||||
|
||||
grid-area: toggle;
|
||||
|
||||
svg {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
color: $color-secondary-100;
|
||||
}
|
||||
}
|
||||
|
||||
&__helper-text {
|
||||
@extend %base-helper-text;
|
||||
|
||||
grid-area: helper;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user