import { ArgTypes } from '@storybook/blocks';
import { Select, AsyncSelect } from './Select';

# Select

Select is the base for every component on this page. The approaches mentioned here are also applicable to `AsyncSelect`, `MultiSelect`, `AsyncMultiSelect`.

## When to use

This component is particularly useful when you have a large number of options to choose from. Additionally, consider using Select in favor of radio button components when you have more than 4 options to choose from.

## Select variants

Select is an input with the ability to search and create new values. It should be used when you have a list of options. If the data has a tree structure, consider using `Cascader` instead.
Select has some features:

- Search a list of values
- Select multiple values
- Select from async data
- Create custom values that aren't in the list

### Options format

There are four properties for each option:

- `label` - Text that is visible in the menu.
- `value` - Could be anything, but is usually a string. Used to identify what is **actually** selected.
- `description` - Longer description that describes the choice. Use this sparingly.
- `imgUrl` - URL to an image. Use this when an image or icon provides more context for the option.

```jsx
const options = [
  { label: 'Basic option', value: 0 },
  { label: 'Option with description', value: 1, description: 'this is a description' },
  {
    label: 'Option with description and image',
    value: 2,
    description: 'This is a very elaborate description, describing all the wonders in the world.',
    imgUrl: 'https://placekitten.com/40/40',
  },
];
```

### Creatable option

Creatable option is used when you want to be able to add a custom value to the list of options. `allowCustomValue` needs to be true and you must handle the value creation with `onCreateOption`.

```jsx
import { Select } from "@grafana/ui";

const SelectComponent = () => {
  const [value, setValue] = useState<SelectableValue<number>>();

  return (
    <Select
      options={option}
      value={value}
      allowCustomValue
      onCreateOption={customValue => {
        setValue(customValue);
      }}
    />
  );
};
```

### Resetting selected value from outside the component

If you want to reset the selected value from outside the component, e.g. if there are two Select components that should be in sync, you can set the dependent Select value to `null` in the `onChange` handler of the first Select component.

```tsx
import { useState } from 'react';
import { Select } from '@grafana/ui';

const SelectComponent = () => {
  const [person, setPerson] = useState<string | undefined>('');
  const [team, setTeam] = useState<string | undefined | null>('');

  return (
    <form>
      <Select
        onChange={({ value }) => {
          setPerson(value);
          setTeam(null); // Setting the team to null will reset the selected value in the team Select
        }}
        options={[
          {
            value: 'option1',
            label: 'Option 1',
          },
          {
            value: 'option2',
            label: 'Option 2',
          },
        ]}
        value={person}
        backspaceRemovesValue
      />
      <Select
        onChange={({ value }) => setTeam(value)}
        options={[
          {
            value: 'team1',
            label: 'Team 1',
          },
          {
            value: 'team',
            label: 'Team 2',
          },
        ]}
        value={team}
      />
    </form>
  );
};
```

## AsyncSelect

Like regular Select, but handles fetching options asynchronously. Use the `loadOptions` prop for the async function that loads the options. If `defaultOptions` is set to `true`, `loadOptions` will be called when the component is mounted.

```jsx

import { AsyncSelect } from '@grafana/ui';

const basicSelectAsync = () => {
  const [value, setValue] = useState<SelectableValue<string>>();

  return (
    <AsyncSelect
      loadOptions={loadAsyncOptions}
      defaultOptions
      value={value}
      onChange={v => {
        setValue(v);
      }}
    />
  );
};
```

Where the async function could look like this:

```tsx
const loadAsyncOptions = () => {
  return new Promise<Array<SelectableValue<string>>>((resolve) => {
    setTimeout(() => {
      resolve(options);
    }, 2000);
  });
};
```

## MultiSelect

Possible to Select multiple values at the same time.

```tsx
import { MultiSelect } from '@grafana/ui';

const multiSelect = () => {
  const [value, setValue] = useState<Array<SelectableValue<string>>>([]);

  return (
    <>
      <MultiSelect
        options={options}
        value={value}
        onChange={(v) => {
          setValue(v);
        }}
      />
    </>
  );
};
```

## AsyncMultiSelect

Like MultiSelect but handles data asynchronously with the `loadOptions` prop.

## Virtualized option list

The Select component can be configured to use a virtualized option list. The `virtualized` prop must be set to true to enable this behavior. This is useful when you have a large number of options, as it improves performance by only rendering the options that are currently visible in the dropdown.

## Adding icons to Select

A custom icon can be added to Select, which will be displayed before the selected value, by using the `prefix` prop.

```tsx
<Select options={options} value={value} onChange={setValue} prefix={<Icon name="arrow-down" />} />
```

## Testing

Using React Testing Library, you can select the `<Select />` using its matching label, such as the label assigned with the `inputId` prop. Use the `react-select-event` package to select values from the options.

```tsx
import { render, screen } from '@testing-library/react';
import selectEvent from 'react-select-event';
import { Select } from '@grafana/ui';

it('should call onChange', () => {
  const onChange = jest.fn();

  render(
    <>
      <label htmlFor="my-select">My select</label>
      <Select onChange={onChange} options={options} inputId="my-select" />
    </>
  );

  const selectEl = screen.getByLabelText('My select');
  expect(selectEl).toBeInTheDocument();

  await selectEvent.select(selectEl, 'Option 2', { container: document.body });
  expect(onChange).toHaveBeenCalledWith({
    label: 'Option 2',
    value: 2,
  });
});
```

## Props

<ArgTypes of={Select} />
