feat: add TextArea component (#77)

This commit is contained in:
luoxiaozero 2024-01-12 11:17:04 +08:00 committed by GitHub
parent 4aeab59e6f
commit 559add3a3c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 217 additions and 1 deletions

View file

@ -7,6 +7,7 @@ view! {
<Space vertical=true>
<Input value/>
<Input value variant=InputVariant::Password placeholder="Password"/>
<TextArea value placeholder="Textarea"/>
</Space>
}
```
@ -19,6 +20,7 @@ let value = create_rw_signal(String::from("o"));
view! {
<Space vertical=true>
<Input value disabled=true/>
<TextArea value disabled=true/>
</Space>
}
```
@ -31,6 +33,7 @@ let value = create_rw_signal(String::from("o"));
view! {
<Space vertical=true>
<Input value invalid=true/>
<TextArea value invalid=true/>
</Space>
}
```
@ -119,3 +122,7 @@ view! {
| ----- | ----------- | ------------------------ |
| focus | `Fn(&self)` | Focus the input element. |
| blur | `Fn(&self)` | Blur the input element. |
### TextArea Props
Removes variant and slot from Input component.

View file

@ -1,11 +1,14 @@
mod text_area;
mod theme;
pub use text_area::{TextArea, TextAreaRef};
pub use theme::InputTheme;
use crate::{
theme::{use_theme, Theme},
utils::{class_list::class_list, mount_style, ComponentRef},
};
use leptos::*;
pub use theme::InputTheme;
#[derive(Default, Clone)]
pub enum InputVariant {

View file

@ -0,0 +1,54 @@
.thaw-textarea {
display: inline-flex;
width: 100%;
box-sizing: border-box;
background-color: var(--thaw-background-color);
font-size: 14px;
color: var(--thaw-font-color);
border: 1px solid var(--thaw-border-color);
border-radius: var(--thaw-border-radius);
cursor: text;
transition: all 0.3s;
}
.thaw-textarea--focus,
.thaw-textarea:hover:not(.thaw-textarea--disabled, .thaw-textarea--invalid) {
border-color: var(--thaw-border-color-hover);
}
.thaw-textarea--disabled,
.thaw-textarea--disabled .thaw-textarea__textarea-el {
cursor: not-allowed;
background-color: var(--thaw-background-color-disabled);
color: var(--thaw-font-color-disabled);
}
.thaw-textarea--invalid {
border-color: var(--thaw-border-color-error);
}
.thaw-textarea--focus:not(.thaw-textarea--invalid) {
box-shadow: 0 0 0 2px var(--thaw-box-shadow-color);
}
.thaw-textarea--focus.thaw-textarea--invalid {
box-shadow: 0 0 0 2px var(--thaw-box-shadow-color-invalid);
}
.thaw-textarea__textarea-el {
width: 100%;
height: 100%;
min-height: 34px;
padding: 5px 10px;
background-color: transparent !important;
color: var(--thaw-font-color);
font-size: inherit;
line-height: 1.2;
border: none;
outline: none;
resize: vertical;
}
.thaw-textarea__textarea-el::placeholder {
color: var(--thaw-placeholder-color);
}

152
thaw/src/input/text_area.rs Normal file
View file

@ -0,0 +1,152 @@
use crate::{
theme::{use_theme, Theme},
utils::{class_list::class_list, mount_style, ComponentRef},
};
use leptos::*;
#[component]
pub fn TextArea(
#[prop(optional, into)] value: RwSignal<String>,
#[prop(optional, into)] allow_value: Option<Callback<String, bool>>,
#[prop(optional, into)] placeholder: MaybeSignal<String>,
#[prop(optional, into)] on_focus: Option<Callback<ev::FocusEvent>>,
#[prop(optional, into)] on_blur: Option<Callback<ev::FocusEvent>>,
#[prop(optional, into)] disabled: MaybeSignal<bool>,
#[prop(optional, into)] invalid: MaybeSignal<bool>,
#[prop(optional)] comp_ref: ComponentRef<TextAreaRef>,
#[prop(optional, into)] class: MaybeSignal<String>,
#[prop(attrs)] attrs: Vec<(&'static str, Attribute)>,
) -> impl IntoView {
let theme = use_theme(Theme::light);
mount_style("text-area", include_str!("./text-area.css"));
let value_trigger = create_trigger();
let on_input = move |ev| {
let input_value = event_target_value(&ev);
if let Some(allow_value) = allow_value.as_ref() {
if !allow_value.call(input_value.clone()) {
value_trigger.notify();
return;
}
}
value.set(input_value);
};
let is_focus = create_rw_signal(false);
let on_internal_focus = move |ev| {
is_focus.set(true);
if let Some(on_focus) = on_focus.as_ref() {
on_focus.call(ev);
}
};
let on_internal_blur = move |ev| {
is_focus.set(false);
if let Some(on_blur) = on_blur.as_ref() {
on_blur.call(ev);
}
};
let css_vars = create_memo(move |_| {
let mut css_vars = String::new();
theme.with(|theme| {
let border_color_hover = theme.common.color_primary.clone();
css_vars.push_str(&format!("--thaw-border-color-hover: {border_color_hover};"));
css_vars.push_str(&format!("--thaw-box-shadow-color: {border_color_hover}33;"));
let border_radius = theme.common.border_radius.clone();
css_vars.push_str(&format!("--thaw-border-radius: {border_radius};"));
css_vars.push_str(&format!(
"--thaw-background-color: {};",
theme.input.background_color
));
css_vars.push_str(&format!("--thaw-font-color: {};", theme.input.font_color));
css_vars.push_str(&format!(
"--thaw-border-color: {};",
theme.input.border_color
));
css_vars.push_str(&format!(
"--thaw-border-color-error: {};",
theme.common.color_error
));
css_vars.push_str(&format!(
"--thaw-placeholder-color: {};",
theme.input.placeholder_color
));
css_vars.push_str(&format!(
"--thaw-background-color-disabled: {};",
theme.input.background_color_disabled
));
css_vars.push_str(&format!(
"--thaw-font-color-disabled: {};",
theme.input.font_color_disabled
));
css_vars.push_str(&format!(
"--thaw-box-shadow-color-invalid: {}33;",
theme.common.color_error
));
});
css_vars
});
let textarea_ref = create_node_ref::<html::Textarea>();
textarea_ref.on_load(move |_| {
comp_ref.load(TextAreaRef { textarea_ref });
});
#[cfg(debug_assertions)]
{
const INNER_ATTRS: [&'static str; 3] = ["class", "disabled", "placeholder"];
attrs.iter().for_each(|attr| {
if INNER_ATTRS.contains(&attr.0) {
logging::warn!(
"Thaw: The '{}' attribute already exists on elements inside the TextArea component, which may cause conflicts.",
attr.0
);
}
});
}
view! {
<div
class=class_list![
"thaw-textarea", ("thaw-textarea--focus", move || is_focus.get()),
("thaw-textarea--disabled", move || disabled.get()), ("thaw-textarea--invalid", move ||
invalid.get()), move || class.get()
]
style=move || css_vars.get()
>
<textarea
{..attrs}
prop:value=move || {
value_trigger.track();
value.get()
}
on:input=on_input
on:focus=on_internal_focus
on:blur=on_internal_blur
class="thaw-textarea__textarea-el"
disabled=move || disabled.get()
placeholder=move || placeholder.get()
ref=textarea_ref
/>
</div>
}
}
#[derive(Clone)]
pub struct TextAreaRef {
textarea_ref: NodeRef<html::Textarea>,
}
impl TextAreaRef {
pub fn focus(&self) {
if let Some(textarea_el) = self.textarea_ref.get_untracked() {
_ = textarea_el.focus();
}
}
pub fn blur(&self) {
if let Some(textarea_el) = self.textarea_ref.get_untracked() {
_ = textarea_el.blur();
}
}
}