feat: Combobox adds clearable prop

This commit is contained in:
luoxiao 2024-06-27 17:31:52 +08:00
parent c7be391441
commit f6d534220e
3 changed files with 101 additions and 8 deletions

View file

@ -15,6 +15,23 @@ view! {
}
```
### Clearable
```rust demo
let value = RwSignal::new(vec![]);
view! {
<Combobox value multiselect=true clearable=true>
<ComboboxOption key="cat">
"Cat"
</ComboboxOption>
<ComboboxOption key="dog">
"Dog"
</ComboboxOption>
</Combobox>
}
```
### Multiselect
```rust demo

View file

@ -78,6 +78,7 @@
outline-style: none;
}
.thaw-combobox__clear-icon,
.thaw-combobox__expand-icon {
display: block;
margin-left: var(--spacingHorizontalXXS);
@ -152,7 +153,6 @@
.thaw-combobox-option__check-icon {
visibility: hidden;
margin-left: calc(var(--spacingHorizontalXXS) * -1);
margin-right: var(--spacingHorizontalXXS);
font-size: var(--fontSizeBase400);
@ -162,6 +162,8 @@
visibility: visible;
}
.thaw-combobox__clear-icon > svg,
.thaw-combobox__expand-icon > svg,
.thaw-combobox-option__check-icon--multiselect > svg,
.thaw-combobox-option__check-icon > svg {
display: block;

View file

@ -1,12 +1,13 @@
use crate::ConfigInjection;
use leptos::*;
use thaw_components::{Binder, CSSTransition, Follower, FollowerPlacement, FollowerWidth};
use thaw_utils::{mount_style, Model};
use thaw_utils::{add_event_listener, mount_style, Model};
#[component]
pub fn Combobox(
#[prop(optional, into)] value: Model<Vec<String>>,
#[prop(optional)] multiselect: bool,
#[prop(optional)] clearable: bool,
children: Children,
) -> impl IntoView {
mount_style("combobox", include_str!("./combobox.css"));
@ -15,13 +16,63 @@ pub fn Combobox(
let listbox_ref = NodeRef::<html::Div>::new();
let is_show_listbox = RwSignal::new(false);
let clear_icon_ref = NodeRef::<html::Span>::new();
let is_show_clear_icon = Memo::new(move |_| {
if clearable {
value.with(|value| !value.is_empty())
} else {
false
}
});
if clearable {
clear_icon_ref.on_load(move |clear_icon_el| {
let handler = add_event_listener(clear_icon_el.into_any(), ev::click, move |e| {
e.stop_propagation();
value.set(vec![]);
});
on_cleanup(move || handler.remove());
});
}
#[cfg(any(feature = "csr", feature = "hydrate"))]
{
let handle = window_event_listener(ev::click, move |ev| {
use leptos::wasm_bindgen::__rt::IntoJsResult;
if !is_show_listbox.get_untracked() {
return;
}
let el = ev.target();
let mut el: Option<web_sys::Element> =
el.into_js_result().map_or(None, |el| Some(el.into()));
let body = document().body().unwrap();
if let Some(listbox_el) = listbox_ref.get_untracked() {
if let Some(trigger_el) = trigger_ref.get_untracked() {
while let Some(current_el) = el {
if current_el == *body {
break;
};
if current_el == ***listbox_el {
return;
}
if current_el == ***trigger_el {
return;
}
el = current_el.parent_element();
}
}
}
is_show_listbox.set(false);
});
on_cleanup(move || handle.remove());
}
view! {
<Binder target_ref=trigger_ref>
<div
class="thaw-combobox"
ref=trigger_ref
on:click=move |_| {
is_show_listbox.set(true);
is_show_listbox.update(|show| *show = !*show);
}
>
<input
@ -29,21 +80,42 @@ pub fn Combobox(
aria-expanded="true"
role="combobox"
class="thaw-combobox__input"
// #TODO
readonly=true
prop:value=move || {
value.with(|value| {
if multiselect {
None
String::new()
} else {
value.first().cloned()
value.first().cloned().unwrap_or_default()
}
})
}
/>
{
if clearable {
view! {
<span
aria-hidden="true"
class="thaw-combobox__clear-icon"
style=move || (!is_show_clear_icon.get()).then(|| "display: none")
ref=clear_icon_ref
>
<svg fill="currentColor" aria-hidden="true" width="1em" height="1em" viewBox="0 0 20 20">
<path d="m4.09 4.22.06-.07a.5.5 0 0 1 .63-.06l.07.06L10 9.29l5.15-5.14a.5.5 0 0 1 .63-.06l.07.06c.18.17.2.44.06.63l-.06.07L10.71 10l5.14 5.15c.18.17.2.44.06.63l-.06.07a.5.5 0 0 1-.63.06l-.07-.06L10 10.71l-5.15 5.14a.5.5 0 0 1-.63.06l-.07-.06a.5.5 0 0 1-.06-.63l.06-.07L9.29 10 4.15 4.85a.5.5 0 0 1-.06-.63l.06-.07-.06.07Z" fill="currentColor"></path>
</svg>
</span>
}.into()
} else {
None
}
}
<span
aria-expanded="true"
role="button"
aria-label="Open"
class="thaw-combobox__expand-icon"
style=move || is_show_clear_icon.get().then(|| "display: none")
>
<svg fill="currentColor" aria-hidden="true" width="1em" height="1em" viewBox="0 0 20 20">
<path d="M15.85 7.65c.2.2.2.5 0 .7l-5.46 5.49a.55.55 0 0 1-.78 0L4.15 8.35a.5.5 0 1 1 .7-.7L10 12.8l5.15-5.16c.2-.2.5-.2.7 0Z" fill="currentColor">
@ -57,7 +129,7 @@ pub fn Combobox(
placement=FollowerPlacement::BottomStart
width=FollowerWidth::MinTarget
>
<Provider value=ComboboxInjection{value, multiselect}>
<Provider value=ComboboxInjection{value, multiselect, is_show_listbox}>
<CSSTransition
node_ref=listbox_ref
name="fade-in-scale-up-transition"
@ -84,6 +156,7 @@ pub fn Combobox(
#[derive(Clone, Copy)]
pub(crate) struct ComboboxInjection {
value: Model<Vec<String>>,
is_show_listbox: RwSignal<bool>,
pub multiselect: bool,
}
@ -103,10 +176,11 @@ impl ComboboxInjection {
value.remove(index);
return;
}
value.push(key.clone());
} else {
value.clear();
*value = vec![key.clone()];
self.is_show_listbox.set(false);
}
value.push(key.clone());
});
}
}