feat: add auto complete component

This commit is contained in:
luoxiao 2023-10-15 12:23:26 +08:00
parent c2712cab15
commit 9380cdfad6
8 changed files with 215 additions and 12 deletions

View file

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

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

View file

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

View file

@ -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::*;

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

View file

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

View file

@ -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::*;