10.6.1 โ€ข Published 2 months ago

@highlight-ui/select v10.6.1

Weekly downloads
-
License
MIT
Repository
-
Last release
2 months ago

npm personio.design storybook.personio.design

@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.

enableNewSearchInputComponentComments
falseDefaultSearchInputWrapper with role="search" containing a <label> which groups an <Icon> and <input>
trueSearchInputUses 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.

enableFlowTriggersmultipleComponentComments
falsefalseDefaultTrigger or Custom renderTrigger()<button> showing the selected option (by default) or a custom element
falsetrueDefaultSearchInput or Custom renderTrigger()<button> showing the selected options count and clear icon (by default) or a custom element
truefalseInputTrigger<InputContainer> component with an <IconButton> as suffix, showing the selected option
truetrueMultiInputTrigger<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:

PropTypeOption typeDescription
disabledbooleanallShould the option be disabled or enabled
metadataComponentMetadataallObject used for testing. Contains testId and actionName
subLabelstringmulti-line and avatarSmaller, descriptive text used below main label
avatarstringavatarURL used to fetch avatar image
imageLoadingeager or lazyavatarSpecifies 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

PropTypeRequiredDefaultDescription
createButtonProps.labelstringYesText used in the submit button
createButtonProps.loadingbooleanNofalseDefines if the submit button will be in loading state
createButtonProps.metadataComponentMetadataNonullObject used for testing. Contains testId and actionName
inputProps.placeholderstringNonullPlaceholder used in search bar
inputProps.metadataComponentMetadataNonullObject used for testing. Contains testId and actionName
onCreatefunction(item: SelectOption): voidYesnullFunction 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

PropTypeRequiredDefaultDescription
submitOptions.onSubmitfunction(options: SelectOption[]): voidYesFunction called whenever the submit button is pressed
submitOptions.onCancelfunction(): voidYesFunction called whenever the cancel button is pressed
submitOptions.submitLabelstringNo'Apply'Label used in the submit button
submitOptions.cancelLabelstringNo'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.

PropTypeRequiredDefaultDescription
optionsSelectOption[]YesList of objects
onSelectfunction(values: SelectOption[]): voidYesGets called each time an option is selected
selectedOptionsSelectOption[]YesList of selected options
classNamestringNonullClass name to apply to the element
listClassNamestringNonullClass name to apply to the List element
closeOnSelectbooleanNofalseShould the options list disappear by each selection?
disabledbooleanNofalseShould trigger be enabled or disabled
selectAllLabelstringNo'Select all'Label for Select all functionality when multiple prop is true
metadataComponentMetadataNonullObject used for testing. Contains testId and actionName
onSearchChangefunction(search: string): voidNonullGets called whenever text is typed in search bar
onListVisibilityChangefunction(visible: boolean): voidNonullGets called whenever the visibility of the select's list has changed
emptyStateLabelstringNo'Nothing to display'Label for displaying the empty state of the Select list
renderEmptyStatefunction(props: SelectEmptyStateProps): ReactNodeNonullFunction to return custom empty options list message
renderTriggerfunction(props: SelectTriggerProps & ref: RefCallback): ReactNodeNonullFunction to return a custom trigger
optionTypesingle-line or colored or multi-line or avatarNo'single-line'String specifying the option type that should be rendered.
variantinline or full-widthNo'inline'inline when used outside forms and full-width when used inside forms
searchablebooleanNotrueDetermines whether or not a search input should be rendered in the select list
searchAutofocusbooleanNotrueWhether or not the search field should get the focus automatically each time the list opens up
searchInitialValuestringNonullInitial search field value
searchNoResultsLabelstringNo'No results for'No search results message
searchPlaceholderstringNo'Search'No search results message
minOptionsToDisplaySearchnumberNo8If Select is searchable search input will render if the number of options is more or equal to this number.
triggerLabelstringNonullLabel being shown by default on the trigger button
outlinedefault or warning or errorNo'default'String specifying the outline that should be rendered
autoFocusbooleanNofalseAutomatically focus the Select's trigger on mount
triggerIdstringNonullHTML id attribute asssigned to trigger component
creatableOptionsCreatableOptionsPropsNonullOptions used for creatable option feature
isClearablebooleanNofalseDefines wether to render the clearing x button
highlightedTextstringNonullHighlights this text in the options list only when the Select is not searchable.
isLoadingbooleanNofalseDisplay the loading state of the list
submitOptionsSubmitOptionsNonullOptions used for the submit button
enableFlowTriggersbooleanNofalseAllow toggling between original and new trigger
enableNewSearchInputbooleanNofalseAllow toggling between original and new search input
limitTwoRowsbooleanNotrueLimit selected chips to only display two rows. Only applicable to multi-select.
enableSubLabelSearchbooleanNofalseSearch in subLabel text when optionType is multi-line

