Skip to content

Commit

Permalink
Add "Select all" functionality to multiselect
Browse files Browse the repository at this point in the history
  • Loading branch information
ynotdraw committed Aug 2, 2023
1 parent 2e89732 commit 3312a38
Show file tree
Hide file tree
Showing 15 changed files with 683 additions and 43 deletions.
6 changes: 6 additions & 0 deletions .changeset/two-pets-travel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@crowdstrike/ember-toucan-core': patch
'@crowdstrike/ember-toucan-form': patch
---

Adds "Select all" functionality to the `Multiselect` via a new component argument. Provide `@selectAllText` to opt-in to the functionality.
46 changes: 42 additions & 4 deletions docs/components/multiselect-field/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ Provide a string to the `@label` component argument or content to the `:label` n

Required.

A `:chip` block is required and is used for rendering each selected option.
A `:chip` block is required and is used for rendering each selected option.
The block has the following block parameters:

- `index`: The index of the current chip
Expand All @@ -72,8 +72,8 @@ The `Chip` component allows for slight customization to the underlying chip.
</:chip>
```

The `Remove` component contains the removal `X` on each selected chip.
Clicking the button will remove the item from the selected options array.
The `Remove` component contains the removal `X` on each selected chip.
Clicking the button will remove the item from the selected options array.
When the multiselect is disabled or in the readonly state, the button will not be available.

A `@label` argument is **required** for accessibility reasons for the Remove component.
Expand Down Expand Up @@ -134,7 +134,7 @@ An example with translations may be something like:

Required.

`@noResultsText` is shown when there are no results after filtering.
`@noResultsText` is shown when there are no results after filtering.

```hbs
<Form::Fields::Multiselect
Expand Down Expand Up @@ -300,6 +300,44 @@ export default class extends Component {
}
```

## Select all

Optional.

"Select all" functionality can be opted into by providing the `@selectAllText` argument.

By providing this argument, a checkbox will be rendered at the top of the list to allow users a convenient way to select all visible options. When clicking this item, all `@options` are returned to the `@onChange` handler. The "Select all" checkbox has the following state rules:

- The checkbox only appears when filtering is not active.
- The checkbox will be checked when all options are selected.
- If no options are selected, the checkbox will be unchecked.
- If more than one option is selected, but not all of them, then the checkbox will be in the indeterminate state.
- When the checkbox is in the indeterminate state, clicking the checkbox re-selects all options.

```hbs
<Form::Fields::Multiselect
@contentClass='z-10'
@label='Label'
@onChange={{this.onChange}}
@options={{this.options}}
@selectAllText='Select all'
@selected={{this.selected}}
>
<:chip as |chip|>
<chip.Chip>
{{chip.option}}
<chip.Remove @label={{(concat 'Remove' ' ' chip.option)}} />
</chip.Chip>
</:chip>
<:default as |multiselect|>
<multiselect.Option>
{{multiselect.option}}
</multiselect.Option>
</:default>
</Form::Fields::Multiselect>
```

## onFilter

Optional.
Expand Down
36 changes: 33 additions & 3 deletions docs/components/multiselect/demo/base-demo.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<div class='flex flex-col gap-4 w-96'>
<Form::Controls::Multiselect
@contentClass='z-10'
@noResultsText="No results"
@noResultsText='No results'
@onChange={{this.onChange}}
@options={{this.options}}
@selected={{this.selected}}
Expand All @@ -24,7 +24,7 @@
<Form::Controls::Multiselect
@contentClass='z-10'
@noResultsText="No results"
@noResultsText='No results'
@onChange={{this.onChange2}}
@options={{this.options2}}
@selected={{this.selected2}}
Expand All @@ -46,7 +46,7 @@
<Form::Controls::Multiselect
@contentClass='z-10'
@noResultsText="No results"
@noResultsText='No results'
@onChange={{this.onChange3}}
@onFilter={{this.onFilter}}
@options={{this.options}}
Expand All @@ -66,6 +66,29 @@
</multiselect.Option>
</:default>
</Form::Controls::Multiselect>
<Form::Controls::Multiselect
@contentClass='z-10'
@noResultsText='No results'
@onChange={{this.onChange4}}
@options={{this.options}}
@selected={{this.selected4}}
@selectAllText='Select all'
placeholder='Colors w/ Select All'
>
<:chip as |chip|>
<chip.Chip>
{{chip.option}}
<chip.Remove @label={{(concat 'Remove' ' ' chip.option)}} />
</chip.Chip>
</:chip>
<:default as |multiselect|>
<multiselect.Option>
{{multiselect.option}}
</multiselect.Option>
</:default>
</Form::Controls::Multiselect>
</div>
```

