feat: Textarea adds resize prop

This commit is contained in:
luoxiao 2024-06-24 23:26:03 +08:00
parent 0bf842536f
commit 2cbb4daa9a
5 changed files with 77 additions and 234 deletions

View file

@ -22,5 +22,18 @@ view! {
}
```
### Resize
```rust demo
view! {
<Space vertical=true>
<Textarea placeholder=r#"Textarea with resize set to "none""#/>
<Textarea placeholder=r#"Textarea with resize set to "vertical""# resize=TextareaResize::Vertical/>
<Textarea placeholder=r#"Textarea with resize set to "horizontal""# resize=TextareaResize::Horizontal/>
<Textarea placeholder=r#"Textarea with resize set to "both""# resize=TextareaResize::Both/>
</Space>
}
```
### Textarea Props

View file

@ -1,54 +0,0 @@
.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);
}

View file

@ -1,150 +0,0 @@
use crate::theme::{use_theme, Theme};
use leptos::*;
use thaw_utils::{class_list, mount_style, ComponentRef, Model, OptionalProp};
#[component]
pub fn TextArea(
#[prop(optional, into)] value: Model<String>,
#[prop(optional, into)] allow_value: Option<Callback<String, bool>>,
#[prop(optional, into)] placeholder: OptionalProp<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: OptionalProp<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: [&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()), class.map(| c | move || c.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=placeholder.map(|p| move || p.get())
ref=textarea_ref
></textarea>
</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();
}
}
}

View file

@ -1,22 +1,23 @@
use leptos::*;
use thaw_utils::{class_list, mount_style, ComponentRef, Model, OptionalProp};
use thaw_utils::{class_list, mount_style, ComponentRef, Model};
#[component]
pub fn Textarea(
#[prop(optional, into)] value: Model<String>,
#[prop(optional, into)] allow_value: Option<Callback<String, bool>>,
#[prop(optional, into)] placeholder: OptionalProp<MaybeSignal<String>>,
#[prop(optional, into)] placeholder: MaybeProp<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: OptionalProp<MaybeSignal<String>>,
/// Which direction the Textarea is allowed to be resized.
#[prop(optional, into)] resize: MaybeSignal<TextareaResize>,
#[prop(optional)] comp_ref: ComponentRef<TextareaRef>,
#[prop(optional, into)] class: MaybeProp<String>,
#[prop(attrs)] attrs: Vec<(&'static str, Attribute)>,
) -> impl IntoView {
mount_style("textarea", include_str!("./textarea.css"));
let value_trigger = create_trigger();
let value_trigger = Trigger::new();
let on_input = move |ev| {
let input_value = event_target_value(&ev);
if let Some(allow_value) = allow_value.as_ref() {
@ -27,23 +28,20 @@ pub fn Textarea(
}
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 textarea_ref = create_node_ref::<html::Textarea>();
let textarea_ref = NodeRef::<html::Textarea>::new();
textarea_ref.on_load(move |_| {
comp_ref.load(TextAreaRef { textarea_ref });
comp_ref.load(TextareaRef { textarea_ref });
});
#[cfg(debug_assertions)]
@ -62,9 +60,10 @@ pub fn Textarea(
view! {
<span
class=class_list![
"thaw-textarea", ("thaw-textarea--focus", move || is_focus.get()),
("thaw-textarea--disabled", move || disabled.get()), ("thaw-textarea--invalid", move
|| invalid.get()), class.map(| c | move || c.get())
"thaw-textarea",
("thaw-textarea--disabled", move || disabled.get()),
move || format!("thaw-textarea--resize-{}", resize.get().as_str()),
class
]
>
<textarea
@ -79,7 +78,7 @@ pub fn Textarea(
on:blur=on_internal_blur
class="thaw-textarea__textarea"
disabled=move || disabled.get()
placeholder=placeholder.map(|p| move || p.get())
placeholder=move || placeholder.get()
ref=textarea_ref
></textarea>
</span>
@ -87,11 +86,11 @@ pub fn Textarea(
}
#[derive(Clone)]
pub struct TextAreaRef {
pub struct TextareaRef {
textarea_ref: NodeRef<html::Textarea>,
}
impl TextAreaRef {
impl TextareaRef {
pub fn focus(&self) {
if let Some(textarea_el) = self.textarea_ref.get_untracked() {
_ = textarea_el.focus();
@ -104,3 +103,23 @@ impl TextAreaRef {
}
}
}
#[derive(Clone, Default)]
pub enum TextareaResize {
#[default]
None,
Both,
Horizontal,
Vertical,
}
impl TextareaResize {
pub fn as_str(&self) -> &'static str {
match self {
TextareaResize::None => "none",
TextareaResize::Both => "both",
TextareaResize::Horizontal => "horizontal",
TextareaResize::Vertical => "vertical",
}
}
}

View file

@ -5,8 +5,8 @@
padding: 0 0 var(--strokeWidthThick) 0;
background-color: var(--colorNeutralBackground1);
border-radius: var(--borderRadiusMedium);
border-bottom-color: var(--colorNeutralStrokeAccessible);
border: var(--strokeWidthThin) solid var(--colorNeutralStroke1);
border-bottom-color: var(--colorNeutralStrokeAccessible);
box-sizing: border-box;
}
@ -28,18 +28,6 @@
border-bottom-color: var(--colorNeutralStrokeAccessiblePressed);
}
/* .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::after {
content: "";
position: absolute;
@ -93,3 +81,30 @@
.thaw-textarea__textarea-el::placeholder {
color: var(--thaw-placeholder-color);
}
.thaw-textarea.thaw-textarea--disabled {
background-color: var(--colorTransparentBackground);
border: var(--strokeWidthThin) solid var(--colorNeutralStrokeDisabled);
}
.thaw-textarea--disabled > .thaw-textarea__textarea {
background-color: var(--colorTransparentBackground);
color: var(--colorNeutralForegroundDisabled);
cursor: not-allowed;
}
.thaw-textarea--disabled > .thaw-textarea__textarea::placeholder {
color: var(--colorNeutralForegroundDisabled);
}
.thaw-textarea--resize-vertical > .thaw-textarea__textarea {
resize: vertical;
}
.thaw-textarea--resize-horizontal > .thaw-textarea__textarea {
resize: horizontal;
}
.thaw-textarea--resize-both > .thaw-textarea__textarea {
resize: both;
}