mirror of
https://github.com/adoyle0/thaw.git
synced 2025-01-23 06:19: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
|
### 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 leptos::*;
|
||||||
use thaw_utils::{class_list, mount_style, ComponentRef, Model, OptionalProp};
|
use thaw_utils::{class_list, mount_style, ComponentRef, Model};
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Textarea(
|
pub fn Textarea(
|
||||||
#[prop(optional, into)] value: Model<String>,
|
#[prop(optional, into)] value: Model<String>,
|
||||||
#[prop(optional, into)] allow_value: Option<Callback<String, bool>>,
|
#[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_focus: Option<Callback<ev::FocusEvent>>,
|
||||||
#[prop(optional, into)] on_blur: Option<Callback<ev::FocusEvent>>,
|
#[prop(optional, into)] on_blur: Option<Callback<ev::FocusEvent>>,
|
||||||
#[prop(optional, into)] disabled: MaybeSignal<bool>,
|
#[prop(optional, into)] disabled: MaybeSignal<bool>,
|
||||||
#[prop(optional, into)] invalid: MaybeSignal<bool>,
|
/// Which direction the Textarea is allowed to be resized.
|
||||||
#[prop(optional)] comp_ref: ComponentRef<TextAreaRef>,
|
#[prop(optional, into)] resize: MaybeSignal<TextareaResize>,
|
||||||
#[prop(optional, into)] class: OptionalProp<MaybeSignal<String>>,
|
#[prop(optional)] comp_ref: ComponentRef<TextareaRef>,
|
||||||
|
#[prop(optional, into)] class: MaybeProp<String>,
|
||||||
#[prop(attrs)] attrs: Vec<(&'static str, Attribute)>,
|
#[prop(attrs)] attrs: Vec<(&'static str, Attribute)>,
|
||||||
) -> impl IntoView {
|
) -> impl IntoView {
|
||||||
mount_style("textarea", include_str!("./textarea.css"));
|
mount_style("textarea", include_str!("./textarea.css"));
|
||||||
|
|
||||||
let value_trigger = create_trigger();
|
let value_trigger = Trigger::new();
|
||||||
let on_input = move |ev| {
|
let on_input = move |ev| {
|
||||||
let input_value = event_target_value(&ev);
|
let input_value = event_target_value(&ev);
|
||||||
if let Some(allow_value) = allow_value.as_ref() {
|
if let Some(allow_value) = allow_value.as_ref() {
|
||||||
|
@ -27,23 +28,20 @@ pub fn Textarea(
|
||||||
}
|
}
|
||||||
value.set(input_value);
|
value.set(input_value);
|
||||||
};
|
};
|
||||||
let is_focus = create_rw_signal(false);
|
|
||||||
let on_internal_focus = move |ev| {
|
let on_internal_focus = move |ev| {
|
||||||
is_focus.set(true);
|
|
||||||
if let Some(on_focus) = on_focus.as_ref() {
|
if let Some(on_focus) = on_focus.as_ref() {
|
||||||
on_focus.call(ev);
|
on_focus.call(ev);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let on_internal_blur = move |ev| {
|
let on_internal_blur = move |ev| {
|
||||||
is_focus.set(false);
|
|
||||||
if let Some(on_blur) = on_blur.as_ref() {
|
if let Some(on_blur) = on_blur.as_ref() {
|
||||||
on_blur.call(ev);
|
on_blur.call(ev);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let textarea_ref = create_node_ref::<html::Textarea>();
|
let textarea_ref = NodeRef::<html::Textarea>::new();
|
||||||
textarea_ref.on_load(move |_| {
|
textarea_ref.on_load(move |_| {
|
||||||
comp_ref.load(TextAreaRef { textarea_ref });
|
comp_ref.load(TextareaRef { textarea_ref });
|
||||||
});
|
});
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
|
@ -62,9 +60,10 @@ pub fn Textarea(
|
||||||
view! {
|
view! {
|
||||||
<span
|
<span
|
||||||
class=class_list![
|
class=class_list![
|
||||||
"thaw-textarea", ("thaw-textarea--focus", move || is_focus.get()),
|
"thaw-textarea",
|
||||||
("thaw-textarea--disabled", move || disabled.get()), ("thaw-textarea--invalid", move
|
("thaw-textarea--disabled", move || disabled.get()),
|
||||||
|| invalid.get()), class.map(| c | move || c.get())
|
move || format!("thaw-textarea--resize-{}", resize.get().as_str()),
|
||||||
|
class
|
||||||
]
|
]
|
||||||
>
|
>
|
||||||
<textarea
|
<textarea
|
||||||
|
@ -79,7 +78,7 @@ pub fn Textarea(
|
||||||
on:blur=on_internal_blur
|
on:blur=on_internal_blur
|
||||||
class="thaw-textarea__textarea"
|
class="thaw-textarea__textarea"
|
||||||
disabled=move || disabled.get()
|
disabled=move || disabled.get()
|
||||||
placeholder=placeholder.map(|p| move || p.get())
|
placeholder=move || placeholder.get()
|
||||||
ref=textarea_ref
|
ref=textarea_ref
|
||||||
></textarea>
|
></textarea>
|
||||||
</span>
|
</span>
|
||||||
|
@ -87,11 +86,11 @@ pub fn Textarea(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct TextAreaRef {
|
pub struct TextareaRef {
|
||||||
textarea_ref: NodeRef<html::Textarea>,
|
textarea_ref: NodeRef<html::Textarea>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextAreaRef {
|
impl TextareaRef {
|
||||||
pub fn focus(&self) {
|
pub fn focus(&self) {
|
||||||
if let Some(textarea_el) = self.textarea_ref.get_untracked() {
|
if let Some(textarea_el) = self.textarea_ref.get_untracked() {
|
||||||
_ = textarea_el.focus();
|
_ = 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;
|
padding: 0 0 var(--strokeWidthThick) 0;
|
||||||
background-color: var(--colorNeutralBackground1);
|
background-color: var(--colorNeutralBackground1);
|
||||||
border-radius: var(--borderRadiusMedium);
|
border-radius: var(--borderRadiusMedium);
|
||||||
border-bottom-color: var(--colorNeutralStrokeAccessible);
|
|
||||||
border: var(--strokeWidthThin) solid var(--colorNeutralStroke1);
|
border: var(--strokeWidthThin) solid var(--colorNeutralStroke1);
|
||||||
|
border-bottom-color: var(--colorNeutralStrokeAccessible);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,18 +28,6 @@
|
||||||
border-bottom-color: var(--colorNeutralStrokeAccessiblePressed);
|
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 {
|
.thaw-textarea::after {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -93,3 +81,30 @@
|
||||||
.thaw-textarea__textarea-el::placeholder {
|
.thaw-textarea__textarea-el::placeholder {
|
||||||
color: var(--thaw-placeholder-color);
|
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