@highlight-ui/select
v10.6.5
Published
Select component for the Highlight UI library
Downloads
6,601
Maintainers
Keywords
Readme
@highlight-ui/select
Feature-rich select control.
Collects user information by displaying a list, triggered by click action, allowing single or multiple selection.
Features
- Supports selection of one or multiple options
- Asynchronous flow
- built-in search bar
- search term highlighted results
- optional search in sub labels
- dynamically generated options
- loader
- Option groups
- split multiple options my line separator and title
- Option types
- single-line
- colored
- multi-line
- avatar
- Option metadata
- built with TS generics
- Footer
- apply action
- cancel action
- Creatable option
- Support for i18n
- Controlled component
- Variants
- inline
- full-width
- 🆕 🧪 Early access features
- New search input
- New trigger
Installation
Using npm:
npm install @highlight-ui/select
Using yarn:
yarn add @highlight-ui/select
Using pnpm:
pnpm install @highlight-ui/select
Once the package is installed, you can import the library:
import { Select } from '@highlight-ui/select';
Usage
Single
import React from 'react';
import { Select, SelectOption } from '@highlight-ui/select';
export default function SingleSelectExample() {
const [selectedOptions, setSelectedOptions] = useState<SelectOption[]>([]);
return (
<Select
triggerLabel="Pick a name"
selectedOptions={selectedOptions}
onSelect={setSelectedOptions}
options={[
{ label: 'Genesis', value: '23095' },
{ label: 'Destinee', value: '26211' },
]}
/>
);
}
Multiple
import React from 'react';
import { Select, SelectOption } from '@highlight-ui/select';
export default function MultiSelectExample() {
const [selectedOptions, setSelectedOptions] = useState<SelectOption[]>([]);
return (
<Select
multiple
triggerLabel="Pick a name"
selectedOptions={selectedOptions}
onSelect={setSelectedOptions}
options={[
{ label: 'Genesis', value: '23095' },
{ label: 'Destinee', value: '26211' },
]}
/>
);
}
🆕 🧪 Early access features
The following sections highlight new features that can be enabled via props.
Search input
When the content is open, a search input will be visible if the searchable
prop is set to true
and the number of options is equal or above the number as provided through the minOptionsToDisplaySearch
prop.
By default enableNewSearchInput
will be false
. Below is a comparison of what it renders, based on its value.
| enableNewSearchInput
| Component | Comments |
| :--------------------- | :------------------- | :----------------------------------------------------------------------------------------- |
| false
| DefaultSearchInput
| Wrapper with role="search"
containing a <label>
which groups an <Icon>
and <input>
|
| true
| SearchInput
| Uses the <TextInput>
component with an <Icon>
as prefix |
Trigger
By default enableFlowTriggers
will be false
. Below is a comparison of what it renders, based on its value.
| enableFlowTriggers
| multiple
| Component | Comments |
| :------------------- | :--------- | :----------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------- |
| false
| false
| DefaultTrigger
or Custom renderTrigger()
| <button>
showing the selected option (by default) or a custom element |
| false
| true
| DefaultSearchInput
or Custom renderTrigger()
| <button>
showing the selected options count and clear icon (by default) or a custom element |
| true
| false
| InputTrigger
| <InputContainer>
component with an <IconButton>
as suffix, showing the selected option |
| true
| true
| MultiInputTrigger
| <InputContainer>
component with an <IconButton>
as suffix, showing the selected options as <SelectChip>
s and clear icon |
Advanced usage
Asynchronous flow
This example focus on loading options asynchronously.
In real-world example, in the onListVisibilityChange
handler hardcoded options would be replaced by API call.
import React from 'react';
import { Select, SelectOption } from '@highlight-ui/select';
const initialOptions: SelectOption[] = [
{ label: 'Genesis', value: '23095' },
{ label: 'Destinee', value: '26211' },
{ label: 'Chyna', value: '86910' },
{ label: 'Alexie', value: '49249' },
{ label: 'Natalie', value: '18694' },
];
export default function AsyncExample() {
const [selectedOptions, setSelectedOptions] = useState<SelectOption[]>([]);
const [options, setOptions] = useState([]);
const [isLoading, setIsLoading] = useState(false);
return (
<Select
triggerLabel="Pick a name"
selectedOptions={selectedOptions}
onSelect={setSelectedOptions}
searchable
isLoading={isLoading}
minOptionsToDisplaySearch={1}
onListVisibilityChange={(visible) => {
if (visible) {
setIsLoading(true);
// mocks API call. Should be replaced with network request
setTimeout(() => {
setOptions(initialOptions);
setIsLoading(false);
}, 3000);
}
}}
options={options}
/>
);
}
Option groups
Option groups provide a way to visually group multiple options. Groups are determined by specifying a group title in each option.
import React from 'react';
import { Select, SelectOption } from '@highlight-ui/select';
export default function OptionGroupsExample() {
const [selectedOptions, setSelectedOptions] = useState<SelectOption[]>([]);
return (
<Select
triggerLabel="Pick a name"
selectedOptions={selectedOptions}
onSelect={setSelectedOptions}
options={[
{ label: 'Genesis', value: '23095', group: 'One' },
{ label: 'Destinee', value: '26211', group: 'Two' },
]}
/>
);
}
Option types
Four different option types are supported:
- single-line
- colored
- multi-line
- avatar
Option types are determined by specifying optionType
prop.
Additionally, each option type has its own props which are specified in each option.
import React from 'react';
import { Select, SelectOption } from '@highlight-ui/select';
export default function OptionTypesExample() {
const [selectedOptions, setSelectedOptions] = useState<SelectOption[]>([]);
return (
<Select
triggerLabel="Pick a name"
selectedOptions={selectedOptions}
onSelect={setSelectedOptions}
optionType="colored"
options={[
{ label: 'Genesis', value: '23095', color: 'red' },
{ label: 'Destinee', value: '26211', color: 'green' },
]}
/>
);
}
In addition to the label
, value
and group
, these props are used to define option type:
| Prop | Type | Option type | Description |
| :------------- | :------------------ | :-------------------- | :------------------------------------------------------ |
| disabled
| boolean
| all | Should the option be disabled or enabled |
| metadata
| ComponentMetadata
| all | Object used for testing. Contains testId and actionName |
| subLabel
| string
| multi-line and avatar | Smaller, descriptive text used below main label |
| avatar
| string
| avatar | URL used to fetch avatar image |
| imageLoading
| eager
or lazy
| avatar | Specifies how browser will load avatar image |
Creatable option
Select has a support for options dynamically created by user.
import React from 'react';
import { Select, SelectOption } from '@highlight-ui/select';
export default function CreatableOptionExample() {
const [selectedOptions, setSelectedOptions] = useState<SelectOption[]>([]);
const [options, setOptions] = useState([
{
label: 'Genesis',
value: '23095',
},
]);
return (
<Select
triggerLabel="Pick a name"
selectedOptions={selectedOptions}
onSelect={setSelectedOptions}
creatableOptions={{
createButtonProps: {
label: 'Add',
loading: false,
},
inputProps: {
placeholder: 'Create a new one',
},
onCreate: (item) => {
setOptions([...options, { label: item.value, value: item.value }]);
},
}}
options={options}
/>
);
}
⚠️ Submit footer cannot be used alongside creatable options.
CreatableOptionsProps
| Prop | Type | Required | Default | Description |
| :--------------------------- | :----------------------------------- | :------- | :------ | :------------------------------------------------------ |
| createButtonProps.label
| string
| Yes | | Text used in the submit button |
| createButtonProps.loading
| boolean
| No | false
| Defines if the submit button will be in loading state |
| createButtonProps.metadata
| ComponentMetadata
| No | null
| Object used for testing. Contains testId and actionName |
| inputProps.placeholder
| string
| No | null
| Placeholder used in search bar |
| inputProps.metadata
| ComponentMetadata
| No | null
| Object used for testing. Contains testId and actionName |
| onCreate
| function(item: SelectOption): void
| Yes | null
| Function called whenever new option is created |
Submit footer
Select has a support for submit footer.It provides distinction between selected and submitted options. This means that changes in selected options can be discarded.
import React from 'react';
import { Select, SelectOption } from '@highlight-ui/select';
export default function SubmitFooterExample() {
const [selectedOptions, setSelectedOptions] = useState<SelectOption[]>([]);
const [submittedOptions, setSubmittedOptions] = useState<SelectOption[]>([]);
return (
<Select
triggerLabel="Pick a name"
selectedOptions={selectedOptions}
onSelect={setSelectedOptions}
multiple
options={[
{ label: 'Genesis', value: '23095' },
{ label: 'Destinee', value: '26211' },
]}
submitOptions={{
onCancel: () => setSelectedOptions(submittedOptions),
onSubmit: (options) => setSubmittedOptions(options),
}}
/>
);
}
⚠ Creatable options cannot be used alongside submit footer.
SubmitOptions
| Prop | Type | Required | Default | Description |
| :-------------------------- | :---------------------------------------- | :------- | :------- | ----------------------------------------------------- |
| submitOptions.onSubmit
| function(options: SelectOption[]): void
| Yes | | Function called whenever the submit button is pressed |
| submitOptions.onCancel
| function(): void
| Yes | | Function called whenever the cancel button is pressed |
| submitOptions.submitLabel
| string
| No | 'Apply' | Label used in the submit button |
| submitOptions.cancelLabel
| string
| No | 'Cancel' | Label used in the cancel button |
Props 📜
ℹ️ Whenever SelectOption is used, there is an option to extend this interface and provide your own metadata.
| Prop | Type | Required | Default | Description |
| :-------------------------- | :------------------------------------------------------------------ | :------- | :------------------- | :--------------------------------------------------------------------------------------------------------- |
| options
| SelectOption[]
| Yes | | List of objects |
| onSelect
| function(values: SelectOption[]): void
| Yes | | Gets called each time an option is selected |
| selectedOptions
| SelectOption[]
| Yes | | List of selected options |
| className
| string
| No | null
| Class name to apply to the element |
| listClassName
| string
| No | null
| Class name to apply to the List element |
| closeOnSelect
| boolean
| No | false
| Should the options list disappear by each selection? |
| disabled
| boolean
| No | false
| Should trigger be enabled or disabled |
| selectAllLabel
| string
| No | 'Select all' | Label for Select all
functionality when multiple
prop is true
|
| metadata
| ComponentMetadata
| No | null
| Object used for testing. Contains testId and actionName |
| onSearchChange
| function(search: string): void
| No | null
| Gets called whenever text is typed in search bar |
| onListVisibilityChange
| function(visible: boolean): void
| No | null
| Gets called whenever the visibility of the select's list has changed |
| emptyStateLabel
| string
| No | 'Nothing to display' | Label for displaying the empty state of the Select list |
| renderEmptyState
| function(props: SelectEmptyStateProps): ReactNode
| No | null
| Function to return custom empty options list message |
| renderTrigger
| function(props: SelectTriggerProps & ref: RefCallback): ReactNode
| No | null
| Function to return a custom trigger |
| optionType
| single-line or colored or multi-line or avatar
| No | 'single-line' | String specifying the option type that should be rendered. |
| variant
| inline or full-width
| No | 'inline' | inline
when used outside forms and full-width
when used inside forms |
| searchable
| boolean
| No | true
| Determines whether or not a search input should be rendered in the select list |
| searchAutofocus
| boolean
| No | true
| Whether or not the search field should get the focus automatically each time the list opens up |
| searchInitialValue
| string
| No | null
| Initial search field value |
| searchNoResultsLabel
| string
| No | 'No results for' | No search results message |
| searchPlaceholder
| string
| No | 'Search' | No search results message |
| minOptionsToDisplaySearch
| number
| No | 8 | If Select is searchable search input will render if the number of options is more or equal to this number. |
| triggerLabel
| string
| No | null
| Label being shown by default on the trigger button |
| outline
| default or warning or error
| No | 'default' | String specifying the outline that should be rendered |
| autoFocus
| boolean
| No | false
| Automatically focus the Select's trigger on mount |
| triggerId
| string
| No | null
| HTML id attribute asssigned to trigger component |
| creatableOptions
| CreatableOptionsProps
| No | null
| Options used for creatable option feature |
| isClearable
| boolean
| No | false
| Defines wether to render the clearing x button |
| highlightedText
| string
| No | null
| Highlights this text in the options list only when the Select is not searchable. |
| isLoading
| boolean
| No | false
| Display the loading state of the list |
| submitOptions
| SubmitOptions
| No | null
| Options used for the submit button |
| enableFlowTriggers
| boolean
| No | false
| Allow toggling between original and new trigger |
| enableNewSearchInput
| boolean
| No | false
| Allow toggling between original and new search input |
| limitTwoRows
| boolean
| No | true
| Limit selected chips to only display two rows. Only applicable to multi-select. |
| enableSubLabelSearch
| boolean
| No | false
| Search in subLabel text when optionType
is multi-line
|
SelectEmptyStateProps
| Prop | Type | Required | Default | Description |
| :------ | :------- | :------- | :------ | ------------------------ |
| label
| string
| No | null
| Label shown in the empty |
Accessibility ♿️
Select tries to mimic native HTML select a11y features as best as possible.
This is well-known to be a notoriously hard job There are some smaller deviation to native select's behaviour:
- Keyboard navigation
- using keyboard letters won't focus on list options
Testing
This example serves as starting point on how you can use Select
component as part of your tests.
Some of the props are already defined in the SelectWrapper
since they are mandatory but they can be overriden by sending props to the renderSelectWrapper
function.
import React, { useState } from 'react';
import { render } from '@testing-library/react';
import { Select, SelectOption } from '@highlight-ui/select';
type Optional<T, K extends keyof T> = Omit<T, K> & Partial<T>;
type SelectWrapperProps = Optional<
SelectProps<SelectOption>,
'selectedOptions' | 'onSelect' | 'options'
>;
function SelectWrapper(props: SelectWrapperProps) {
const [selectedOptions, setSelectedOptions] = useState<SelectOption[]>([]);
return (
<Select
selectedOptions={selectedOptions}
onSelect={setSelectedOptions}
options={[
{ label: 'Genesis', value: '23095', color: 'red' },
{ label: 'Destinee', value: '26211', color: 'green' },
]}
{...props}
/>
);
}
describe('SelectTest', () => {
const renderSelectWrapper = (props: SelectWrapperProps) => {
return render(<SelectWrapper {...props} />);
};
it('test description', () => {
renderSelectWrapper({});
// write your expect here
});
});
Place in design system 💻
Select
component's flexibility is used to implement a number of different components:
For picking a color from a color palette, color-picker should be used.
For picking unselectable menu item which will open URL or call custom callback, look at the dropdown-menu
Contributing 🖌️
Please visit personio.design.
If you're interested in contributing, please visit our contribution page.