feat: ComboboxOption adds disabled prop

This commit is contained in:
luoxiao 2024-08-06 21:56:18 +08:00
parent 25d0b81c07
commit 1c243922bd
4 changed files with 97 additions and 23 deletions

View file

@ -78,6 +78,11 @@
outline-style: none;
}
.thaw-combobox__input::placeholder {
color: var(--colorNeutralForeground4);
opacity: 1;
}
.thaw-combobox__clear-icon,
.thaw-combobox__expand-icon {
display: block;
@ -88,6 +93,28 @@
font-size: 20px;
}
.thaw-combobox.thaw-combobox--disabled {
border-color: var(--colorNeutralStrokeDisabled);
border-bottom-color: var(--colorNeutralStrokeDisabled);
background-color: var(--colorTransparentBackground);
cursor: not-allowed;
}
.thaw-combobox--disabled > .thaw-combobox__input {
background-color: var(--colorTransparentBackground);
color: var(--colorNeutralForegroundDisabled);
cursor: not-allowed;
}
.thaw-combobox--disabled > .thaw-combobox__input::placeholder {
color: var(--colorNeutralForegroundDisabled);
}
.thaw-combobox--disabled > .thaw-combobox__clear-icon,
.thaw-combobox--disabled > .thaw-combobox__expand-icon {
cursor: not-allowed;
}
.thaw-combobox__listbox {
row-gap: var(--spacingHorizontalXXS);
display: flex;
@ -181,3 +208,13 @@
color: var(--colorNeutralForegroundInverted);
background-color: var(--colorCompoundBrandBackground);
}
.thaw-combobox-option--disabled {
color: var(--colorNeutralForegroundDisabled);
}
.thaw-combobox-option--disabled:active,
.thaw-combobox-option--disabled:hover {
background-color: var(--colorTransparentBackground);
color: var(--colorNeutralForegroundDisabled);
}

View file

