mirror of
https://github.com/adoyle0/thaw.git
synced 2025-01-22 22:09:22 -05:00
refactor: Textarea
This commit is contained in:
parent
7e04778a44
commit
0bf842536f
11 changed files with 239 additions and 52 deletions
|
@ -86,6 +86,7 @@ fn TheRouter(is_routing: RwSignal<bool>) -> impl IntoView {
|
||||||
<Route path="/table" view=TableMdPage/>
|
<Route path="/table" view=TableMdPage/>
|
||||||
<Route path="/tag" view=TagMdPage/>
|
<Route path="/tag" view=TagMdPage/>
|
||||||
<Route path="/text" view=TextMdPage/>
|
<Route path="/text" view=TextMdPage/>
|
||||||
|
<Route path="/textarea" view=TextareaMdPage/>
|
||||||
<Route path="/time-picker" view=TimePickerMdPage/>
|
<Route path="/time-picker" view=TimePickerMdPage/>
|
||||||
<Route path="/upload" view=UploadMdPage/>
|
<Route path="/upload" view=UploadMdPage/>
|
||||||
</Route>
|
</Route>
|
||||||
|
|
|
@ -199,6 +199,10 @@ pub(crate) fn gen_menu_data() -> Vec<MenuGroupOption> {
|
||||||
value: "/components/text".into(),
|
value: "/components/text".into(),
|
||||||
label: "Text".into(),
|
label: "Text".into(),
|
||||||
},
|
},
|
||||||
|
MenuItemOption {
|
||||||
|
value: "/components/textarea".into(),
|
||||||
|
label: "Textarea".into(),
|
||||||
|
},
|
||||||
MenuItemOption {
|
MenuItemOption {
|
||||||
value: "/components/auto-complete".into(),
|
value: "/components/auto-complete".into(),
|
||||||
label: "Auto Complete".into(),
|
label: "Auto Complete".into(),
|
||||||
|
|
|
@ -7,7 +7,6 @@ view! {
|
||||||
<Space vertical=true>
|
<Space vertical=true>
|
||||||
<Input value/>
|
<Input value/>
|
||||||
<Input value variant=InputVariant::Password placeholder="Password"/>
|
<Input value variant=InputVariant::Password placeholder="Password"/>
|
||||||
<TextArea value placeholder="Textarea"/>
|
|
||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -45,7 +44,6 @@ let value = RwSignal::new(String::from("o"));
|
||||||
view! {
|
view! {
|
||||||
<Space vertical=true>
|
<Space vertical=true>
|
||||||
<Input value disabled=true/>
|
<Input value disabled=true/>
|
||||||
<TextArea value disabled=true/>
|
|
||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -66,7 +64,6 @@ let value = create_rw_signal(String::from("o"));
|
||||||
view! {
|
view! {
|
||||||
<Space vertical=true>
|
<Space vertical=true>
|
||||||
<Input value invalid=true/>
|
<Input value invalid=true/>
|
||||||
<TextArea value invalid=true/>
|
|
||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
26
demo_markdown/docs/textarea/mod.md
Normal file
26
demo_markdown/docs/textarea/mod.md
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
# Input
|
||||||
|
|
||||||
|
```rust demo
|
||||||
|
let value = RwSignal::new(String::from("o"));
|
||||||
|
|
||||||
|
view! {
|
||||||
|
<Space vertical=true>
|
||||||
|
<Textarea value placeholder="Textarea"/>
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Disabled
|
||||||
|
|
||||||
|
```rust demo
|
||||||
|
let value = RwSignal::new(String::from("o"));
|
||||||
|
|
||||||
|
view! {
|
||||||
|
<Space vertical=true>
|
||||||
|
<Textarea value disabled=true/>
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Textarea Props
|
||||||
|
|
|
@ -67,6 +67,7 @@ pub fn include_md(_token_stream: proc_macro::TokenStream) -> proc_macro::TokenSt
|
||||||
"TabListMdPage" => "../docs/tab_list/mod.md",
|
"TabListMdPage" => "../docs/tab_list/mod.md",
|
||||||
"TableMdPage" => "../docs/table/mod.md",
|
"TableMdPage" => "../docs/table/mod.md",
|
||||||
"TagMdPage" => "../docs/tag/mod.md",
|
"TagMdPage" => "../docs/tag/mod.md",
|
||||||
|
"TextareaMdPage" => "../docs/textarea/mod.md",
|
||||||
"TimePickerMdPage" => "../docs/time_picker/mod.md",
|
"TimePickerMdPage" => "../docs/time_picker/mod.md",
|
||||||
"TextMdPage" => "../docs/text/mod.md",
|
"TextMdPage" => "../docs/text/mod.md",
|
||||||
"UploadMdPage" => "../docs/upload/mod.md"
|
"UploadMdPage" => "../docs/upload/mod.md"
|
||||||
|
|
|
@ -1,9 +1,3 @@
|
||||||
mod text_area;
|
|
||||||
mod theme;
|
|
||||||
|
|
||||||
pub use text_area::{TextArea, TextAreaRef};
|
|
||||||
pub use theme::InputTheme;
|
|
||||||
|
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
use thaw_utils::{class_list, mount_style, ComponentRef, Model, OptionalProp};
|
use thaw_utils::{class_list, mount_style, ComponentRef, Model, OptionalProp};
|
||||||
|
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
use crate::theme::ThemeMethod;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct InputTheme {
|
|
||||||
pub font_color: String,
|
|
||||||
pub placeholder_color: String,
|
|
||||||
pub border_color: String,
|
|
||||||
pub background_color: String,
|
|
||||||
pub font_color_disabled: String,
|
|
||||||
pub background_color_disabled: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ThemeMethod for InputTheme {
|
|
||||||
fn light() -> Self {
|
|
||||||
Self {
|
|
||||||
font_color: "#333639".into(),
|
|
||||||
placeholder_color: "#c2c2c2".into(),
|
|
||||||
border_color: "#e0e0e6".into(),
|
|
||||||
background_color: "#fff".into(),
|
|
||||||
font_color_disabled: "#c2c2c2".into(),
|
|
||||||
background_color_disabled: "#fafafc".into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dark() -> Self {
|
|
||||||
Self {
|
|
||||||
font_color: "#ffffffd1".into(),
|
|
||||||
placeholder_color: "#c2c2c2".into(),
|
|
||||||
border_color: "#0000".into(),
|
|
||||||
background_color: "#ffffff1a".into(),
|
|
||||||
font_color_disabled: "#ffffff61".into(),
|
|
||||||
background_color_disabled: "#ffffff0f".into(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -37,10 +37,11 @@ mod space;
|
||||||
mod spin_button;
|
mod spin_button;
|
||||||
mod spinner;
|
mod spinner;
|
||||||
mod switch;
|
mod switch;
|
||||||
mod table;
|
|
||||||
mod tab_list;
|
mod tab_list;
|
||||||
|
mod table;
|
||||||
mod tag;
|
mod tag;
|
||||||
mod text;
|
mod text;
|
||||||
|
mod textarea;
|
||||||
mod theme;
|
mod theme;
|
||||||
mod time_picker;
|
mod time_picker;
|
||||||
mod upload;
|
mod upload;
|
||||||
|
@ -83,10 +84,11 @@ pub use space::*;
|
||||||
pub use spin_button::*;
|
pub use spin_button::*;
|
||||||
pub use spinner::*;
|
pub use spinner::*;
|
||||||
pub use switch::*;
|
pub use switch::*;
|
||||||
pub use table::*;
|
|
||||||
pub use tab_list::*;
|
pub use tab_list::*;
|
||||||
|
pub use table::*;
|
||||||
pub use tag::*;
|
pub use tag::*;
|
||||||
pub use text::*;
|
pub use text::*;
|
||||||
|
pub use textarea::*;
|
||||||
pub use thaw_utils::{create_component_ref, ComponentRef, SignalWatch};
|
pub use thaw_utils::{create_component_ref, ComponentRef, SignalWatch};
|
||||||
pub use theme::*;
|
pub use theme::*;
|
||||||
pub use time_picker::*;
|
pub use time_picker::*;
|
||||||
|
|
106
thaw/src/textarea/mod.rs
Normal file
106
thaw/src/textarea/mod.rs
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
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 {
|
||||||
|
mount_style("textarea", include_str!("./textarea.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 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! {
|
||||||
|
<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())
|
||||||
|
]
|
||||||
|
>
|
||||||
|
<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"
|
||||||
|
disabled=move || disabled.get()
|
||||||
|
placeholder=placeholder.map(|p| move || p.get())
|
||||||
|
ref=textarea_ref
|
||||||
|
></textarea>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
95
thaw/src/textarea/textarea.css
Normal file
95
thaw/src/textarea/textarea.css
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
.thaw-textarea {
|
||||||
|
position: relative;
|
||||||
|
display: inline-flex;
|
||||||
|
margin: 0px;
|
||||||
|
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);
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thaw-textarea:focus-within {
|
||||||
|
border-bottom-color: var(--colorCompoundBrandStroke);
|
||||||
|
outline-width: var(--strokeWidthThick);
|
||||||
|
outline-color: transparent;
|
||||||
|
outline-style: solid;
|
||||||
|
border: var(--strokeWidthThin) solid var(--colorNeutralStroke1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.thaw-textarea:hover {
|
||||||
|
border: var(--strokeWidthThin) solid var(--colorNeutralStroke1Hover);
|
||||||
|
border-bottom-color: var(--colorNeutralStrokeAccessibleHover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.thaw-textarea:active {
|
||||||
|
border: var(--strokeWidthThin) solid var(--colorNeutralStroke1Pressed);
|
||||||
|
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;
|
||||||
|
bottom: -1px;
|
||||||
|
right: -1px;
|
||||||
|
left: -1px;
|
||||||
|
height: max(var(--strokeWidthThick), var(--borderRadiusMedium));
|
||||||
|
border-bottom-right-radius: var(--borderRadiusMedium);
|
||||||
|
border-bottom-left-radius: var(--borderRadiusMedium);
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-bottom: var(--strokeWidthThick) solid var(--colorCompoundBrandStroke);
|
||||||
|
transition-delay: var(--curveAccelerateMid);
|
||||||
|
transition-duration: var(--durationUltraFast);
|
||||||
|
transition-property: transform;
|
||||||
|
transform: scaleX(0);
|
||||||
|
clip-path: inset(calc(100% - var(--strokeWidthThick)) 0 0 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.thaw-textarea:focus-within::after {
|
||||||
|
transition-delay: var(--curveDecelerateMid);
|
||||||
|
transition-duration: var(--durationNormal);
|
||||||
|
transition-property: transform;
|
||||||
|
transform: scaleX(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.thaw-textarea:focus-within:active::after {
|
||||||
|
border-bottom-color: var(--colorCompoundBrandStrokePressed);
|
||||||
|
}
|
||||||
|
|
||||||
|
.thaw-textarea__textarea {
|
||||||
|
flex-grow: 1;
|
||||||
|
height: 100%;
|
||||||
|
max-height: 260px;
|
||||||
|
min-height: 52px;
|
||||||
|
|
||||||
|
margin: 0px;
|
||||||
|
padding: var(--spacingVerticalSNudge)
|
||||||
|
calc(var(--spacingHorizontalMNudge) + var(--spacingHorizontalXXS));
|
||||||
|
outline-style: none;
|
||||||
|
background-color: transparent;
|
||||||
|
color: var(--colorNeutralForeground1);
|
||||||
|
line-height: var(--lineHeightBase300);
|
||||||
|
font-weight: var(--fontWeightRegular);
|
||||||
|
font-size: var(--fontSizeBase300);
|
||||||
|
font-family: var(--fontFamilyBase);
|
||||||
|
border-style: none;
|
||||||
|
box-sizing: border-box;
|
||||||
|
resize: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thaw-textarea__textarea-el::placeholder {
|
||||||
|
color: var(--thaw-placeholder-color);
|
||||||
|
}
|
|
@ -4,9 +4,8 @@ mod common;
|
||||||
use self::common::CommonTheme;
|
use self::common::CommonTheme;
|
||||||
use crate::{
|
use crate::{
|
||||||
mobile::{NavBarTheme, TabbarTheme},
|
mobile::{NavBarTheme, TabbarTheme},
|
||||||
AlertTheme, AnchorTheme, BackTopTheme, CalendarTheme, DatePickerTheme,
|
AlertTheme, AnchorTheme, BackTopTheme, CalendarTheme, DatePickerTheme, MessageTheme,
|
||||||
InputTheme, MessageTheme, ProgressTheme, ScrollbarTheme, SelectTheme, TimePickerTheme,
|
ProgressTheme, ScrollbarTheme, SelectTheme, TimePickerTheme, UploadTheme,
|
||||||
UploadTheme,
|
|
||||||
};
|
};
|
||||||
pub use color::ColorTheme;
|
pub use color::ColorTheme;
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
|
@ -21,7 +20,6 @@ pub struct Theme {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub common: CommonTheme,
|
pub common: CommonTheme,
|
||||||
pub color: ColorTheme,
|
pub color: ColorTheme,
|
||||||
pub input: InputTheme,
|
|
||||||
pub alert: AlertTheme,
|
pub alert: AlertTheme,
|
||||||
pub message: MessageTheme,
|
pub message: MessageTheme,
|
||||||
pub select: SelectTheme,
|
pub select: SelectTheme,
|
||||||
|
@ -43,7 +41,6 @@ impl Theme {
|
||||||
name: "light".into(),
|
name: "light".into(),
|
||||||
common: CommonTheme::light(),
|
common: CommonTheme::light(),
|
||||||
color: ColorTheme::light(),
|
color: ColorTheme::light(),
|
||||||
input: InputTheme::light(),
|
|
||||||
alert: AlertTheme::light(),
|
alert: AlertTheme::light(),
|
||||||
message: MessageTheme::light(),
|
message: MessageTheme::light(),
|
||||||
select: SelectTheme::light(),
|
select: SelectTheme::light(),
|
||||||
|
@ -64,7 +61,6 @@ impl Theme {
|
||||||
name: "dark".into(),
|
name: "dark".into(),
|
||||||
common: CommonTheme::dark(),
|
common: CommonTheme::dark(),
|
||||||
color: ColorTheme::dark(),
|
color: ColorTheme::dark(),
|
||||||
input: InputTheme::dark(),
|
|
||||||
alert: AlertTheme::dark(),
|
alert: AlertTheme::dark(),
|
||||||
message: MessageTheme::dark(),
|
message: MessageTheme::dark(),
|
||||||
select: SelectTheme::dark(),
|
select: SelectTheme::dark(),
|
||||||
|
|
Loading…
Add table
Reference in a new issue