SelectEmptyStateProps

PropTypeRequiredDefaultDescription
labelstringNonullLabel 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.

10.6.1

2 months ago

10.6.0

2 months ago

10.5.0

3 months ago

10.4.5

3 months ago

10.4.4

4 months ago

10.4.3

5 months ago

10.4.2

5 months ago

10.4.1

5 months ago

10.0.8

10 months ago

10.0.9

10 months ago

10.0.13

10 months ago

10.0.12

10 months ago

10.0.11

10 months ago

10.0.10

10 months ago

10.0.18

10 months ago

10.0.17

10 months ago

10.0.16

10 months ago

10.0.15

10 months ago

10.0.14

10 months ago

10.2.3

9 months ago

10.2.4

9 months ago

10.2.5

9 months ago

10.2.6

9 months ago

10.2.7

9 months ago

10.2.8

9 months ago

10.2.9

9 months ago

10.2.21

7 months ago

10.2.0

9 months ago

10.2.1

9 months ago

10.2.20

7 months ago

10.2.2

9 months ago

10.2.14

8 months ago

10.2.15

8 months ago

10.2.12

9 months ago

10.2.13

8 months ago

10.2.10

9 months ago

10.2.11

9 months ago

10.2.18

7 months ago

10.2.19

7 months ago

10.2.16

8 months ago

10.2.17

8 months ago

10.1.0

10 months ago

10.1.1

10 months ago

10.1.2

10 months ago

10.4.0

6 months ago

10.3.2

7 months ago

10.3.3

7 months ago

10.3.0

7 months ago

10.3.1

7 months ago

10.3.0-r18.0

7 months ago

10.0.5

11 months ago

10.0.6

11 months ago

10.0.7

11 months ago

10.0.0

11 months ago

10.0.1

11 months ago

10.0.2

11 months ago

10.0.3

11 months ago

10.0.4

11 months ago

9.5.15

11 months ago

9.5.16

11 months ago

9.5.13

12 months ago

9.5.14

11 months ago

9.5.11

12 months ago

9.5.12

12 months ago

9.5.10

12 months ago

9.5.5

1 year ago

9.5.9

12 months ago

9.5.8

12 months ago

9.5.7

12 months ago

9.5.6

12 months ago

9.4.15

1 year ago

9.4.14

1 year ago

9.4.13

1 year ago

9.4.12

1 year ago

9.4.19

1 year ago

9.4.18

1 year ago

9.4.17

1 year ago

9.4.16

1 year ago

9.4.11

1 year ago

9.4.10

1 year ago

9.4.21

1 year ago

9.4.20

1 year ago

9.4.6

1 year ago

9.4.5

1 year ago

9.4.4

1 year ago

9.4.3

1 year ago

9.4.2

1 year ago

9.4.1

1 year ago

9.4.0

1 year ago

9.4.9

1 year ago

9.4.8

1 year ago

9.4.7

1 year ago

9.5.4

1 year ago

9.5.3

1 year ago

9.5.2

1 year ago

9.5.1

1 year ago

9.5.0

1 year ago

9.2.4

1 year ago

9.2.3

1 year ago

9.2.2

1 year ago

9.2.1

1 year ago

9.1.13

1 year ago

9.1.14

1 year ago

9.1.12

1 year ago

9.3.2

1 year ago

9.3.1

1 year ago

9.3.0

1 year ago

9.2.0

1 year ago

9.0.6

1 year ago

9.0.5

1 year ago

9.0.4

1 year ago

9.0.3

1 year ago

8.0.38

1 year ago

8.2.3

1 year ago

8.2.2

1 year ago

8.0.41

1 year ago

8.0.40

1 year ago

8.1.0

1 year ago

8.1.2

1 year ago

8.1.1

1 year ago

9.1.9

1 year ago

9.1.8

1 year ago

9.1.7

