mirror of
https://github.com/adoyle0/thaw.git
synced 2025-01-22 22:09:22 -05:00
feat: ComboboxOption adds disabled prop
This commit is contained in:
parent
25d0b81c07
commit
1c243922bd
4 changed files with 97 additions and 23 deletions
|
@ -78,6 +78,11 @@
|
||||||
outline-style: none;
|
outline-style: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.thaw-combobox__input::placeholder {
|
||||||
|
color: var(--colorNeutralForeground4);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.thaw-combobox__clear-icon,
|
.thaw-combobox__clear-icon,
|
||||||
.thaw-combobox__expand-icon {
|
.thaw-combobox__expand-icon {
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -88,6 +93,28 @@
|
||||||
font-size: 20px;
|
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 {
|
.thaw-combobox__listbox {
|
||||||
row-gap: var(--spacingHorizontalXXS);
|
row-gap: var(--spacingHorizontalXXS);
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -181,3 +208,13 @@
|
||||||
color: var(--colorNeutralForegroundInverted);
|
color: var(--colorNeutralForegroundInverted);
|
||||||
background-color: var(--colorCompoundBrandBackground);
|
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);
|
||||||
|
}
|
||||||
|
|
|
@ -10,8 +10,11 @@ pub fn Combobox(
|
||||||
#[prop(optional, into)] class: MaybeProp<String>,
|
#[prop(optional, into)] class: MaybeProp<String>,
|
||||||
#[prop(optional, into)] value: Model<String>,
|
#[prop(optional, into)] value: Model<String>,
|
||||||
#[prop(optional, into)] selected_options: VecModel<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.
|
/// If set, the combobox will show an icon to clear the current value.
|
||||||
#[prop(optional)] clearable: 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"));
|
||||||
|
@ -19,7 +22,7 @@ pub fn Combobox(
|
||||||
let input_ref = NodeRef::<html::Input>::new();
|
let input_ref = NodeRef::<html::Input>::new();
|
||||||
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 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 clear_icon_ref = NodeRef::<html::Span>::new();
|
||||||
let is_show_clear_icon = Memo::new(move |_| {
|
let is_show_clear_icon = Memo::new(move |_| {
|
||||||
|
@ -40,6 +43,9 @@ pub fn Combobox(
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
let handler = add_event_listener(clear_icon_el.into(), ev::click, move |e| {
|
let handler = add_event_listener(clear_icon_el.into(), ev::click, move |e| {
|
||||||
|
if disabled.get_untracked() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
e.stop_propagation();
|
e.stop_propagation();
|
||||||
selected_options.set(vec![]);
|
selected_options.set(vec![]);
|
||||||
});
|
});
|
||||||
|
@ -138,7 +144,10 @@ pub fn Combobox(
|
||||||
&active_descendant_controller,
|
&active_descendant_controller,
|
||||||
move |option| {
|
move |option| {
|
||||||
combobox_injection.options.with_value(|options| {
|
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);
|
combobox_injection.select_option(value, text);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -149,7 +158,11 @@ pub fn Combobox(
|
||||||
view! {
|
view! {
|
||||||
<Binder target_ref=trigger_ref>
|
<Binder target_ref=trigger_ref>
|
||||||
<div
|
<div
|
||||||
class=class_list!["thaw-combobox", class]
|
class=class_list![
|
||||||
|
"thaw-combobox",
|
||||||
|
("thaw-combobox--disabled", move || disabled.get()),
|
||||||
|
class
|
||||||
|
]
|
||||||
node_ref=trigger_ref
|
node_ref=trigger_ref
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
|
@ -160,6 +173,8 @@ pub fn Combobox(
|
||||||
prop:value=move || {
|
prop:value=move || {
|
||||||
value.get()
|
value.get()
|
||||||
}
|
}
|
||||||
|
placeholder=move || placeholder.get()
|
||||||
|
disabled=move || disabled.get()
|
||||||
node_ref=input_ref
|
node_ref=input_ref
|
||||||
on:input=on_input
|
on:input=on_input
|
||||||
on:blur=on_blur
|
on:blur=on_blur
|
||||||
|
@ -187,12 +202,16 @@ pub fn Combobox(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
<span
|
<span
|
||||||
aria-expanded="true"
|
aria-disabled=move || if disabled.get() { "true" } else { "" }
|
||||||
|
aria-expanded=move || is_show_listbox.get().to_string()
|
||||||
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").unwrap_or_default()
|
style=move || is_show_clear_icon.get().then(|| "display: none").unwrap_or_default()
|
||||||
on:click=move |_| {
|
on:click=move |_| {
|
||||||
|
if disabled.get_untracked() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
is_show_listbox.update(|show| *show = !*show);
|
is_show_listbox.update(|show| *show = !*show);
|
||||||
if let Some(el) = input_ref.get_untracked() {
|
if let Some(el) = input_ref.get_untracked() {
|
||||||
let _ = el.focus();
|
let _ = el.focus();
|
||||||
|
@ -225,7 +244,7 @@ pub fn Combobox(
|
||||||
pub(crate) struct ComboboxInjection {
|
pub(crate) struct ComboboxInjection {
|
||||||
value: Model<String>,
|
value: Model<String>,
|
||||||
selected_options: VecModel<String>,
|
selected_options: VecModel<String>,
|
||||||
options: StoredValue<HashMap<String, (String, String)>>,
|
options: StoredValue<HashMap<String, (String, String, MaybeSignal<bool>)>>,
|
||||||
is_show_listbox: RwSignal<bool>,
|
is_show_listbox: RwSignal<bool>,
|
||||||
pub multiselect: bool,
|
pub multiselect: bool,
|
||||||
}
|
}
|
||||||
|
@ -235,7 +254,7 @@ impl ComboboxInjection {
|
||||||
expect_context()
|
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
|
self.options
|
||||||
.update_value(|options| options.insert(id, value));
|
.update_value(|options| options.insert(id, value));
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,10 +7,16 @@ use thaw_utils::class_list;
|
||||||
#[component]
|
#[component]
|
||||||
pub fn ComboboxOption(
|
pub fn ComboboxOption(
|
||||||
#[prop(optional, into)] class: MaybeProp<String>,
|
#[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.
|
/// 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.
|
/// 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>,
|
#[prop(optional)] children: Option<Children>,
|
||||||
) -> impl IntoView {
|
) -> impl IntoView {
|
||||||
let combobox = ComboboxInjection::expect_context();
|
let combobox = ComboboxInjection::expect_context();
|
||||||
|
@ -21,6 +27,9 @@ pub fn ComboboxOption(
|
||||||
let id = uuid::Uuid::new_v4().to_string();
|
let id = uuid::Uuid::new_v4().to_string();
|
||||||
|
|
||||||
let on_click = move |_| {
|
let on_click = move |_| {
|
||||||
|
if disabled.get_untracked() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
text.with_value(|text| {
|
text.with_value(|text| {
|
||||||
value.with_value(|value| {
|
value.with_value(|value| {
|
||||||
combobox.select_option(value, text);
|
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();
|
let id = id.clone();
|
||||||
listbox.trigger();
|
listbox.trigger();
|
||||||
on_cleanup(move || {
|
on_cleanup(move || {
|
||||||
|
@ -41,11 +50,13 @@ pub fn ComboboxOption(
|
||||||
view! {
|
view! {
|
||||||
<div
|
<div
|
||||||
role="option"
|
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
|
id=id
|
||||||
class=class_list![
|
class=class_list![
|
||||||
"thaw-combobox-option",
|
"thaw-combobox-option",
|
||||||
("thaw-combobox-option--selected", move || is_selected.get()),
|
("thaw-combobox-option--selected", move || is_selected.get()),
|
||||||
|
("thaw-combobox-option--disabled", move || disabled.get()),
|
||||||
class
|
class
|
||||||
]
|
]
|
||||||
on:click=on_click
|
on:click=on_click
|
||||||
|
|
|
@ -4,8 +4,8 @@
|
||||||
let selected_options = RwSignal::new(None::<String>);
|
let selected_options = RwSignal::new(None::<String>);
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<Combobox selected_options>
|
<Combobox selected_options placeholder="Select an animal">
|
||||||
<ComboboxOption value="cat" text="Car" />
|
<ComboboxOption value="cat" text="Cat" disabled=true/>
|
||||||
<ComboboxOption value="dog" text="Dog" />
|
<ComboboxOption value="dog" text="Dog" />
|
||||||
</Combobox>
|
</Combobox>
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ let selected_options = RwSignal::new(vec![]);
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<Combobox selected_options clearable=true>
|
<Combobox selected_options clearable=true>
|
||||||
<ComboboxOption value="cat" text="Car" />
|
<ComboboxOption value="cat" text="Cat" disabled=true/>
|
||||||
<ComboboxOption value="dog" text="Dog" />
|
<ComboboxOption value="dog" text="Dog" />
|
||||||
</Combobox>
|
</Combobox>
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ let selected_options = RwSignal::new(vec![]);
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<Combobox selected_options>
|
<Combobox selected_options>
|
||||||
<ComboboxOption value="cat" text="Car" />
|
<ComboboxOption value="cat" text="Cat" disabled=true/>
|
||||||
<ComboboxOption value="dog" text="Dog" />
|
<ComboboxOption value="dog" text="Dog" />
|
||||||
</Combobox>
|
</Combobox>
|
||||||
}
|
}
|
||||||
|
@ -49,21 +49,28 @@ view! {
|
||||||
<ComboboxOptionGroup label="Land">
|
<ComboboxOptionGroup label="Land">
|
||||||
{
|
{
|
||||||
land.into_iter().map(|option| view!{
|
land.into_iter().map(|option| view!{
|
||||||
<ComboboxOption value={option.clone()}>
|
<ComboboxOption text={option} />
|
||||||
{option}
|
|
||||||
</ComboboxOption>
|
|
||||||
}).collect_view()
|
}).collect_view()
|
||||||
}
|
}
|
||||||
</ComboboxOptionGroup>
|
</ComboboxOptionGroup>
|
||||||
<OptionGroup label="Sea">
|
<ComboboxOptionGroup label="Sea">
|
||||||
{
|
{
|
||||||
water.into_iter().map(|option| view!{
|
water.into_iter().map(|option| view!{
|
||||||
<ComboboxOption value={option.clone()}>
|
<ComboboxOption text={option} />
|
||||||
{option}
|
|
||||||
</ComboboxOption>
|
|
||||||
}).collect_view()
|
}).collect_view()
|
||||||
}
|
}
|
||||||
</OptionGroup>
|
</ComboboxOptionGroup>
|
||||||
</Combobox>
|
</Combobox>
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Disabled
|
||||||
|
|
||||||
|
```rust demo
|
||||||
|
view! {
|
||||||
|
<Combobox disabled=true>
|
||||||
|
<ComboboxOption value="cat" text="Car" />
|
||||||
|
<ComboboxOption value="dog" text="Dog" />
|
||||||
|
</Combobox>
|
||||||
|
}
|
||||||
|
```
|
Loading…
Add table
Reference in a new issue