MultiSelect

Used to choose multiple options from a predefined list of options.

Installation

import { MultiSelect } from "@octopusdeploy/design-system-components"

Properties

Using the MultiSelect component

When setting up a MultiSelect component you will need to import MultiSelect from the design-system-components package.

For example:


                                                        
                                                        
                                                            <MultiSelect    
                                                            label="Input label"   
                                                            value={selectedItemIds}    
                                                            items={items}    
                                                            getOption={(item) => ({ value: item.Id, label: item.Name })}    
                                                            onChange={handleChange} 
                                                        />
                                                        
                                                            

Form Description

Form descriptions allow you to pass in a string, with the option to include an inline code descriptor or link. It has a particular format to allow for flexibility. You can include as many links or code descriptors as you need.

For example:


                                                        
                                                        
                                                             descriptionText`Set ${descriptionText.code("itemEnabled")} to true or see our ${descriptionText.link("guide", "/docs/guide")} for more details.`
                                                        
                                                            

The above code will render the following:

Adding a Popover to the label

This is used to display a Popover with more content explaining the context of a field. It should only be used when the Helper Text is not sufficient.

To include this you can pass the PopoverBasicHelp component to the popover prop, e.g.:


                                                        
                                                        
                                                            <MultiSelect
                                                            label="Input label"
                                                            value={selectedItemIds}
                                                            items={items}
                                                            popover={<PopoverBasicHelp placement="right-start" description="A popover" />}
                                                            getOption={(item) => ({ value: item.Id, label: item.Name, description: item.Description })}
                                                            onChange={handleChange} 
                                                        />
                                                        
                                                            

Filtering options

When the MultiSelect has many options, you may want to allow the user to filter them. You can enable the allowFilter prop to show a search input in the dropdown.

Showing a description for the options

A description can be shown under the label for each option. To do this, return a description property in the option returned by getOption.


                                                        
                                                        
                                                            <MultiSelect
                                                            label="Input label"
                                                            value={selectedItemIds}
                                                            items={items}
                                                            getOption={(item) => ({ value: item.Id, label: item.Name, description: item.Description })}
                                                            onChange={handleChange} 
                                                        />
                                                        
                                                            

Actions

MultiSelect accepts an optional actions prop which is an array of a maximum of two icon-only Button elements rendered inside the field, in the action area to the right of the dropdown chevron (separated by a divider).

They can be used for lightweight, field-scoped actions such as adding a related entity or revealing extra options.When setting up the button you should always use importance="ghost" and size="small" so they sit correctly within the field.

When the Select is backed by an async list that supports refreshing, a refresh button is shown automatically as the first item in the action area. This is separate from actions and does not count toward the two-action limit.

Sorting

By default the MultiSelect does not change the ordering of the items provided. If a specific ordering is required, this should be applied to the items before rendering the MultiSelect, for example:


                                                        
                                                        
                                                            const sortedItems = useMemo(() => alphanumericSort(items), [items]);
                                                        
                                                            

Displaying a validation message

To display a validation message, use the validationMessage prop, which will be displayed as an error. Other validation states are not supported for the MultiSelect.

Asynchronous data loading

When the number of items to be displayed is large, you may want to load the data in batches rather than all at once.

To do this, the items prop can be an AsyncList instead on an array. AsyncList is an interface which provides the MultiSelect with the currently loaded items, along with a way to load more items. When opening the MultiSelect, data will begin loading and scrolling to the end of the list will trigger additional items to be loaded.

In Portal, the useAsyncList hook can be used to create an AsyncList.

  • loadItems is a function that returns the items and must support a take parameter that controls how many items are loaded. If allowFiltering is enabled for the Select, loadItems must also handle the filter value.
  • getItemId must be a function that returns an identifier for each item that matches the value for the corresponding option.
  • name will be used by log events for the queries.
  • initialItems should be an array of any items that need to be available before any data has been loaded. For example, if the Select has an initial value, you'll want to display it regardless of the loading state of the AsyncList. This item should be loaded separately before the Select is shown, then made available in initialItems which will allow the Select to use it immediately.
  • initialQuery can be optionally provided if you want to start the loading of the data before the Select has been opened, for example in the page loader.
  • take can be optionally provided to control how many additional items to load each time a batch is requested.

                                                        
                                                        
                                                            const asyncList = useAsyncList({
                                                            loadItems: async (repository, take, filter) => getItems(repository, take, filter),
                                                            name: "Items",
                                                            getItemId: (item) => item.Id,
                                                            initialItems: [],
                                                         });
                                                        
                                                            

This can then be used when rendering the Select.


                                                        
                                                        
                                                            <MultiSelect
                                                            label="Input label"
                                                            value={selectedItemIds}
                                                            items={asyncList}
                                                            getOption={(item) => ({ value: item.Id, label: item.Name /> })}
                                                            onChange={handleChange} 
                                                        />
                                                        
                                                            

This will initiate the data loading when useAsyncList runs. If you want the loading to be started earlier (for example in a page loader), provide the initialQuery value. The createAsyncList function is also provided to simplify this. It returns a AsyncListConfig that can be passed to useAsyncList and can be run outside of a React component.


                                                        
                                                        
                                                            // Page loader
                                                        const asyncListConfig = createAsyncList(repository, {
                                                            loadItems: async (repository, take, filter) => getItems(repository, take, filter),
                                                            name: "Items",
                                                            getItemId: (item) => item.Id,
                                                            initialItems: [],
                                                         });
                                                        
                                                        // Component
                                                        const asyncList = useAsyncList(loaderData.asyncListConfig);
                                                        
                                                            

Migrating from a legacy MultiSelect

In Portal, many legacy MultiSelect usages are wrapped in a separate component typed to a specific resource. This is not required for the new MultiSelect and should be avoided. An explicit mapping for getOption should be provided.


                                                        
                                                        
                                                            // Legacy Select
                                                        <TeamMultiSelect 
                                                            label="Select teams" 
                                                            items={loaderData.teams} 
                                                            value={selectedTeams}
                                                            onChange={handleTeamChange} 
                                                        />
                                                        
                                                        // New Select
                                                        <MultiSelect 
                                                            label="Select teams"
                                                            items={loaderData.teams}
                                                            getOption={(team) => ({ value: team.Id, label: team.Name }))}
                                                            value={selectedTeams} 
                                                            onChange={handleTeamChange} 
                                                        />
                                                        
                                                            

The following props are no longer supported:

  • autoComplete: The MultiSelect is no longer a text input.
  • empty: The empty state is fixed and cannot be changed.
  • hideFloatingLabel: Labels are no longer floating
  • addNewTemplate, addNewOnBlur, onNew: Adding new items from the MultiSelect is no longer supported
  • renderItem: Custom rendering for items is not supported. Only label and description can be provided through getOption.
  • renderChip: Selected options are always rendered as a Tag.
  • accessibleName: Accessible name is computed from the required label.
  • disableFilterSelectedItems: Selected items are never filtered from the list.
  • disableCloseOnSelected: Dropdown always stays open after selection.
  • actionButtons
  • openOnFocus
  • multiSelectRef
  • helperText

If you run into an issue with these unsupported props, reach out to #team-frontend-foundations-requests to discuss your use case.