@ -10,8 +10,11 @@ pub fn Combobox(
#[prop(optional, into)] class: MaybeProp<String>,
#[prop(optional, into)] value: Model<String>,
#[prop(optional, into)] selected_options: VecModel<String>,
#[prop(optional, into)] disabled: MaybeSignal<bool>,
#[prop(optional, into)] placeholder: MaybeProp<String>,
/// If set, the combobox will show an icon to clear the current value.
#[prop(optional)] clearable: bool,
#[prop(optional)]
clearable: bool,
children: Children,
) -> impl IntoView {
mount_style("combobox", include_str!("./combobox.css"));
@ -19,7 +22,7 @@ pub fn Combobox(
let input_ref = NodeRef::<html::Input>::new();
let listbox_ref = NodeRef::<html::Div>::new();
let is_show_listbox = RwSignal::new(false);
let options = StoredValue::new(HashMap::<String, (String, String)>::new());
let options = StoredValue::new(HashMap::<String, (String, String, MaybeSignal<bool>)>::new());
let clear_icon_ref = NodeRef::<html::Span>::new();
let is_show_clear_icon = Memo::new(move |_| {
@ -40,6 +43,9 @@ pub fn Combobox(
return;
};
let handler = add_event_listener(clear_icon_el.into(), ev::click, move |e| {
if disabled.get_untracked() {
return;
}
e.stop_propagation();
selected_options.set(vec![]);
});
@ -138,7 +144,10 @@ pub fn Combobox(
&active_descendant_controller,
move |option| {
combobox_injection.options.with_value(|options| {
if let Some((value, text)) = options.get(&option.id()) {
if let Some((value, text, disabled)) = options.get(&option.id()) {
if disabled.get_untracked() {
return;
}
combobox_injection.select_option(value, text);
}
});
@ -149,7 +158,11 @@ pub fn Combobox(
view! {
<Binder target_ref=trigger_ref>
<div
class=class_list!["thaw-combobox", class]
class=class_list![
"thaw-combobox",
("thaw-combobox--disabled", move || disabled.get()),
class
]
node_ref=trigger_ref
>
<input
@ -160,6 +173,8 @@ pub fn Combobox(
prop:value=move || {
value.get()
}
placeholder=move || placeholder.get()
disabled=move || disabled.get()
node_ref=input_ref
on:input=on_input
on:blur=on_blur
@ -187,12 +202,16 @@ pub fn Combobox(
}
}
<span
aria-expanded="true"
aria-disabled=move || if disabled.get() { "true" } else { "" }
aria-expanded=move || is_show_listbox.get().to_string()
role="button"
aria-label="Open"
class="thaw-combobox__expand-icon"
style=move || is_show_clear_icon.get().then(|| "display: none").unwrap_or_default()
on:click=move |_| {
if disabled.get_untracked() {
return;
}
is_show_listbox.update(|show| *show = !*show);
if let Some(el) = input_ref.get_untracked() {
let _ = el.focus();
@ -225,7 +244,7 @@ pub fn Combobox(
pub(crate) struct ComboboxInjection {
value: Model<String>,
selected_options: VecModel<String>,
options: StoredValue<HashMap<String, (String, String)>>,
options: StoredValue<HashMap<String, (String, String, MaybeSignal<bool>)>>,
is_show_listbox: RwSignal<bool>,
pub multiselect: bool,
}
@ -235,7 +254,7 @@ impl ComboboxInjection {
expect_context()
}
pub fn insert_option(&self, id: String, value: (String, String)) {
pub fn insert_option(&self, id: String, value: (String, String, MaybeSignal<bool>)) {
self.options
.update_value(|options| options.insert(id, value));
}

View file

@ -7,10 +7,16 @@ use thaw_utils::class_list;
#[component]
pub fn ComboboxOption(
#[prop(optional, into)] class: MaybeProp<String>,
/// Sets an option to the disabled state. Disabled options cannot be selected,
/// but are still keyboard navigable.
#[prop(optional, into)]
disabled: MaybeSignal<bool>,
/// Defines a unique identifier for the option. Defaults to `text` if not provided.
#[prop(optional, into)] value: Option<String>,
#[prop(optional, into)]
value: Option<String>,
/// An optional override the string value of the Option's display text, defaulting to the Option's child content.
#[prop(into)] text: String,
#[prop(into)]
text: String,
#[prop(optional)] children: Option<Children>,
) -> impl IntoView {
let combobox = ComboboxInjection::expect_context();
@ -21,6 +27,9 @@ pub fn ComboboxOption(
let id = uuid::Uuid::new_v4().to_string();
let on_click = move |_| {
if disabled.get_untracked() {
return;
}
text.with_value(|text| {
value.with_value(|value| {
combobox.select_option(value, text);
@ -29,7 +38,7 @@ pub fn ComboboxOption(
};
{
combobox.insert_option(id.clone(), (value.get_value(), text.get_value()));
combobox.insert_option(id.clone(), (value.get_value(), text.get_value(), disabled));
let id = id.clone();
listbox.trigger();
on_cleanup(move || {
@ -41,11 +50,13 @@ pub fn ComboboxOption(
view! {
<div
role="option"
aria-selected=move || if is_selected.get() { "true" } else { "false" }
aria-disabled=move || if disabled.get() { "true" } else { "" }
aria-selected=move || is_selected.get().to_string()
id=id
class=class_list![
"thaw-combobox-option",
("thaw-combobox-option--selected", move || is_selected.get()),
("thaw-combobox-option--disabled", move || disabled.get()),
class
]
on:click=on_click

View file

@ -4,8 +4,8 @@
let selected_options = RwSignal::new(None::<String>);
view! {
<Combobox selected_options>
<ComboboxOption value="cat" text="Car" />
<Combobox selected_options placeholder="Select an animal">
<ComboboxOption value="cat" text="Cat" disabled=true/>
<ComboboxOption value="dog" text="Dog" />
</Combobox>
}
@ -18,7 +18,7 @@ let selected_options = RwSignal::new(vec![]);
view! {
<Combobox selected_options clearable=true>
<ComboboxOption value="cat" text="Car" />
<ComboboxOption value="cat" text="Cat" disabled=true/>
<ComboboxOption value="dog" text="Dog" />
</Combobox>
}
@ -31,7 +31,7 @@ let selected_options = RwSignal::new(vec![]);
view! {
<Combobox selected_options>
<ComboboxOption value="cat" text="Car" />
<ComboboxOption value="cat" text="Cat" disabled=true/>
<ComboboxOption value="dog" text="Dog" />
</Combobox>
}
@ -49,21 +49,28 @@ view! {
<ComboboxOptionGroup label="Land">
{
land.into_iter().map(|option| view!{
<ComboboxOption value={option.clone()}>
{option}
</ComboboxOption>
<ComboboxOption text={option} />
}).collect_view()
}
</ComboboxOptionGroup>
<OptionGroup label="Sea">
<ComboboxOptionGroup label="Sea">
{
water.into_iter().map(|option| view!{
<ComboboxOption value={option.clone()}>
{option}
</ComboboxOption>
<ComboboxOption text={option} />
}).collect_view()
}
</OptionGroup>
</ComboboxOptionGroup>
</Combobox>
}
```
### Disabled
```rust demo
view! {
<Combobox disabled=true>
<ComboboxOption value="cat" text="Car" />
<ComboboxOption value="dog" text="Dog" />
</Combobox>
}
```