Expand All @@ -78,6 +101,7 @@ export default class extends Component {
@tracked selected;
@tracked selected2;
@tracked selected3;
@tracked selected4;

options = [
{
Expand Down Expand Up @@ -148,6 +172,12 @@ export default class extends Component {
console.log(option);
}

@action
onChange4(option) {
this.selected4 = option;
console.log(option);
}

@action
onFilter(value) {
console.log(`filtering with the value "${value}"`);
Expand Down
53 changes: 45 additions & 8 deletions docs/components/multiselect/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ If you are building forms, you may be interested in the [MultiselectField](./mul

Required.

A `:chip` block is required and is used for rendering each selected option.
A `:chip` block is required and is used for rendering each selected option.
The block returns the following:

- `index`: The index of the current chip
Expand All @@ -26,8 +26,8 @@ The `Chip` component allows for slight customization to the underlying chip.
</:chip>
```

The `Remove` component contains the removal `X` on each selected chip.
Clicking the button will remove the item from the selected options array.
The `Remove` component contains the removal `X` on each selected chip.
Clicking the button will remove the item from the selected options array.
When the multiselect is disabled or in the readonly state, the button will not be available.

A `@label` argument is **required** for accessibility reasons for the Remove component.
Expand All @@ -36,7 +36,7 @@ The `option` for that chip is yielded back to the consumer so that an appropriat

```hbs
<Form::Controls::Multiselect
@noResultsText="No results"
@noResultsText='No results'
@onChange={{this.onChange}}
@options={{this.options}}
@contentClass='z-10'
Expand All @@ -62,7 +62,7 @@ An example with translations may be something like:

```hbs
<Form::Controls::Multiselect
@noResultsText="No results"
@noResultsText='No results'
@onChange={{this.onChange}}
@options={{this.options}}
@contentClass='z-10'
Expand All @@ -88,12 +88,12 @@ An example with translations may be something like:

Required.

`@noResultsText` is shown when there are no results after filtering.
`@noResultsText` is shown when there are no results after filtering.

```hbs
<Form::Controls::Multiselect
@contentClass='z-10'
@noResultsText="No results"
@noResultsText='No results'
@onChange={{this.onChange}}
@options={{this.options}}
@selected={{this.selected}}
Expand Down Expand Up @@ -205,6 +205,43 @@ export default class extends Component {
}
```

## Select all

Optional.

"Select all" functionality can be opted into by providing the `@selectAllText` argument.

By providing this argument, a checkbox will be rendered at the top of the list to allow users a convenient way to select all visible options. When clicking this item, all `@options` are returned to the `@onChange` handler. The "Select all" checkbox has the following state rules:

- The checkbox only appears when filtering is not active.
- The checkbox will be checked when all options are selected.
- If no options are selected, the checkbox will be unchecked.
- If more than one option is selected, but not all of them, then the checkbox will be in the indeterminate state.
- When the checkbox is in the indeterminate state, clicking the checkbox re-selects all options.

```hbs
<Form::Controls::Multiselect
@contentClass='z-10'
@onChange={{this.onChange}}
@options={{this.options}}
@selectAllText='Select all'
@selected={{this.selected}}
>
<:chip as |chip|>
<chip.Chip>
{{chip.option}}
<chip.Remove @label={{(concat 'Remove' ' ' chip.option)}} />
</chip.Chip>
</:chip>
<:default as |multiselect|>
<multiselect.Option>
{{multiselect.option}}
</multiselect.Option>
</:default>
</Form::Controls::Multiselect>
```

## onFilter

Optional.
Expand All @@ -214,7 +251,7 @@ Specify `onFilter` if you want to do something different.

```hbs
<Form::Controls::Multiselect
@noResultsText="No results"
@noResultsText='No results'
@onFilter={{this.onFilter}}
@onChange={{this.onChange}}
@options={{this.options}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ interface ToucanFormMultiselectOptionControlComponentSignature {
Element: HTMLLIElement;
}

const className = 'toucan-form-select-option-control';
export const className = 'toucan-form-select-option-control';

export const selector = `.${className}`;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{{! template-lint-disable require-presentational-children }}
{{#let (unique-id) as |uniqueId|}}
<li
aria-selected={{if @isSelected "true" "false"}}
class="my-0 cursor-default px-2 leading-4
{{this.styles}}
{{this.className}}
"
data-active={{if @isActive "true" "false"}}
data-multiselect-select-all-option
id="{{@popoverId}}-{{@index}}"
role="option"
{{on "click" this.onClick}}
{{! template-lint-disable no-pointer-down-event-binding }}
{{on "mousedown" this.onMousedown}}
{{on "mouseover" @onMouseover}}
...attributes
>
<div class="border-lines-dark flex w-full items-center gap-2 border-b py-2">
{{!
We set `tabindex="-1"` here to remove the checkbox from the tab order.
Instead, we allow the user to use the keyboard arrows to "focus" and
make options active. If we remove this line, you'll notice that the
focus returns to the body when tabbing out of the multiautocomplete
component, which is a strange user experience.
Template lint doesn't like us setting tabindex, but we need it here
as we don't want these elements focusable!
}}
{{! template-lint-disable no-nested-interactive }}
<Form::Controls::Checkbox
data-multiselect-select-all-checkbox
id={{uniqueId}}
tabindex="-1"
@isChecked={{@isSelected}}
@isDisabled={{@isDisabled}}
@isIndeterminate={{@isIndeterminate}}
@isReadOnly={{@isReadOnly}}
@value={{@value}}
/>

<span>
{{yield}}
</span>
</div>
</li>
{{/let}}
Loading

0 comments on commit 3312a38

Please sign in to comment.