mirror of
https://github.com/adoyle0/thaw.git
synced 2025-02-02 08:34:15 -05:00
feat: Combobox adds selected_options prop
This commit is contained in:
parent
f08956586e
commit
58c3905ed3
4 changed files with 83 additions and 58 deletions
|
@ -31,9 +31,7 @@ pub fn SwitchVersion() -> impl IntoView {
|
||||||
<Combobox>
|
<Combobox>
|
||||||
{
|
{
|
||||||
options.into_iter().map(|option| view! {
|
options.into_iter().map(|option| view! {
|
||||||
<ComboboxOption key=option.1>
|
<ComboboxOption value=option.1 text=option.0 />
|
||||||
{option.0}
|
|
||||||
</ComboboxOption>
|
|
||||||
}).collect_view()
|
}).collect_view()
|
||||||
}
|
}
|
||||||
</Combobox>
|
</Combobox>
|
||||||
|
|
|
@ -1,16 +1,12 @@
|
||||||
# Combobox
|
# Combobox
|
||||||
|
|
||||||
```rust demo
|
```rust demo
|
||||||
let value = RwSignal::new(vec![]);
|
let selected_options = RwSignal::new(vec![]);
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<Combobox value>
|
<Combobox selected_options>
|
||||||
<ComboboxOption key="cat">
|
<ComboboxOption value="cat" text="Car" />
|
||||||
"Cat"
|
<ComboboxOption value="dog" text="Dog" />
|
||||||
</ComboboxOption>
|
|
||||||
<ComboboxOption key="dog">
|
|
||||||
"Dog"
|
|
||||||
</ComboboxOption>
|
|
||||||
</Combobox>
|
</Combobox>
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -18,16 +14,12 @@ view! {
|
||||||
### Clearable
|
### Clearable
|
||||||
|
|
||||||
```rust demo
|
```rust demo
|
||||||
let value = RwSignal::new(vec![]);
|
let selected_options = RwSignal::new(vec![]);
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<Combobox value multiselect=true clearable=true>
|
<Combobox selected_options multiselect=true clearable=true>
|
||||||
<ComboboxOption key="cat">
|
<ComboboxOption value="cat" text="Car" />
|
||||||
"Cat"
|
<ComboboxOption value="dog" text="Dog" />
|
||||||
</ComboboxOption>
|
|
||||||
<ComboboxOption key="dog">
|
|
||||||
"Dog"
|
|
||||||
</ComboboxOption>
|
|
||||||
</Combobox>
|
</Combobox>
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
@ -35,16 +27,12 @@ view! {
|
||||||
### Multiselect
|
### Multiselect
|
||||||
|
|
||||||
```rust demo
|
```rust demo
|
||||||
let value = RwSignal::new(vec![]);
|
let selected_options = RwSignal::new(vec![]);
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<Combobox value multiselect=true>
|
<Combobox selected_options multiselect=true>
|
||||||
<ComboboxOption key="cat">
|
<ComboboxOption value="cat" text="Car" />
|
||||||
"Cat"
|
<ComboboxOption value="dog" text="Dog" />
|
||||||
</ComboboxOption>
|
|
||||||
<ComboboxOption key="dog">
|
|
||||||
"Dog"
|
|
||||||
</ComboboxOption>
|
|
||||||
</Combobox>
|
</Combobox>
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
|
@ -5,7 +5,8 @@ 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<String>,
|
||||||
|
#[prop(optional, into)] selected_options: Model<Vec<String>>,
|
||||||
#[prop(optional)] multiselect: bool,
|
#[prop(optional)] multiselect: bool,
|
||||||
#[prop(optional)] clearable: bool,
|
#[prop(optional)] clearable: bool,
|
||||||
children: Children,
|
children: Children,
|
||||||
|
@ -19,7 +20,7 @@ pub fn Combobox(
|
||||||
let clear_icon_ref = NodeRef::<html::Span>::new();
|
let clear_icon_ref = NodeRef::<html::Span>::new();
|
||||||
let is_show_clear_icon = Memo::new(move |_| {
|
let is_show_clear_icon = Memo::new(move |_| {
|
||||||
if clearable {
|
if clearable {
|
||||||
value.with(|value| !value.is_empty())
|
selected_options.with(|options| !options.is_empty())
|
||||||
} else {
|
} else {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
@ -28,7 +29,7 @@ pub fn Combobox(
|
||||||
clear_icon_ref.on_load(move |clear_icon_el| {
|
clear_icon_ref.on_load(move |clear_icon_el| {
|
||||||
let handler = add_event_listener(clear_icon_el.into_any(), ev::click, move |e| {
|
let handler = add_event_listener(clear_icon_el.into_any(), ev::click, move |e| {
|
||||||
e.stop_propagation();
|
e.stop_propagation();
|
||||||
value.set(vec![]);
|
selected_options.set(vec![]);
|
||||||
});
|
});
|
||||||
on_cleanup(move || handler.remove());
|
on_cleanup(move || handler.remove());
|
||||||
});
|
});
|
||||||
|
@ -66,6 +67,32 @@ pub fn Combobox(
|
||||||
on_cleanup(move || handle.remove());
|
on_cleanup(move || handle.remove());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let on_input = move |ev| {
|
||||||
|
let input_value = event_target_value(&ev);
|
||||||
|
if !multiselect {
|
||||||
|
if selected_options.with_untracked(|options| {
|
||||||
|
if let Some(option) = options.first() {
|
||||||
|
if option != &input_value {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}) {
|
||||||
|
selected_options.set(vec![]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
value.set(input_value);
|
||||||
|
};
|
||||||
|
let on_blur = move |_| {
|
||||||
|
if multiselect {
|
||||||
|
value.set(String::new());
|
||||||
|
} else {
|
||||||
|
if selected_options.with_untracked(|options| options.is_empty()) {
|
||||||
|
value.set(String::new());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<Binder target_ref=trigger_ref>
|
<Binder target_ref=trigger_ref>
|
||||||
<div
|
<div
|
||||||
|
@ -80,17 +107,11 @@ 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.get()
|
||||||
if multiselect {
|
|
||||||
String::new()
|
|
||||||
} else {
|
|
||||||
value.first().cloned().unwrap_or_default()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
on:input=on_input
|
||||||
|
on:blur=on_blur
|
||||||
/>
|
/>
|
||||||
{
|
{
|
||||||
if clearable {
|
if clearable {
|
||||||
|
@ -129,7 +150,7 @@ pub fn Combobox(
|
||||||
placement=FollowerPlacement::BottomStart
|
placement=FollowerPlacement::BottomStart
|
||||||
width=FollowerWidth::MinTarget
|
width=FollowerWidth::MinTarget
|
||||||
>
|
>
|
||||||
<Provider value=ComboboxInjection{value, multiselect, is_show_listbox}>
|
<Provider value=ComboboxInjection{value, multiselect, selected_options, 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"
|
||||||
|
@ -155,7 +176,8 @@ pub fn Combobox(
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub(crate) struct ComboboxInjection {
|
pub(crate) struct ComboboxInjection {
|
||||||
value: Model<Vec<String>>,
|
value: Model<String>,
|
||||||
|
selected_options: Model<Vec<String>>,
|
||||||
is_show_listbox: RwSignal<bool>,
|
is_show_listbox: RwSignal<bool>,
|
||||||
pub multiselect: bool,
|
pub multiselect: bool,
|
||||||
}
|
}
|
||||||
|
@ -165,20 +187,22 @@ impl ComboboxInjection {
|
||||||
expect_context()
|
expect_context()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_selected(&self, key: &String) -> bool {
|
pub fn is_selected(&self, value: &String) -> bool {
|
||||||
self.value.with(|value| value.contains(key))
|
self.selected_options
|
||||||
|
.with(|options| options.contains(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn on_option_select(&self, key: &String) {
|
pub fn on_option_select(&self, value: &String, text: &String) {
|
||||||
self.value.update(|value| {
|
self.selected_options.update(|options| {
|
||||||
if self.multiselect {
|
if self.multiselect {
|
||||||
if let Some(index) = value.iter().position(|k| k == key) {
|
if let Some(index) = options.iter().position(|v| v == value) {
|
||||||
value.remove(index);
|
options.remove(index);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
value.push(key.clone());
|
options.push(value.clone());
|
||||||
} else {
|
} else {
|
||||||
*value = vec![key.clone()];
|
*options = vec![value.clone()];
|
||||||
|
self.value.set(text.clone());
|
||||||
self.is_show_listbox.set(false);
|
self.is_show_listbox.set(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,12 +1,24 @@
|
||||||
use crate::ComboboxInjection;
|
use crate::ComboboxInjection;
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
use thaw_components::{If, Then};
|
use thaw_components::{Fallback, If, OptionComp, Then};
|
||||||
use thaw_utils::class_list;
|
use thaw_utils::class_list;
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn ComboboxOption(#[prop(into)] key: String, children: Children) -> impl IntoView {
|
pub fn ComboboxOption(
|
||||||
|
#[prop(optional, into)] value: Option<String>,
|
||||||
|
#[prop(into)] text: String,
|
||||||
|
#[prop(optional)] children: Option<Children>,
|
||||||
|
) -> impl IntoView {
|
||||||
let combobox = ComboboxInjection::use_();
|
let combobox = ComboboxInjection::use_();
|
||||||
let key = StoredValue::new(key);
|
let value = StoredValue::new(value.unwrap_or_else(|| text.clone()));
|
||||||
|
let text = StoredValue::new(text);
|
||||||
|
let on_click = move |_| {
|
||||||
|
text.with_value(|text| {
|
||||||
|
value.with_value(|value| {
|
||||||
|
combobox.on_option_select(value, text);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<div
|
<div
|
||||||
|
@ -14,17 +26,15 @@ pub fn ComboboxOption(#[prop(into)] key: String, children: Children) -> impl Int
|
||||||
aria-selected="true"
|
aria-selected="true"
|
||||||
class=class_list![
|
class=class_list![
|
||||||
"thaw-combobox-option",
|
"thaw-combobox-option",
|
||||||
("thaw-combobox-option--selected", move || key.with_value(|key| combobox.is_selected(&key)))
|
("thaw-combobox-option--selected", move || value.with_value(|value| combobox.is_selected(&value)))
|
||||||
]
|
]
|
||||||
on:click=move |_| {
|
on:click=on_click
|
||||||
key.with_value(|key| combobox.on_option_select(key));
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
if combobox.multiselect {
|
if combobox.multiselect {
|
||||||
view! {
|
view! {
|
||||||
<span aria-hidden="true" class="thaw-combobox-option__check-icon--multiselect">
|
<span aria-hidden="true" class="thaw-combobox-option__check-icon--multiselect">
|
||||||
<If cond=Signal::derive(move || key.with_value(|key| combobox.is_selected(&key)))>
|
<If cond=Signal::derive(move || value.with_value(|value| combobox.is_selected(&value)))>
|
||||||
<Then slot>
|
<Then slot>
|
||||||
<svg fill="currentColor" aria-hidden="true" width="12" height="12" viewBox="0 0 12 12">
|
<svg fill="currentColor" aria-hidden="true" width="12" height="12" viewBox="0 0 12 12">
|
||||||
<path d="M9.76 3.2c.3.29.32.76.04 1.06l-4.25 4.5a.75.75 0 0 1-1.08.02L2.22 6.53a.75.75 0 0 1 1.06-1.06l1.7 1.7L8.7 3.24a.75.75 0 0 1 1.06-.04Z" fill="currentColor"></path>
|
<path d="M9.76 3.2c.3.29.32.76.04 1.06l-4.25 4.5a.75.75 0 0 1-1.08.02L2.22 6.53a.75.75 0 0 1 1.06-1.06l1.7 1.7L8.7 3.24a.75.75 0 0 1 1.06-.04Z" fill="currentColor"></path>
|
||||||
|
@ -43,7 +53,12 @@ pub fn ComboboxOption(#[prop(into)] key: String, children: Children) -> impl Int
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
<OptionComp value=children let:children>
|
||||||
|
<Fallback slot>
|
||||||
|
{text.get_value()}
|
||||||
|
</Fallback>
|
||||||
{children()}
|
{children()}
|
||||||
|
</OptionComp>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue