mirror of
https://github.com/adoyle0/thaw.git
synced 2025-02-02 08:34:15 -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",
|
||||
"AiGithubOutlined",
|
||||
"AiUserOutlined",
|
||||
"AiSearchOutlined",
|
||||
] }
|
||||
prisms = { git = "https://github.com/luoxiaozero/prisms", rev = "16d4d34b93fc20578ebf03137d54ecc7eafa4d4b" }
|
||||
|
||||
|
|
|
@ -66,6 +66,17 @@ pub fn SiteHeader() -> impl IntoView {
|
|||
let navigate = use_navigate();
|
||||
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! {
|
||||
<LayoutHeader style>
|
||||
<Space>
|
||||
|
@ -84,11 +95,16 @@ pub fn SiteHeader() -> impl IntoView {
|
|||
<Space>
|
||||
<AutoComplete
|
||||
value=search_value
|
||||
placeholder="Search"
|
||||
placeholder="Type '/' to search"
|
||||
options=search_options
|
||||
clear_after_select=true
|
||||
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
|
||||
variant=ButtonVariant::Text
|
||||
on_click=move |_| {
|
||||
|
|
|
@ -161,6 +161,32 @@ pub fn AutoCompletePage() -> impl IntoView {
|
|||
</tr>
|
||||
</tbody>
|
||||
</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>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ use thaw::*;
|
|||
#[component]
|
||||
pub fn InputPage() -> impl IntoView {
|
||||
let value = create_rw_signal(String::from("o"));
|
||||
let input_ref = create_component_ref::<InputRef>();
|
||||
view! {
|
||||
<div style="width: 896px; margin: 0 auto;">
|
||||
<h1>"Input"</h1>
|
||||
|
@ -64,7 +65,46 @@ pub fn InputPage() -> impl IntoView {
|
|||
|
||||
</DemoCode>
|
||||
</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>
|
||||
<Space vertical=true>
|
||||
<Input value>
|
||||
|
@ -197,6 +237,32 @@ pub fn InputPage() -> impl IntoView {
|
|||
</tr>
|
||||
</tbody>
|
||||
</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>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ use crate::{
|
|||
components::{Binder, Follower, FollowerPlacement, FollowerWidth},
|
||||
use_theme,
|
||||
utils::{mount_style, StoredMaybeSignal},
|
||||
Input, Theme,
|
||||
ComponentRef, Input, InputPrefix, InputRef, InputSuffix, Theme,
|
||||
};
|
||||
use leptos::*;
|
||||
pub use theme::AutoCompleteTheme;
|
||||
|
@ -16,6 +16,16 @@ pub struct AutoCompleteOption {
|
|||
pub value: String,
|
||||
}
|
||||
|
||||
#[slot]
|
||||
pub struct AutoCompletePrefix {
|
||||
children: Children,
|
||||
}
|
||||
|
||||
#[slot]
|
||||
pub struct AutoCompleteSuffix {
|
||||
children: Children,
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn AutoComplete(
|
||||
#[prop(optional, into)] value: RwSignal<String>,
|
||||
|
@ -26,6 +36,9 @@ pub fn AutoComplete(
|
|||
#[prop(optional, into)] disabled: MaybeSignal<bool>,
|
||||
#[prop(optional, into)] invalid: MaybeSignal<bool>,
|
||||
#[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 {
|
||||
mount_style("auto-complete", include_str!("./auto-complete.css"));
|
||||
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);
|
||||
view! {
|
||||
|
@ -121,7 +138,27 @@ pub fn AutoComplete(
|
|||
on_focus=move |_| open_menu()
|
||||
on_blur=move |_| is_show_menu.set(false)
|
||||
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>
|
||||
<Follower
|
||||
slot
|
||||
|
@ -196,3 +233,22 @@ pub fn AutoComplete(
|
|||
</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::{
|
||||
theme::{use_theme, Theme},
|
||||
utils::mount_style,
|
||||
utils::{mount_style, ComponentRef},
|
||||
};
|
||||
use leptos::*;
|
||||
pub use theme::InputTheme;
|
||||
|
@ -25,11 +25,15 @@ impl InputVariant {
|
|||
|
||||
#[slot]
|
||||
pub struct InputPrefix {
|
||||
#[prop(default = true)]
|
||||
if_: bool,
|
||||
children: Children,
|
||||
}
|
||||
|
||||
#[slot]
|
||||
pub struct InputSuffix {
|
||||
#[prop(default = true)]
|
||||
if_: bool,
|
||||
children: Children,
|
||||
}
|
||||
|
||||
|
@ -45,6 +49,7 @@ pub fn Input(
|
|||
#[prop(optional, into)] invalid: MaybeSignal<bool>,
|
||||
#[prop(optional)] input_prefix: Option<InputPrefix>,
|
||||
#[prop(optional)] input_suffix: Option<InputSuffix>,
|
||||
#[prop(optional)] comp_ref: ComponentRef<InputRef>,
|
||||
) -> impl IntoView {
|
||||
let theme = use_theme(Theme::light);
|
||||
mount_style("input", include_str!("./input.css"));
|
||||
|
@ -114,6 +119,10 @@ pub fn Input(
|
|||
});
|
||||
css_vars
|
||||
});
|
||||
let input_ref = create_node_ref::<html::Input>();
|
||||
input_ref.on_load(move |_| {
|
||||
comp_ref.load(InputRef { input_ref });
|
||||
});
|
||||
view! {
|
||||
<div
|
||||
class="thaw-input"
|
||||
|
@ -122,7 +131,7 @@ pub fn Input(
|
|||
class=("thaw-input--invalid", move || invalid.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()
|
||||
} else {
|
||||
None
|
||||
|
@ -141,9 +150,10 @@ pub fn Input(
|
|||
class="thaw-input__input-el"
|
||||
disabled=move || disabled.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()
|
||||
} else {
|
||||
None
|
||||
|
@ -152,3 +162,22 @@ pub fn Input(
|
|||
</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 typography::*;
|
||||
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>>);
|
||||
|
||||
|
@ -17,6 +21,17 @@ impl<T> Clone for ComponentRef<T> {
|
|||
impl<T: 'static> Copy for 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>
|
||||
where
|
||||
T: Clone,
|
||||
|
@ -24,7 +39,33 @@ impl<T> ComponentRef<T> {
|
|||
self.0.get_untracked()
|
||||
}
|
||||
|
||||
pub fn load(&self, comp_ref: T) {
|
||||
self.0.set(Some(comp_ref));
|
||||
pub fn load(&self, comp: T) {
|
||||
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;
|
||||
|
||||
// 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 event_listener::*;
|
||||
pub(crate) use mount_style::mount_style;
|
||||
|
|
Loading…
Add table
Reference in a new issue