refactor: Textarea

This commit is contained in:
luoxiao 2024-06-23 23:33:09 +08:00
parent 7e04778a44
commit 0bf842536f
11 changed files with 239 additions and 52 deletions

View file

@ -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>

View file

@ -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(),

View file

@ -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>
} }
``` ```

View 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

View file

@ -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"

View file

@ -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};

View file

@ -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(),
}
}
}

View file

@ -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
View 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();
}
}
}

View 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);
}

View file

@ -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(),