Skip to content

Commit

Permalink
Feat/tag picker (#260)
Browse files Browse the repository at this point in the history
* refactor: Listbox and OptionGroup

* feat: adds TagPickerOptionGroup component

* fix: call_on_click_outside_with_list
  • Loading branch information
luoxiaozero authored Sep 12, 2024
1 parent 09ef9f8 commit 092581d
Show file tree
Hide file tree
Showing 15 changed files with 172 additions and 53 deletions.
15 changes: 0 additions & 15 deletions thaw/src/combobox/combobox.css
Original file line number Diff line number Diff line change
Expand Up @@ -115,21 +115,6 @@
cursor: not-allowed;
}

.thaw-combobox__listbox {
row-gap: var(--spacingHorizontalXXS);
display: flex;
flex-direction: column;
min-width: 160px;
max-height: 80vh;
background-color: var(--colorNeutralBackground1);
padding: var(--spacingHorizontalXS);
outline: 1px solid var(--colorTransparentStroke);
border-radius: var(--borderRadiusMedium);
box-shadow: var(--shadow16);
box-sizing: border-box;
overflow-y: auto;
}

.thaw-combobox-option {
column-gap: var(--spacingHorizontalXS);
position: relative;
Expand Down
16 changes: 2 additions & 14 deletions thaw/src/combobox/combobox_option_group.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::option_group::OptionGroup;
use leptos::prelude::*;
use thaw_utils::{class_list, mount_style};

#[component]
pub fn ComboboxOptionGroup(
Expand All @@ -9,17 +9,5 @@ pub fn ComboboxOptionGroup(
label: String,
children: Children,
) -> impl IntoView {
mount_style(
"combobox-option-group",
include_str!("./combobox-option-group.css"),
);

view! {
<div role="group" class=class_list!["thaw-combobox-option-group", class]>
<span role="presentation" class="thaw-combobox-option-group__label">
{label}
</span>
{children()}
</div>
}
view! { <OptionGroup class_prefix="thaw-combobox-option-group" class label children /> }
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
.thaw-listbox {
row-gap: var(--spacingHorizontalXXS);
display: flex;
flex-direction: column;
min-width: 160px;
max-height: 80vh;
background-color: var(--colorNeutralBackground1);
padding: var(--spacingHorizontalXS);
outline: 1px solid var(--colorTransparentStroke);
border-radius: var(--borderRadiusMedium);
box-shadow: var(--shadow16);
box-sizing: border-box;
overflow-y: auto;
}

.thaw-listbox.fade-in-scale-up-transition-leave-active {
transform-origin: inherit;
transition: opacity 0.2s cubic-bezier(0.4, 0, 1, 1),
Expand All @@ -20,4 +35,4 @@
.thaw-listbox.fade-in-scale-up-transition-enter-to {
opacity: 1;
transform: scale(1);
}
}
File renamed without changes.
3 changes: 3 additions & 0 deletions thaw/src/combobox/common/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
pub mod listbox;
pub mod option_group;
mod utils;
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
.thaw-combobox-option-group {
.thaw-option-group {
display: flex;
row-gap: var(--spacingHorizontalXXS);
flex-direction: column;
}

.thaw-combobox-option-group__label {
.thaw-option-group__label {
display: block;
color: var(--colorNeutralForeground3);
font-weight: var(--fontWeightSemibold);
Expand All @@ -14,7 +14,7 @@
border-radius: var(--borderRadiusMedium);
}

.thaw-combobox-option-group:not(:last-child)::after {
.thaw-option-group:not(:last-child)::after {
content: "";
display: block;
margin: 0 calc(var(--spacingHorizontalXS) * -1) var(--spacingVerticalXS);
Expand Down
25 changes: 25 additions & 0 deletions thaw/src/combobox/common/option_group.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
use leptos::prelude::*;
use thaw_utils::{class_list, mount_style};

#[component]
pub fn OptionGroup(
class_prefix: &'static str,
class: MaybeProp<String>,
/// Label of the group.
label: String,
children: Children,
) -> impl IntoView {
mount_style("option-group", include_str!("./option-group.css"));

view! {
<div role="group" class=class_list!["thaw-option-group", class_prefix, class]>
<span
role="presentation"
class=format!("thaw-option-group__label {class_prefix}__label")
>
{label}
</span>
{children()}
</div>
}
}
File renamed without changes.
4 changes: 2 additions & 2 deletions thaw/src/combobox/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
mod combobox;
mod combobox_option;
mod combobox_option_group;
pub(crate) mod listbox;
mod utils;
mod common;

pub use combobox::*;
pub use combobox_option::*;
pub use combobox_option_group::*;
pub(crate) use common::*;
78 changes: 78 additions & 0 deletions thaw/src/tag_picker/docs/mod.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,76 @@ view! {
}
```

### Grouped

```rust demo
use leptos::either::Either;
let selected_options = RwSignal::new(vec![]);
let land = vec!["Cat", "Dog", "Ferret", "Hamster"];
let water = vec!["Fish", "Jellyfish", "Octopus", "Seal"];

view! {
<TagPicker selected_options>
<TagPickerControl slot>
<TagPickerGroup>
{move || {
selected_options.get().into_iter().map(|option| view!{
<Tag value=option.clone()>
{option}
</Tag>
}).collect_view()
}}
</TagPickerGroup>
<TagPickerInput />
</TagPickerControl>
{move || {
selected_options.with(|selected_options| {
let land_view = land.iter().filter_map(|option| {
if selected_options.iter().any(|o| o == option) {
return None
} else {
Some(view! {
<TagPickerOption value=option.clone() text=option.clone() />
})
}
}).collect_view();
if land_view.is_empty() {
Either::Left(())
} else {
Either::Right(view! {
<TagPickerOptionGroup label="Land">
{land_view}
</TagPickerOptionGroup>
})
}
})
}}
{move || {
selected_options.with(|selected_options| {
let water_view = water.iter().filter_map(|option| {
if selected_options.iter().any(|o| o == option) {
return None
} else {
Some(view! {
<TagPickerOption value=option.clone() text=option.clone() />
})
}
}).collect_view();
if water_view.is_empty() {
Either::Left(())
} else {
Either::Right(view! {
<TagPickerOptionGroup label="Sea">
{water_view}
</TagPickerOptionGroup>
})
}
})
}}
</TagPicker>
}
```

### TagPicker Props

| Name | Type | Default | Description |
Expand Down Expand Up @@ -68,3 +138,11 @@ view! {
| value | `String` | | Defines a unique identifier for the option. |
| text | `String` | | An optional override the string value of the Option's display text, defaulting to the Option's child content. |
| children | `Option<Children>` | `None` | |

### TagPickerOptionGroup Props

| Name | Type | Default | Desciption |
| -------- | ------------------- | -------------------- | ------------------- |
| class | `MaybeProp<String>` | `Default::default()` | |
| label | `String` | | Label of the group. |
| children | `Children` | | |
2 changes: 2 additions & 0 deletions thaw/src/tag_picker/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ mod tag_picker;
mod tag_picker_group;
mod tag_picker_input;
mod tag_picker_option;
mod tag_picker_option_group;

pub use tag_picker::*;
pub use tag_picker_group::*;
pub use tag_picker_input::*;
pub use tag_picker_option::*;
pub use tag_picker_option_group::*;
15 changes: 0 additions & 15 deletions thaw/src/tag_picker/tag-picker.css
Original file line number Diff line number Diff line change
Expand Up @@ -82,21 +82,6 @@
outline-style: none;
}

.thaw-tag-picker__listbox {
display: flex;
flex-direction: column;
row-gap: var(--spacingHorizontalXXS);
overflow-y: auto;
min-width: 160px;
max-height: 80vh;
background-color: var(--colorNeutralBackground1);
padding: var(--spacingHorizontalXS);
outline: 1px solid var(--colorTransparentStroke);
border-radius: var(--borderRadiusMedium);
box-shadow: var(--shadow16);
box-sizing: border-box;
}

.thaw-tag-picker-option {
grid-template-columns: auto 1fr;
column-gap: var(--spacingHorizontalXS);
Expand Down
6 changes: 3 additions & 3 deletions thaw/src/tag_picker/tag_picker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::{
use leptos::{context::Provider, ev, html, prelude::*};
use std::collections::HashMap;
use thaw_components::{Binder, Follower, FollowerPlacement, FollowerWidth};
use thaw_utils::{call_on_click_outside, class_list, mount_style, BoxCallback, Model};
use thaw_utils::{call_on_click_outside_with_list, class_list, mount_style, BoxCallback, Model};

#[component]
pub fn TagPicker(
Expand Down Expand Up @@ -55,8 +55,8 @@ pub fn TagPicker(
}
is_show_listbox.update(|show| *show = !*show);
};
call_on_click_outside(
trigger_ref,
call_on_click_outside_with_list(
vec![trigger_ref, listbox_ref],
{
move || {
is_show_listbox.set(false);
Expand Down
13 changes: 13 additions & 0 deletions thaw/src/tag_picker/tag_picker_option_group.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
use crate::option_group::OptionGroup;
use leptos::prelude::*;

#[component]
pub fn TagPickerOptionGroup(
#[prop(optional, into)] class: MaybeProp<String>,
/// Label of the group.
#[prop(into)]
label: String,
children: Children,
) -> impl IntoView {
view! { <OptionGroup class_prefix="thaw-tag-picker-option-group" class label children /> }
}
25 changes: 25 additions & 0 deletions thaw_utils/src/on_click_outside.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,28 @@ pub fn call_on_click_outside(element: NodeRef<Div>, on_click: BoxCallback) {
let _ = on_click;
}
}

pub fn call_on_click_outside_with_list(refs: Vec<NodeRef<Div>>, on_click: BoxCallback) {
#[cfg(any(feature = "csr", feature = "hydrate"))]
{
let handle = window_event_listener(::leptos::ev::click, move |ev| {
let composed_path = ev.composed_path();
if refs.iter().any(|r| {
if let Some(el) = r.get_untracked() {
composed_path.includes(&el, 0)
} else {
false
}
}) {
return;
}
on_click();
});
on_cleanup(move || handle.remove());
}
#[cfg(not(any(feature = "csr", feature = "hydrate")))]
{
let _ = refs;
let _ = on_click;
}
}

0 comments on commit 092581d

Please sign in to comment.