1 year ago

9.1.6

1 year ago

9.1.5

1 year ago

9.1.4

1 year ago

9.1.3

1 year ago

9.1.2

1 year ago

8.3.5

1 year ago

8.3.2

1 year ago

8.3.1

1 year ago

8.3.4

1 year ago

8.3.3

1 year ago

8.2.1

1 year ago

8.2.0

1 year ago

9.0.2

1 year ago

9.0.1

1 year ago

9.0.0

1 year ago

9.1.10

1 year ago

9.1.11

1 year ago

8.4.5

1 year ago

8.4.4

1 year ago

8.4.7

1 year ago

8.4.6

1 year ago

8.4.1

1 year ago

8.4.0

1 year ago

8.4.3

1 year ago

8.4.2

1 year ago

8.4.9

1 year ago

8.4.8

1 year ago

8.3.0

1 year ago

9.1.1

1 year ago

9.1.0

1 year ago

8.1.3

1 year ago

8.0.30

2 years ago

8.0.32

2 years ago

8.0.31

2 years ago

8.0.33

2 years ago

8.0.35

2 years ago

8.0.37

2 years ago

8.0.27

2 years ago

8.0.26

2 years ago

8.0.29

2 years ago

8.0.28

2 years ago

8.0.19

2 years ago

8.0.21

2 years ago

8.0.20

2 years ago

8.0.23

2 years ago

8.0.22

2 years ago

8.0.25

2 years ago

8.0.24

2 years ago

8.0.9

2 years ago

8.0.8

2 years ago

8.0.5

2 years ago

8.0.4

2 years ago

8.0.7

2 years ago

8.0.6

2 years ago

8.0.10

2 years ago

8.0.11

2 years ago

8.0.14

2 years ago

8.0.13

2 years ago

8.0.16

2 years ago

8.0.15

2 years ago

8.0.18

2 years ago

8.0.17

2 years ago

7.0.0

2 years ago

8.0.1

2 years ago

8.0.0

2 years ago

8.0.3

2 years ago

8.0.2

2 years ago

6.3.4

2 years ago

7.0.0-alpha.1

2 years ago

7.0.0-alpha.0

2 years ago

6.3.2

2 years ago

6.3.1

2 years ago

6.3.3

2 years ago

6.3.0

2 years ago

6.1.19

2 years ago

6.2.0

2 years ago

6.1.20

2 years ago

6.1.21

2 years ago

6.1.17

2 years ago

6.1.16

2 years ago

6.1.15

2 years ago

6.1.14

2 years ago

6.1.18

2 years ago

6.1.13

2 years ago

6.1.12

3 years ago

6.1.11

3 years ago

6.1.10

3 years ago

6.1.9

3 years ago

6.1.6

3 years ago

6.1.8

3 years ago

6.1.7

3 years ago

6.1.5

3 years ago

6.1.2

3 years ago

6.1.4

3 years ago

6.1.3

3 years ago

6.1.0

3 years ago

6.1.1

3 years ago

6.0.1

3 years ago

6.0.2

3 years ago

6.0.0

3 years ago

5.0.1

3 years ago

5.0.0

3 years ago

4.1.0

3 years ago

4.0.3

3 years ago

4.0.2

3 years ago

4.0.1

3 years ago

3.2.1

3 years ago

3.2.0

3 years ago

3.1.0

3 years ago

3.0.1

3 years ago

4.0.0

3 years ago

2.0.11

3 years ago

2.0.12

3 years ago

3.0.0

3 years ago

2.0.10

3 years ago

2.0.9

3 years ago

2.0.7

3 years ago

2.0.8

3 years ago

2.0.6

3 years ago

2.0.5

3 years ago

2.0.4

3 years ago

2.0.3

3 years ago

2.0.2

3 years ago

2.0.1

3 years ago

1.1.7

3 years ago

1.1.6

3 years ago

1.1.5

3 years ago

1.1.4

3 years ago

1.1.3

3 years ago

2.0.0

3 years ago

1.1.2

3 years ago

1.1.1

3 years ago

1.1.0

3 years ago

1.0.9

3 years ago

1.0.8

3 years ago

1.0.7

3 years ago

1.0.6

3 years ago

1.0.5

3 years ago

1.0.4

3 years ago

1.0.3

3 years ago

1.0.2

3 years ago

1.0.1

3 years ago