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="/tag" view=TagMdPage/>
|
||||
<Route path="/text" view=TextMdPage/>
|
||||
<Route path="/textarea" view=TextareaMdPage/>
|
||||
<Route path="/time-picker" view=TimePickerMdPage/>
|
||||
<Route path="/upload" view=UploadMdPage/>
|
||||
</Route>
|
||||
|
|
|
@ -199,6 +199,10 @@ pub(crate) fn gen_menu_data() -> Vec<MenuGroupOption> {
|
|||
value: "/components/text".into(),
|
||||
label: "Text".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "/components/textarea".into(),
|
||||
label: "Textarea".into(),
|
||||
},
|
||||
MenuItemOption {
|
||||
value: "/components/auto-complete".into(),
|
||||
label: "Auto Complete".into(),
|
||||
|
|
|
@ -7,7 +7,6 @@ view! {
|
|||
<Space vertical=true>
|
||||
<Input value/>
|
||||
<Input value variant=InputVariant::Password placeholder="Password"/>
|
||||
<TextArea value placeholder="Textarea"/>
|
||||
</Space>
|
||||
}
|
||||
```
|
||||
|
@ -45,7 +44,6 @@ let value = RwSignal::new(String::from("o"));
|
|||
view! {
|
||||
<Space vertical=true>
|
||||
<Input value disabled=true/>
|
||||
<TextArea value disabled=true/>
|
||||
</Space>
|
||||
}
|
||||
```
|
||||
|
@ -66,7 +64,6 @@ let value = create_rw_signal(String::from("o"));
|
|||
view! {
|
||||
<Space vertical=true>
|
||||
<Input value invalid=true/>
|
||||
<TextArea value invalid=true/>
|
||||
</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",
|
||||
"TableMdPage" => "../docs/table/mod.md",
|
||||
"TagMdPage" => "../docs/tag/mod.md",
|
||||
"TextareaMdPage" => "../docs/textarea/mod.md",
|
||||
"TimePickerMdPage" => "../docs/time_picker/mod.md",
|
||||
"TextMdPage" => "../docs/text/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 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 spinner;
|
||||
mod switch;
|
||||
mod table;
|
||||
mod tab_list;
|
||||
mod table;
|
||||
mod tag;
|
||||
mod text;
|
||||
mod textarea;
|
||||
mod theme;
|
||||
mod time_picker;
|
||||
mod upload;
|
||||
|
@ -83,10 +84,11 @@ pub use space::*;
|
|||
pub use spin_button::*;
|
||||
pub use spinner::*;
|
||||
pub use switch::*;
|
||||
pub use table::*;
|
||||
pub use tab_list::*;
|
||||
pub use table::*;
|
||||
pub use tag::*;
|
||||
pub use text::*;
|
||||
pub use textarea::*;
|
||||
pub use thaw_utils::{create_component_ref, ComponentRef, SignalWatch};
|
||||
pub use theme::*;
|
||||
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 crate::{
|
||||
mobile::{NavBarTheme, TabbarTheme},
|
||||
AlertTheme, AnchorTheme, BackTopTheme, CalendarTheme, DatePickerTheme,
|
||||
InputTheme, MessageTheme, ProgressTheme, ScrollbarTheme, SelectTheme, TimePickerTheme,
|
||||
UploadTheme,
|
||||
AlertTheme, AnchorTheme, BackTopTheme, CalendarTheme, DatePickerTheme, MessageTheme,
|
||||
ProgressTheme, ScrollbarTheme, SelectTheme, TimePickerTheme, UploadTheme,
|
||||
};
|
||||
pub use color::ColorTheme;
|
||||
use leptos::*;
|
||||
|
@ -21,7 +20,6 @@ pub struct Theme {
|
|||
pub name: String,
|
||||
pub common: CommonTheme,
|
||||
pub color: ColorTheme,
|
||||
pub input: InputTheme,
|
||||
pub alert: AlertTheme,
|
||||
pub message: MessageTheme,
|
||||
pub select: SelectTheme,
|
||||
|
@ -43,7 +41,6 @@ impl Theme {
|
|||
name: "light".into(),
|
||||
common: CommonTheme::light(),
|
||||
color: ColorTheme::light(),
|
||||
input: InputTheme::light(),
|
||||
alert: AlertTheme::light(),
|
||||
message: MessageTheme::light(),
|
||||
select: SelectTheme::light(),
|
||||
|
@ -64,7 +61,6 @@ impl Theme {
|
|||
name: "dark".into(),
|
||||
common: CommonTheme::dark(),
|
||||
color: ColorTheme::dark(),
|
||||
input: InputTheme::dark(),
|
||||
alert: AlertTheme::dark(),
|
||||
message: MessageTheme::dark(),
|
||||
select: SelectTheme::dark(),
|
||||
|
|
Loading…
Add table
Reference in a new issue