✨ 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::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");
|
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 struct TextInputState {
|
||||||
pub is_valid: bool,
|
pub is_valid: bool,
|
||||||
pub helper_text: Option<String>,
|
pub helper_text: Option<String>,
|
||||||
@@ -27,32 +42,27 @@ impl TextInputState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Props)]
|
impl InputPropsData for TextInputState {}
|
||||||
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>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn TextInput<'a>(cx: Scope<'a, TextInputProps<'a>>) -> Element<'a> {
|
pub fn TextInput<'a>(cx: Scope<'a, InputProps<'a, TextInputState>>) -> Element<'a> {
|
||||||
let mut level_class = "";
|
let mut criticity_class = "";
|
||||||
let mut helper_text: String = "".to_string();
|
let mut helper_text = "".to_string();
|
||||||
|
|
||||||
match cx.props.state {
|
match cx.props.state {
|
||||||
Some(state) => {
|
Some(state) => {
|
||||||
if !state.read().is_valid {
|
let state = state.read();
|
||||||
level_class = ClassName::INVALID;
|
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();
|
helper_text = text.to_string();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {}
|
None => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let input_classes_str = [ClassName::TEXT_INPUT_INPUT, criticity_class].join(" ");
|
||||||
|
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
style { STYLE_SHEET },
|
style { STYLE_SHEET },
|
||||||
|
|
||||||
@@ -60,29 +70,148 @@ pub fn TextInput<'a>(cx: Scope<'a, TextInputProps<'a>>) -> Element<'a> {
|
|||||||
class: ClassName::TEXT_INPUT,
|
class: ClassName::TEXT_INPUT,
|
||||||
|
|
||||||
input {
|
input {
|
||||||
class: level_class,
|
class: "{input_classes_str}",
|
||||||
|
r#type: "text",
|
||||||
id: cx.props.id.unwrap_or(""),
|
placeholder: cx.props.placeholder,
|
||||||
|
value: cx.props.value,
|
||||||
|
|
||||||
oninput: move |evt| {
|
oninput: move |evt| {
|
||||||
if let Some(cb) = &cx.props.oninput {
|
if let Some(cb) = &cx.props.oninput {
|
||||||
cb.call(evt);
|
cb.call(evt);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
r#type: cx.props.r#type,
|
|
||||||
placeholder: cx.props.placeholder,
|
|
||||||
value: cx.props.value,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
div {
|
div {
|
||||||
class: ClassName::HELPER_TEXT,
|
class: ClassName::TEXT_INPUT_HELPER_TEXT,
|
||||||
|
|
||||||
p {
|
p {
|
||||||
class: level_class,
|
class: criticity_class,
|
||||||
|
|
||||||
helper_text
|
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,33 +1,43 @@
|
|||||||
@import "../_base.scss"
|
@import "../_base.scss"
|
||||||
|
|
||||||
.root {
|
%base-text-input {
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
input {
|
|
||||||
$horizontal-padding: 1vw;
|
$horizontal-padding: 1vw;
|
||||||
|
|
||||||
padding-left: $horizontal-padding;
|
height: 100%;
|
||||||
padding-right: $horizontal-padding;
|
width: calc(100% - (2 * $horizontal-padding));
|
||||||
padding-top: 0px;
|
|
||||||
padding-bottom: 0px;
|
|
||||||
|
|
||||||
height: calc(100% - (2 * ($border-normal-width)));
|
|
||||||
width: calc(100% - (2 * ($border-normal-width + $horizontal-padding)));
|
|
||||||
|
|
||||||
margin: 0;
|
|
||||||
|
|
||||||
border: $border-normal;
|
border: $border-normal;
|
||||||
border-color: $color-primary-90;
|
border-color: $color-primary-90;
|
||||||
border-radius: $border-radius;
|
border-radius: $border-radius;
|
||||||
|
|
||||||
|
padding-left: $horizontal-padding;
|
||||||
|
padding-right: $horizontal-padding;
|
||||||
|
padding-top: 0px;
|
||||||
|
padding-bottom: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
%base-input {
|
||||||
|
$horizontal-padding: 1vw;
|
||||||
|
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
|
||||||
font-size: 2vh;
|
font-size: 2vh;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
&.invalid {
|
&.invalid {
|
||||||
border-color: $color-critical;
|
border-color: $color-critical;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.helper-text {
|
%base-helper-text {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
margin-top: 0.3vh;
|
margin-top: 0.3vh;
|
||||||
|
|
||||||
@@ -37,11 +47,84 @@
|
|||||||
|
|
||||||
p {
|
p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding-left: 1vw;
|
|
||||||
|
|
||||||
&.invalid {
|
&.invalid {
|
||||||
color: $color-critical;
|
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