mirror of
https://github.com/adoyle0/thaw.git
synced 2025-01-22 22:09:22 -05:00
feat: Textarea adds resize prop
This commit is contained in:
parent
0bf842536f
commit
2cbb4daa9a
5 changed files with 77 additions and 234 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue