@highlight-ui/select
TypeScript icon, indicating that this package has built-in type declarations

10.6.9 • Public • Published

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.

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.

/@highlight-ui/select/

    Package Sidebar

    Install

    npm i @highlight-ui/select

    Weekly Downloads

    1,653

    Version

    10.6.9

    License

    MIT

    Unpacked Size

    1.94 MB

    Total Files

    101

    Last publish

    Collaborators

    • riain-personio
    • yunxi-yang
    • andresfrompersonio
    • kbpersonio
    • jordan-personio
    • amadeofrompersonio
    • cusero
    • mislav_lukac_personio_ext
    • ante.zebic
    • personio-npm-ci