mirror of
https://github.com/adoyle0/thaw.git
synced 2025-02-08 19:03:09 -05:00
Feat/input comp ref (#47)
* feat: input add ComponentRef * feat: AutoComplete add ComponentRef * feat: AutoComplete component add prefix and suffix slot
This commit is contained in:
parent
2ea5087273
commit
08936c92ff
9 changed files with 248 additions and 13 deletions
|
@ -17,6 +17,7 @@ icondata = { version = "0.1.0", features = [
|
||||||
"AiCheckOutlined",
|
"AiCheckOutlined",
|
||||||
"AiGithubOutlined",
|
"AiGithubOutlined",
|
||||||
"AiUserOutlined",
|
"AiUserOutlined",
|
||||||
|
"AiSearchOutlined",
|
||||||
] }
|
] }
|
||||||
prisms = { git = "https://github.com/luoxiaozero/prisms", rev = "16d4d34b93fc20578ebf03137d54ecc7eafa4d4b" }
|
prisms = { git = "https://github.com/luoxiaozero/prisms", rev = "16d4d34b93fc20578ebf03137d54ecc7eafa4d4b" }
|
||||||
|
|
||||||
|
|
|
@ -66,6 +66,17 @@ pub fn SiteHeader() -> impl IntoView {
|
||||||
let navigate = use_navigate();
|
let navigate = use_navigate();
|
||||||
navigate(&path, Default::default());
|
navigate(&path, Default::default());
|
||||||
};
|
};
|
||||||
|
let auto_complete_ref = create_component_ref::<AutoCompleteRef>();
|
||||||
|
let handle = window_event_listener(ev::keydown, move |event| {
|
||||||
|
let key = event.key();
|
||||||
|
if key == *"/" {
|
||||||
|
if let Some(auto_complete_ref) = auto_complete_ref.get_untracked() {
|
||||||
|
event.prevent_default();
|
||||||
|
auto_complete_ref.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
on_cleanup(move || handle.remove());
|
||||||
view! {
|
view! {
|
||||||
<LayoutHeader style>
|
<LayoutHeader style>
|
||||||
<Space>
|
<Space>
|
||||||
|
@ -84,11 +95,16 @@ pub fn SiteHeader() -> impl IntoView {
|
||||||
<Space>
|
<Space>
|
||||||
<AutoComplete
|
<AutoComplete
|
||||||
value=search_value
|
value=search_value
|
||||||
placeholder="Search"
|
placeholder="Type '/' to search"
|
||||||
options=search_options
|
options=search_options
|
||||||
clear_after_select=true
|
clear_after_select=true
|
||||||
on_select=on_search_select
|
on_select=on_search_select
|
||||||
/>
|
comp_ref=auto_complete_ref
|
||||||
|
>
|
||||||
|
<AutoCompletePrefix slot>
|
||||||
|
<Icon icon=icondata::Icon::from(icondata::AiIcon::AiSearchOutlined) style="font-size: 18px; color: var(--thaw-placeholder-color);"/>
|
||||||
|
</AutoCompletePrefix>
|
||||||
|
</AutoComplete>
|
||||||
<Button
|
<Button
|
||||||
variant=ButtonVariant::Text
|
variant=ButtonVariant::Text
|
||||||
on_click=move |_| {
|
on_click=move |_| {
|
||||||
|
|
|
@ -161,6 +161,32 @@ pub fn AutoCompletePage() -> impl IntoView {
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</Table>
|
</Table>
|
||||||
|
<h3>"AutoComplete Ref"</h3>
|
||||||
|
<Table single_column=true>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>"Name"</th>
|
||||||
|
<th>"Type"</th>
|
||||||
|
<th>"Description"</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>"focus"</td>
|
||||||
|
<td>
|
||||||
|
<Text code=true>"Fn(&self)"</Text>
|
||||||
|
</td>
|
||||||
|
<td>"Focus the input element."</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>"blur"</td>
|
||||||
|
<td>
|
||||||
|
<Text code=true>"Fn(&self)"</Text>
|
||||||
|
</td>
|
||||||
|
<td>"Blur the input element."</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ use thaw::*;
|
||||||
#[component]
|
#[component]
|
||||||
pub fn InputPage() -> impl IntoView {
|
pub fn InputPage() -> impl IntoView {
|
||||||
let value = create_rw_signal(String::from("o"));
|
let value = create_rw_signal(String::from("o"));
|
||||||
|
let input_ref = create_component_ref::<InputRef>();
|
||||||
view! {
|
view! {
|
||||||
<div style="width: 896px; margin: 0 auto;">
|
<div style="width: 896px; margin: 0 auto;">
|
||||||
<h1>"Input"</h1>
|
<h1>"Input"</h1>
|
||||||
|
@ -64,7 +65,46 @@ pub fn InputPage() -> impl IntoView {
|
||||||
|
|
||||||
</DemoCode>
|
</DemoCode>
|
||||||
</Demo>
|
</Demo>
|
||||||
<h1>"Prefix & Suffix"</h1>
|
<h3>"Imperative handle"</h3>
|
||||||
|
<Demo>
|
||||||
|
<Space vertical=true>
|
||||||
|
<Space>
|
||||||
|
<Button on_click=move |_| input_ref.get_untracked().unwrap().focus()>
|
||||||
|
"Focus"
|
||||||
|
</Button>
|
||||||
|
<Button on_click=move |_| input_ref.get_untracked().unwrap().blur()>
|
||||||
|
"Blur"
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
<Input value comp_ref=input_ref/>
|
||||||
|
</Space>
|
||||||
|
<DemoCode slot>
|
||||||
|
|
||||||
|
{highlight_str!(
|
||||||
|
r#"
|
||||||
|
let value = create_rw_signal(String::from("o"));
|
||||||
|
let input_ref = create_component_ref::<InputRef>();
|
||||||
|
|
||||||
|
view! {
|
||||||
|
<Space vertical=true>
|
||||||
|
<Space>
|
||||||
|
<Button on_click=move |_| input_ref.get_untracked().unwrap().focus()>
|
||||||
|
"Focus"
|
||||||
|
</Button>
|
||||||
|
<Button on_click=move |_| input_ref.get_untracked().unwrap().blur()>
|
||||||
|
"Blur"
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
<Input value comp_ref=input_ref/>
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
"rust"
|
||||||
|
)}
|
||||||
|
|
||||||
|
</DemoCode>
|
||||||
|
</Demo>
|
||||||
|
<h2>"Prefix & Suffix"</h2>
|
||||||
<Demo>
|
<Demo>
|
||||||
<Space vertical=true>
|
<Space vertical=true>
|
||||||
<Input value>
|
<Input value>
|
||||||
|
@ -197,6 +237,32 @@ pub fn InputPage() -> impl IntoView {
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</Table>
|
</Table>
|
||||||
|
<h3>"Input Ref"</h3>
|
||||||
|
<Table single_column=true>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>"Name"</th>
|
||||||
|
<th>"Type"</th>
|
||||||
|
<th>"Description"</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>"focus"</td>
|
||||||
|
<td>
|
||||||
|
<Text code=true>"Fn(&self)"</Text>
|
||||||
|
</td>
|
||||||
|
<td>"Focus the input element."</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>"blur"</td>
|
||||||
|
<td>
|
||||||
|
<Text code=true>"Fn(&self)"</Text>
|
||||||
|
</td>
|
||||||
|
<td>"Blur the input element."</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ use crate::{
|
||||||
components::{Binder, Follower, FollowerPlacement, FollowerWidth},
|
components::{Binder, Follower, FollowerPlacement, FollowerWidth},
|
||||||
use_theme,
|
use_theme,
|
||||||
utils::{mount_style, StoredMaybeSignal},
|
utils::{mount_style, StoredMaybeSignal},
|
||||||
Input, Theme,
|
ComponentRef, Input, InputPrefix, InputRef, InputSuffix, Theme,
|
||||||
};
|
};
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
pub use theme::AutoCompleteTheme;
|
pub use theme::AutoCompleteTheme;
|
||||||
|
@ -16,6 +16,16 @@ pub struct AutoCompleteOption {
|
||||||
pub value: String,
|
pub value: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[slot]
|
||||||
|
pub struct AutoCompletePrefix {
|
||||||
|
children: Children,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[slot]
|
||||||
|
pub struct AutoCompleteSuffix {
|
||||||
|
children: Children,
|
||||||
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn AutoComplete(
|
pub fn AutoComplete(
|
||||||
#[prop(optional, into)] value: RwSignal<String>,
|
#[prop(optional, into)] value: RwSignal<String>,
|
||||||
|
@ -26,6 +36,9 @@ pub fn AutoComplete(
|
||||||
#[prop(optional, into)] disabled: MaybeSignal<bool>,
|
#[prop(optional, into)] disabled: MaybeSignal<bool>,
|
||||||
#[prop(optional, into)] invalid: MaybeSignal<bool>,
|
#[prop(optional, into)] invalid: MaybeSignal<bool>,
|
||||||
#[prop(optional, into)] class: MaybeSignal<String>,
|
#[prop(optional, into)] class: MaybeSignal<String>,
|
||||||
|
#[prop(optional)] auto_complete_prefix: Option<AutoCompletePrefix>,
|
||||||
|
#[prop(optional)] auto_complete_suffix: Option<AutoCompleteSuffix>,
|
||||||
|
#[prop(optional)] comp_ref: ComponentRef<AutoCompleteRef>,
|
||||||
) -> impl IntoView {
|
) -> impl IntoView {
|
||||||
mount_style("auto-complete", include_str!("./auto-complete.css"));
|
mount_style("auto-complete", include_str!("./auto-complete.css"));
|
||||||
let theme = use_theme(Theme::light);
|
let theme = use_theme(Theme::light);
|
||||||
|
@ -108,6 +121,10 @@ pub fn AutoComplete(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let input_ref = ComponentRef::<InputRef>::new();
|
||||||
|
input_ref.on_load(move |_| {
|
||||||
|
comp_ref.load(AutoCompleteRef { input_ref });
|
||||||
|
});
|
||||||
|
|
||||||
let ssr_class = ssr_class(&class);
|
let ssr_class = ssr_class(&class);
|
||||||
view! {
|
view! {
|
||||||
|
@ -121,7 +138,27 @@ pub fn AutoComplete(
|
||||||
on_focus=move |_| open_menu()
|
on_focus=move |_| open_menu()
|
||||||
on_blur=move |_| is_show_menu.set(false)
|
on_blur=move |_| is_show_menu.set(false)
|
||||||
allow_value
|
allow_value
|
||||||
/>
|
comp_ref=input_ref
|
||||||
|
>
|
||||||
|
<InputPrefix if_=auto_complete_prefix.is_some() slot>
|
||||||
|
{
|
||||||
|
if let Some(auto_complete_prefix) = auto_complete_prefix {
|
||||||
|
Some((auto_complete_prefix.children)())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</InputPrefix>
|
||||||
|
<InputSuffix if_=auto_complete_suffix.is_some() slot>
|
||||||
|
{
|
||||||
|
if let Some(auto_complete_suffix) = auto_complete_suffix {
|
||||||
|
Some((auto_complete_suffix.children)())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</InputSuffix>
|
||||||
|
</Input>
|
||||||
</div>
|
</div>
|
||||||
<Follower
|
<Follower
|
||||||
slot
|
slot
|
||||||
|
@ -196,3 +233,22 @@ pub fn AutoComplete(
|
||||||
</Binder>
|
</Binder>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct AutoCompleteRef {
|
||||||
|
input_ref: ComponentRef<InputRef>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AutoCompleteRef {
|
||||||
|
pub fn focus(&self) {
|
||||||
|
if let Some(input_ref) = self.input_ref.get_untracked() {
|
||||||
|
input_ref.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn blur(&self) {
|
||||||
|
if let Some(input_ref) = self.input_ref.get_untracked() {
|
||||||
|
input_ref.blur();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ mod theme;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
theme::{use_theme, Theme},
|
theme::{use_theme, Theme},
|
||||||
utils::mount_style,
|
utils::{mount_style, ComponentRef},
|
||||||
};
|
};
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
pub use theme::InputTheme;
|
pub use theme::InputTheme;
|
||||||
|
@ -25,11 +25,15 @@ impl InputVariant {
|
||||||
|
|
||||||
#[slot]
|
#[slot]
|
||||||
pub struct InputPrefix {
|
pub struct InputPrefix {
|
||||||
|
#[prop(default = true)]
|
||||||
|
if_: bool,
|
||||||
children: Children,
|
children: Children,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[slot]
|
#[slot]
|
||||||
pub struct InputSuffix {
|
pub struct InputSuffix {
|
||||||
|
#[prop(default = true)]
|
||||||
|
if_: bool,
|
||||||
children: Children,
|
children: Children,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,6 +49,7 @@ pub fn Input(
|
||||||
#[prop(optional, into)] invalid: MaybeSignal<bool>,
|
#[prop(optional, into)] invalid: MaybeSignal<bool>,
|
||||||
#[prop(optional)] input_prefix: Option<InputPrefix>,
|
#[prop(optional)] input_prefix: Option<InputPrefix>,
|
||||||
#[prop(optional)] input_suffix: Option<InputSuffix>,
|
#[prop(optional)] input_suffix: Option<InputSuffix>,
|
||||||
|
#[prop(optional)] comp_ref: ComponentRef<InputRef>,
|
||||||
) -> impl IntoView {
|
) -> impl IntoView {
|
||||||
let theme = use_theme(Theme::light);
|
let theme = use_theme(Theme::light);
|
||||||
mount_style("input", include_str!("./input.css"));
|
mount_style("input", include_str!("./input.css"));
|
||||||
|
@ -114,6 +119,10 @@ pub fn Input(
|
||||||
});
|
});
|
||||||
css_vars
|
css_vars
|
||||||
});
|
});
|
||||||
|
let input_ref = create_node_ref::<html::Input>();
|
||||||
|
input_ref.on_load(move |_| {
|
||||||
|
comp_ref.load(InputRef { input_ref });
|
||||||
|
});
|
||||||
view! {
|
view! {
|
||||||
<div
|
<div
|
||||||
class="thaw-input"
|
class="thaw-input"
|
||||||
|
@ -122,7 +131,7 @@ pub fn Input(
|
||||||
class=("thaw-input--invalid", move || invalid.get())
|
class=("thaw-input--invalid", move || invalid.get())
|
||||||
style=move || css_vars.get()
|
style=move || css_vars.get()
|
||||||
>
|
>
|
||||||
{if let Some(prefix) = input_prefix {
|
{if let Some(prefix) = input_prefix.map(|prefix| prefix.if_.then(|| prefix)).flatten() {
|
||||||
view! { <div class="thaw-input__prefix">{(prefix.children)()}</div> }.into()
|
view! { <div class="thaw-input__prefix">{(prefix.children)()}</div> }.into()
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -141,9 +150,10 @@ pub fn Input(
|
||||||
class="thaw-input__input-el"
|
class="thaw-input__input-el"
|
||||||
disabled=move || disabled.get()
|
disabled=move || disabled.get()
|
||||||
placeholder=move || placeholder.get()
|
placeholder=move || placeholder.get()
|
||||||
|
ref=input_ref
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{if let Some(suffix) = input_suffix {
|
{if let Some(suffix) = input_suffix.map(|suffix| suffix.if_.then(|| suffix)).flatten() {
|
||||||
view! { <div class="thaw-input__suffix">{(suffix.children)()}</div> }.into()
|
view! { <div class="thaw-input__suffix">{(suffix.children)()}</div> }.into()
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -152,3 +162,22 @@ pub fn Input(
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct InputRef {
|
||||||
|
input_ref: NodeRef<html::Input>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InputRef {
|
||||||
|
pub fn focus(&self) {
|
||||||
|
if let Some(input_el) = self.input_ref.get_untracked() {
|
||||||
|
_ = input_el.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn blur(&self) {
|
||||||
|
if let Some(input_el) = self.input_ref.get_untracked() {
|
||||||
|
_ = input_el.blur();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -77,4 +77,4 @@ pub use theme::*;
|
||||||
pub use time_picker::*;
|
pub use time_picker::*;
|
||||||
pub use typography::*;
|
pub use typography::*;
|
||||||
pub use upload::*;
|
pub use upload::*;
|
||||||
pub use utils::SignalWatch;
|
pub use utils::{create_component_ref, ComponentRef, SignalWatch};
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
use leptos::{create_rw_signal, RwSignal, SignalGetUntracked, SignalSet};
|
use leptos::{
|
||||||
|
create_render_effect, create_rw_signal, logging::debug_warn, RwSignal, SignalGet,
|
||||||
|
SignalGetUntracked, SignalUpdate,
|
||||||
|
};
|
||||||
|
use std::cell::Cell;
|
||||||
|
|
||||||
pub struct ComponentRef<T: 'static>(RwSignal<Option<T>>);
|
pub struct ComponentRef<T: 'static>(RwSignal<Option<T>>);
|
||||||
|
|
||||||
|
@ -17,6 +21,17 @@ impl<T> Clone for ComponentRef<T> {
|
||||||
impl<T: 'static> Copy for ComponentRef<T> {}
|
impl<T: 'static> Copy for ComponentRef<T> {}
|
||||||
|
|
||||||
impl<T> ComponentRef<T> {
|
impl<T> ComponentRef<T> {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self) -> Option<T>
|
||||||
|
where
|
||||||
|
T: Clone,
|
||||||
|
{
|
||||||
|
self.0.get()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_untracked(&self) -> Option<T>
|
pub fn get_untracked(&self) -> Option<T>
|
||||||
where
|
where
|
||||||
T: Clone,
|
T: Clone,
|
||||||
|
@ -24,7 +39,33 @@ impl<T> ComponentRef<T> {
|
||||||
self.0.get_untracked()
|
self.0.get_untracked()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load(&self, comp_ref: T) {
|
pub fn load(&self, comp: T) {
|
||||||
self.0.set(Some(comp_ref));
|
self.0.update(|current| {
|
||||||
|
if current.is_some() {
|
||||||
|
debug_warn!(
|
||||||
|
"You are setting a ComponentRef that has already been filled. \
|
||||||
|
It’s possible this is intentional."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
*current = Some(comp);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_load<F>(self, f: F)
|
||||||
|
where
|
||||||
|
T: Clone,
|
||||||
|
F: FnOnce(T) + 'static,
|
||||||
|
{
|
||||||
|
let f = Cell::new(Some(f));
|
||||||
|
|
||||||
|
create_render_effect(move |_| {
|
||||||
|
if let Some(comp) = self.get() {
|
||||||
|
f.take().unwrap()(comp);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create_component_ref<T>() -> ComponentRef<T> {
|
||||||
|
ComponentRef::default()
|
||||||
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ mod signal;
|
||||||
mod stored_maybe_signal;
|
mod stored_maybe_signal;
|
||||||
|
|
||||||
// pub use callback::AsyncCallback;
|
// pub use callback::AsyncCallback;
|
||||||
pub(crate) use component_ref::ComponentRef;
|
pub use component_ref::{create_component_ref, ComponentRef};
|
||||||
pub(crate) use dyn_classes::*;
|
pub(crate) use dyn_classes::*;
|
||||||
pub(crate) use event_listener::*;
|
pub(crate) use event_listener::*;
|
||||||
pub(crate) use mount_style::mount_style;
|
pub(crate) use mount_style::mount_style;
|
||||||
|
|
Loading…
Add table
Reference in a new issue