mirror of
https://github.com/adoyle0/thaw.git
synced 2025-03-13 05:59:49 -04:00
feat: add auto complete component
This commit is contained in:
parent
c2712cab15
commit
9380cdfad6
8 changed files with 215 additions and 12 deletions
|
@ -26,6 +26,7 @@ pub fn App() -> impl IntoView {
|
||||||
<Route path="/color-picker" view=ColorPickerPage/>
|
<Route path="/color-picker" view=ColorPickerPage/>
|
||||||
<Route path="/alert" view=AlertPage/>
|
<Route path="/alert" view=AlertPage/>
|
||||||
<Route path="/grid" view=GridPage/>
|
<Route path="/grid" view=GridPage/>
|
||||||
|
<Route path="/auto-complete" view=AutoCompletePage/>
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/mobile/tabbar" view=TabbarDemoPage/>
|
<Route path="/mobile/tabbar" view=TabbarDemoPage/>
|
||||||
<Route path="/mobile/nav-bar" view=NavBarDemoPage/>
|
<Route path="/mobile/nav-bar" view=NavBarDemoPage/>
|
||||||
|
|
59
demo/src/pages/auto_complete/mod.rs
Normal file
59
demo/src/pages/auto_complete/mod.rs
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
use crate::components::{Demo, DemoCode};
|
||||||
|
use leptos::*;
|
||||||
|
use melt_ui::*;
|
||||||
|
use prisms::highlight_str;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn AutoCompletePage() -> impl IntoView {
|
||||||
|
let value = create_rw_signal(String::new());
|
||||||
|
let options = create_memo(move |_| {
|
||||||
|
let prefix = value
|
||||||
|
.get()
|
||||||
|
.split_once('@')
|
||||||
|
.map_or(value.get(), |v| v.0.to_string());
|
||||||
|
vec!["@gmail.com", "@163.com"]
|
||||||
|
.into_iter()
|
||||||
|
.map(|suffix| AutoCompleteOption {
|
||||||
|
label: format!("{prefix}{suffix}"),
|
||||||
|
value: format!("{prefix}{suffix}"),
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
});
|
||||||
|
|
||||||
|
view! {
|
||||||
|
<div style="width: 896px; margin: 0 auto;">
|
||||||
|
<h1>"Auto Complete"</h1>
|
||||||
|
<Demo>
|
||||||
|
<AutoComplete value options placeholder="Email"/>
|
||||||
|
<DemoCode
|
||||||
|
slot
|
||||||
|
html=highlight_str!(
|
||||||
|
r#"
|
||||||
|
let value = create_rw_signal(String::new());
|
||||||
|
let options =create_memo(|_| {
|
||||||
|
let prefix = value
|
||||||
|
.get()
|
||||||
|
.split_once('@')
|
||||||
|
.map_or(value.get(), |v| v.0.to_string());
|
||||||
|
vec!["@gmail.com", "@163.com"]
|
||||||
|
.into_iter()
|
||||||
|
.map(|suffix| AutoCompleteOption {
|
||||||
|
label: format!("{prefix}{suffix}"),
|
||||||
|
value: format!("{prefix}{suffix}"),
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
});
|
||||||
|
view! {
|
||||||
|
<AutoComplete value options placeholder="Email"/>
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
"rust"
|
||||||
|
)
|
||||||
|
>
|
||||||
|
|
||||||
|
""
|
||||||
|
</DemoCode>
|
||||||
|
</Demo>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,20 +32,23 @@ pub fn ComponentsPage() -> impl IntoView {
|
||||||
<Menu value=selected>
|
<Menu value=selected>
|
||||||
<MenuGroup label="Common Components">
|
<MenuGroup label="Common Components">
|
||||||
<MenuItem key="menu" label="Menu"/>
|
<MenuItem key="menu" label="Menu"/>
|
||||||
<MenuItem key="slider" label="Slider"/>
|
|
||||||
<MenuItem key="input" label="Input"/>
|
|
||||||
<MenuItem key="image" label="Image"/>
|
<MenuItem key="image" label="Image"/>
|
||||||
<MenuItem key="modal" label="Modal"/>
|
<MenuItem key="modal" label="Modal"/>
|
||||||
<MenuItem key="button" label="Button"/>
|
<MenuItem key="button" label="Button"/>
|
||||||
<MenuItem key="checkbox" label="Checkbox"/>
|
|
||||||
<MenuItem key="tabs" label="Tabs"/>
|
<MenuItem key="tabs" label="Tabs"/>
|
||||||
<MenuItem key="select" label="Select"/>
|
|
||||||
<MenuItem key="space" label="Space"/>
|
<MenuItem key="space" label="Space"/>
|
||||||
<MenuItem key="table" label="Table"/>
|
<MenuItem key="table" label="Table"/>
|
||||||
<MenuItem key="color-picker" label="Color Picker"/>
|
|
||||||
<MenuItem key="alert" label="Alert"/>
|
<MenuItem key="alert" label="Alert"/>
|
||||||
<MenuItem key="grid" label="Grid"/>
|
<MenuItem key="grid" label="Grid"/>
|
||||||
</MenuGroup>
|
</MenuGroup>
|
||||||
|
<MenuGroup label="Data Input Components">
|
||||||
|
<MenuItem key="auto-complete" label="Auto Complete"/>
|
||||||
|
<MenuItem key="color-picker" label="Color Picker"/>
|
||||||
|
<MenuItem key="checkbox" label="Checkbox"/>
|
||||||
|
<MenuItem key="input" label="Input"/>
|
||||||
|
<MenuItem key="select" label="Select"/>
|
||||||
|
<MenuItem key="slider" label="Slider"/>
|
||||||
|
</MenuGroup>
|
||||||
<MenuGroup label="Mobile Components">
|
<MenuGroup label="Mobile Components">
|
||||||
<MenuItem key="tabbar" label="Tabbar"/>
|
<MenuItem key="tabbar" label="Tabbar"/>
|
||||||
<MenuItem key="nav-bar" label="Nav Bar"/>
|
<MenuItem key="nav-bar" label="Nav Bar"/>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
mod alert;
|
mod alert;
|
||||||
|
mod auto_complete;
|
||||||
mod button;
|
mod button;
|
||||||
mod checkbox;
|
mod checkbox;
|
||||||
mod color_picker;
|
mod color_picker;
|
||||||
|
@ -20,6 +21,7 @@ mod tabs;
|
||||||
mod toast;
|
mod toast;
|
||||||
|
|
||||||
pub use alert::*;
|
pub use alert::*;
|
||||||
|
pub use auto_complete::*;
|
||||||
pub use button::*;
|
pub use button::*;
|
||||||
pub use checkbox::*;
|
pub use checkbox::*;
|
||||||
pub use color_picker::*;
|
pub use color_picker::*;
|
||||||
|
|
20
src/auto_complete/auto-complete.css
Normal file
20
src/auto_complete/auto-complete.css
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
.melt-auto-complete__menu {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
max-height: 200px;
|
||||||
|
padding: 5px;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 3px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
box-shadow: 0 3px 6px -4px rgba(0, 0, 0, 0.12),
|
||||||
|
0 6px 16px 0 rgba(0, 0, 0, 0.08), 0 9px 28px 8px rgba(0, 0, 0, 0.05);
|
||||||
|
z-index: 2000;
|
||||||
|
}
|
||||||
|
.melt-auto-complete__menu-item {
|
||||||
|
padding: 6px 5px;
|
||||||
|
border-radius: 2px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.melt-auto-complete__menu-item:hover {
|
||||||
|
background-color: #f6f6f7;
|
||||||
|
}
|
96
src/auto_complete/mod.rs
Normal file
96
src/auto_complete/mod.rs
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
use crate::{mount_style, teleport::Teleport, utils::maybe_rw_signal::MaybeRwSignal, Input};
|
||||||
|
use leptos::*;
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq)]
|
||||||
|
pub struct AutoCompleteOption {
|
||||||
|
pub label: String,
|
||||||
|
pub value: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn AutoComplete(
|
||||||
|
#[prop(optional, into)] value: MaybeRwSignal<String>,
|
||||||
|
#[prop(optional, into)] placeholder: MaybeSignal<String>,
|
||||||
|
#[prop(optional, into)] options: MaybeSignal<Vec<AutoCompleteOption>>,
|
||||||
|
) -> impl IntoView {
|
||||||
|
mount_style("auto-complete", include_str!("./auto-complete.css"));
|
||||||
|
let is_show_menu = create_rw_signal(false);
|
||||||
|
let auto_complete_ref = create_node_ref::<html::Div>();
|
||||||
|
let auto_complete_menu_ref = create_node_ref::<html::Div>();
|
||||||
|
let show_menu = move || {
|
||||||
|
is_show_menu.set(true);
|
||||||
|
let rect = auto_complete_ref
|
||||||
|
.get_untracked()
|
||||||
|
.unwrap()
|
||||||
|
.get_bounding_client_rect();
|
||||||
|
|
||||||
|
let auto_complete_menu = auto_complete_menu_ref.get_untracked().unwrap();
|
||||||
|
auto_complete_menu
|
||||||
|
.style("width", format!("{}px", rect.width()))
|
||||||
|
.style(
|
||||||
|
"transform",
|
||||||
|
format!(
|
||||||
|
"translateX({}px) translateY({}px)",
|
||||||
|
rect.x(),
|
||||||
|
rect.y() + rect.height()
|
||||||
|
),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
let allow_value = move |_| {
|
||||||
|
if !is_show_menu.get_untracked() {
|
||||||
|
show_menu();
|
||||||
|
}
|
||||||
|
true
|
||||||
|
};
|
||||||
|
|
||||||
|
view! {
|
||||||
|
<div class="melt-auto-complete" ref=auto_complete_ref>
|
||||||
|
<Input
|
||||||
|
value
|
||||||
|
placeholder
|
||||||
|
on_focus=move |_| show_menu()
|
||||||
|
on_blur=move |_| is_show_menu.set(false)
|
||||||
|
allow_value
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<Teleport>
|
||||||
|
<div
|
||||||
|
class="melt-auto-complete__menu"
|
||||||
|
style=move || {
|
||||||
|
if is_show_menu.get() { String::new() } else { format!("display: none;") }
|
||||||
|
}
|
||||||
|
|
||||||
|
ref=auto_complete_menu_ref
|
||||||
|
>
|
||||||
|
|
||||||
|
{move || {
|
||||||
|
options
|
||||||
|
.get()
|
||||||
|
.into_iter()
|
||||||
|
.map(|v| {
|
||||||
|
let AutoCompleteOption { value: option_value, label } = v;
|
||||||
|
let on_click = move |_| {
|
||||||
|
value.set(option_value.clone());
|
||||||
|
is_show_menu.set(false);
|
||||||
|
};
|
||||||
|
let on_mousedown = move |ev: ev::MouseEvent| {
|
||||||
|
ev.prevent_default();
|
||||||
|
};
|
||||||
|
view! {
|
||||||
|
<div
|
||||||
|
class="melt-auto-complete__menu-item"
|
||||||
|
on:click=on_click
|
||||||
|
on:mousedown=on_mousedown
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect_view()
|
||||||
|
}}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</Teleport>
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,17 +26,34 @@ impl InputVariant {
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Input(
|
pub fn Input(
|
||||||
#[prop(optional, into)] value: MaybeRwSignal<String>,
|
#[prop(optional, into)] value: MaybeRwSignal<String>,
|
||||||
|
#[prop(optional, into)] allow_value: Option<Callback<String, bool>>,
|
||||||
#[prop(optional, into)] variant: MaybeSignal<InputVariant>,
|
#[prop(optional, into)] variant: MaybeSignal<InputVariant>,
|
||||||
|
#[prop(optional, into)] placeholder: MaybeSignal<String>,
|
||||||
|
#[prop(optional, into)] on_focus: Option<Callback<ev::FocusEvent>>,
|
||||||
|
#[prop(optional, into)] on_blur: Option<Callback<ev::FocusEvent>>,
|
||||||
) -> 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"));
|
||||||
|
|
||||||
let input_ref = create_node_ref::<html::Input>();
|
let on_input = move |ev| {
|
||||||
input_ref.on_load(move |input| {
|
let input_value = event_target_value(&ev);
|
||||||
input.on(ev::input, move |ev| {
|
if let Some(allow_value) = allow_value.as_ref() {
|
||||||
value.set(event_target_value(&ev));
|
if !allow_value.call(input_value.clone()) {
|
||||||
});
|
return;
|
||||||
});
|
}
|
||||||
|
}
|
||||||
|
value.set(input_value);
|
||||||
|
};
|
||||||
|
let on_internal_focus = move |ev| {
|
||||||
|
if let Some(on_focus) = on_focus.as_ref() {
|
||||||
|
on_focus.call(ev);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let on_internal_blur = move |ev| {
|
||||||
|
if let Some(on_blur) = on_blur.as_ref() {
|
||||||
|
on_blur.call(ev);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let css_vars = create_memo(move |_| {
|
let css_vars = create_memo(move |_| {
|
||||||
let mut css_vars = String::new();
|
let mut css_vars = String::new();
|
||||||
|
@ -52,8 +69,11 @@ pub fn Input(
|
||||||
<input
|
<input
|
||||||
type=move || variant.get().as_str()
|
type=move || variant.get().as_str()
|
||||||
prop:value=move || value.get()
|
prop:value=move || value.get()
|
||||||
ref=input_ref
|
on:input=on_input
|
||||||
|
on:focus=on_internal_focus
|
||||||
|
on:blur=on_internal_blur
|
||||||
class="melt-input__input-el"
|
class="melt-input__input-el"
|
||||||
|
placeholder=move || placeholder.get()
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
mod alert;
|
mod alert;
|
||||||
|
mod auto_complete;
|
||||||
mod button;
|
mod button;
|
||||||
mod card;
|
mod card;
|
||||||
mod checkbox;
|
mod checkbox;
|
||||||
|
@ -25,6 +26,7 @@ mod utils;
|
||||||
mod wave;
|
mod wave;
|
||||||
|
|
||||||
pub use alert::*;
|
pub use alert::*;
|
||||||
|
pub use auto_complete::*;
|
||||||
pub use button::*;
|
pub use button::*;
|
||||||
pub use card::*;
|
pub use card::*;
|
||||||
pub use checkbox::*;
|
pub use checkbox::*;
|
||||||
|
|
Loading…
Add table
Reference in a new issue