mirror of
https://github.com/adoyle0/thaw.git
synced 2025-01-23 06:19:22 -05:00
feat: Combobox adds clearable prop
This commit is contained in:
parent
c7be391441
commit
f6d534220e
3 changed files with 101 additions and 8 deletions
|
@ -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
|
### Multiselect
|
||||||
|
|
||||||
```rust demo
|
```rust demo
|
||||||
|
|
|
@ -78,6 +78,7 @@
|
||||||
outline-style: none;
|
outline-style: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.thaw-combobox__clear-icon,
|
||||||
.thaw-combobox__expand-icon {
|
.thaw-combobox__expand-icon {
|
||||||
display: block;
|
display: block;
|
||||||
margin-left: var(--spacingHorizontalXXS);
|
margin-left: var(--spacingHorizontalXXS);
|
||||||
|
@ -152,7 +153,6 @@
|
||||||
|
|
||||||
.thaw-combobox-option__check-icon {
|
.thaw-combobox-option__check-icon {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
|
|
||||||
margin-left: calc(var(--spacingHorizontalXXS) * -1);
|
margin-left: calc(var(--spacingHorizontalXXS) * -1);
|
||||||
margin-right: var(--spacingHorizontalXXS);
|
margin-right: var(--spacingHorizontalXXS);
|
||||||
font-size: var(--fontSizeBase400);
|
font-size: var(--fontSizeBase400);
|
||||||
|
@ -162,6 +162,8 @@
|
||||||
visibility: visible;
|
visibility: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.thaw-combobox__clear-icon > svg,
|
||||||
|
.thaw-combobox__expand-icon > svg,
|
||||||
.thaw-combobox-option__check-icon--multiselect > svg,
|
.thaw-combobox-option__check-icon--multiselect > svg,
|
||||||
.thaw-combobox-option__check-icon > svg {
|
.thaw-combobox-option__check-icon > svg {
|
||||||
display: block;
|
display: block;
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
use crate::ConfigInjection;
|
use crate::ConfigInjection;
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
use thaw_components::{Binder, CSSTransition, Follower, FollowerPlacement, FollowerWidth};
|
use thaw_components::{Binder, CSSTransition, Follower, FollowerPlacement, FollowerWidth};
|
||||||
use thaw_utils::{mount_style, Model};
|
use thaw_utils::{add_event_listener, mount_style, Model};
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Combobox(
|
pub fn Combobox(
|
||||||
#[prop(optional, into)] value: Model<Vec<String>>,
|
#[prop(optional, into)] value: Model<Vec<String>>,
|
||||||
#[prop(optional)] multiselect: bool,
|
#[prop(optional)] multiselect: bool,
|
||||||
|
#[prop(optional)] clearable: bool,
|
||||||
children: Children,
|
children: Children,
|
||||||
) -> impl IntoView {
|
) -> impl IntoView {
|
||||||
mount_style("combobox", include_str!("./combobox.css"));
|
mount_style("combobox", include_str!("./combobox.css"));
|
||||||
|
@ -15,13 +16,63 @@ pub fn Combobox(
|
||||||
let listbox_ref = NodeRef::<html::Div>::new();
|
let listbox_ref = NodeRef::<html::Div>::new();
|
||||||
let is_show_listbox = RwSignal::new(false);
|
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! {
|
view! {
|
||||||
<Binder target_ref=trigger_ref>
|
<Binder target_ref=trigger_ref>
|
||||||
<div
|
<div
|
||||||
class="thaw-combobox"
|
class="thaw-combobox"
|
||||||
ref=trigger_ref
|
ref=trigger_ref
|
||||||
on:click=move |_| {
|
on:click=move |_| {
|
||||||
is_show_listbox.set(true);
|
is_show_listbox.update(|show| *show = !*show);
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
|
@ -29,21 +80,42 @@ pub fn Combobox(
|
||||||
aria-expanded="true"
|
aria-expanded="true"
|
||||||
role="combobox"
|
role="combobox"
|
||||||
class="thaw-combobox__input"
|
class="thaw-combobox__input"
|
||||||
|
// #TODO
|
||||||
|
readonly=true
|
||||||
prop:value=move || {
|
prop:value=move || {
|
||||||
value.with(|value| {
|
value.with(|value| {
|
||||||
if multiselect {
|
if multiselect {
|
||||||
None
|
String::new()
|
||||||
} else {
|
} 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
|
<span
|
||||||
aria-expanded="true"
|
aria-expanded="true"
|
||||||
role="button"
|
role="button"
|
||||||
aria-label="Open"
|
aria-label="Open"
|
||||||
class="thaw-combobox__expand-icon"
|
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">
|
<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">
|
<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
|
placement=FollowerPlacement::BottomStart
|
||||||
width=FollowerWidth::MinTarget
|
width=FollowerWidth::MinTarget
|
||||||
>
|
>
|
||||||
<Provider value=ComboboxInjection{value, multiselect}>
|
<Provider value=ComboboxInjection{value, multiselect, is_show_listbox}>
|
||||||
<CSSTransition
|
<CSSTransition
|
||||||
node_ref=listbox_ref
|
node_ref=listbox_ref
|
||||||
name="fade-in-scale-up-transition"
|
name="fade-in-scale-up-transition"
|
||||||
|
@ -84,6 +156,7 @@ pub fn Combobox(
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub(crate) struct ComboboxInjection {
|
pub(crate) struct ComboboxInjection {
|
||||||
value: Model<Vec<String>>,
|
value: Model<Vec<String>>,
|
||||||
|
is_show_listbox: RwSignal<bool>,
|
||||||
pub multiselect: bool,
|
pub multiselect: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,10 +176,11 @@ impl ComboboxInjection {
|
||||||
value.remove(index);
|
value.remove(index);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
value.push(key.clone());
|
||||||
} else {
|
} else {
|
||||||
value.clear();
|
*value = vec![key.clone()];
|
||||||
|
self.is_show_listbox.set(false);
|
||||||
}
|
}
|
||||||
value.push(key.clone());
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue