import { ArgTypes, Canvas, Meta } from '@storybook/addon-docs/blocks';
import * as ComboBoxStories from './ComboBox.stories.js';
import ComboBox from '../ComboBox';
import { stackblitzPrefillConfig } from '../../../previewer/codePreviewer';

<Meta isTemplate />

# ComboBox

[Source code](https://github.com/carbon-design-system/carbon/tree/main/packages/react/src/components/ComboBox)
&nbsp;|&nbsp;
[Usage guidelines](https://www.carbondesignsystem.com/components/dropdown/usage#combo-box)
&nbsp;|&nbsp;
[Accessibility](https://www.carbondesignsystem.com/components/dropdown/accessibility)

{/* <!-- START doctoc generated TOC please keep comment here to allow auto update --> <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> */}

## Table of Contents

- [Overview](#overview)
- [Disabled](#disabled)
- [Combobox uses Downshift](#combobox-uses-downshift)
  - [`downshiftProps`](#downshiftprops)
- [typeahead Prop](#typeahead-prop)
  - [Autocomplete with typeahead](#autocomplete-with-typeahead)
  - [Combobox `downshiftActions`](#combobox-downshiftactions)
    - [`downshiftActions` Usage](#downshiftactions-usage)
- [Placeholders and Labeling](#placeholders-and-labeling)
- [`initialSelectedItem`](#initialselecteditem)
- [`selectedItem`](#selecteditem)
- [`itemToElement`](#itemtoelement)
- [`itemToString`](#itemtostring)
- [`shouldFilterItem`](#shouldfilteritem)
- [`allowCustomValue`](#allowcustomvalue)
- [`inputProps`](#inputprops)
  - [Using `maxLength`](#using-maxlength)
- [With AI Label](#with-ai-label)
- [With Layer](#with-layer)
- [Component API](#component-api)
- [Feedback](#feedback)

{/* <!-- END doctoc generated TOC please keep comment here to allow auto update --> */}

## Overview

A combobox allows the user to make a selection from a predefined list of options
and is typically used when there are a large amount of options to choose from.

<Canvas
  of={ComboBoxStories.Default}
  additionalActions={[
    {
      title: 'Open in Stackblitz',
      onClick: () => stackblitzPrefillConfig(ComboBoxStories.Default),
    },
  ]}
/>

## Disabled

A disabled combobox is available but should not be used as the sole means of
conveying information. For example, if the user must complete a previous form
input before moving on to the combobox, make sure to make that clear to the user
via an error state on the previous form element in addition to disabling the
next element.

```jsx
<Combobox disabled />
```

## Combobox uses Downshift

Our `Combobox` component utilizes [Downshift](https://www.downshift-js.com/)
under the hood to help provide complex yet accessible custom dropdown
components.

### `downshiftProps`

`downshiftProps` is made available as a passthrough to the underlying downshift
`useCombobox` hook.

**Use with caution:** anything you define here overrides the components'
internal handling of that prop. Downshift internals are subject to change, and
in some cases they can not be shimmed to shield you from potentially breaking
changes.

For more information, checkout the Downshift `useCombobox` props
[documentation](https://github.com/downshift-js/downshift/tree/v9.0.7/src/hooks/useCombobox#basic-props)

## typeahead Prop

The `typeahead` prop enables predictive text and autocomplete functionality in
the ComboBox. When enabled, it displays inline suggestions as you type, using a
built-in prefix-matching filter that ignores the `shouldFilterItem` prop.
Pressing `Tab` will complete the input with the first matching suggestion, while
arrow keys can still be used to navigate through filtered options.

When combined with the `allowCustomValue` prop, the ComboBox supports both
custom values and typeahead suggestions.

### Autocomplete with typeahead

<Canvas
  of={ComboBoxStories.AutocompleteWithTypeahead}
  additionalActions={[
    {
      title: 'Open in Stackblitz',
      onClick: () => stackblitzPrefillConfig(ComboBoxStories.AutocompleteWithTypeahead),
    },
  ]}
/>

### Combobox `downshiftActions`

The downshift action methods are made available through the `downshiftActions`
prop. While not recommended, this prop allows you to modify downshift's internal
state without having to fully control the component.

**Use with caution:** calling these actions modifies the internal state of
downshift. It may conflict with or override the state management used within
Combobox. Downshift APIs and internals are subject to change, and in some cases
they can not be shimmed by Carbon to shield you from potentially breaking
changes.

#### `downshiftActions` Usage

Provide a ref that will be mutated to contain an object of downshift action
functions. These can be called to change the internal state of the downshift
useCombobox hook.

```
const downshiftActions = useRef();

return (
  <ComboBox
    ...
    downshiftActions={downshiftActions}
    downshiftProps={{
      onStateChange: (changes) => {
        if (changes.selectedItem === null) {
          downshiftActions?.current?.openMenu?.();
          return;
        }
      },
    }}
  />
);
```

For more information, checkout the Downshift `useCombobox` action functions
[documentation](https://github.com/downshift-js/downshift/tree/v9.0.7/src/hooks/useCombobox#actions)

## Placeholders and Labeling

The placeholder is not a replacement for a label under any circumstances
including space restraints. Placeholders should be used to provide additive
information regarding the format of the input. In all cases a label is required
_in addition to_ a placeholder.

## `initialSelectedItem`

If you want the Combobox to render with a value already selected, but do not
want to fully control the component, you can use `initialSelectedItem`

```jsx
const items = ['Option 1', 'Option 2', 'Option 3']

<Combobox initialSelectedItem={items[2]} onChange={() => {}} />
```

## `selectedItem`

You can use `selectedItem` for a fully controlled component.

<Canvas
  of={ComboBoxStories.Controlled}
  additionalActions={[
    {
      title: 'Open in Stackblitz',
      onClick: () => stackblitzPrefillConfig(ComboBoxStories.Controlled),
    },
  ]}
/>

```jsx
const options = [
  {
    id: 'option-1',
    text: 'Option 1',
  },
  {
    id: 'option-2',
    text: 'Option 2',
  },
  {
    id: 'option-3',
    text: 'Option 3',
  },
];
const [value, setValue] = useState(options[0]);

const onChange = ({ selectedItem }) => {
  setValue(selectedItem);
};

return (
  <div>
    <ComboBox
      onChange={onChange}
      id="carbon-combobox"
      items={options}
      selectedItem={value}
      itemToString={(item) => (item ? item.text : '')}
      titleText="Label"
      helperText="Helper text"
    />
    <div
      style={{
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'space-between',
      }}>
      <Button onClick={() => setValue(null)}>Reset</Button>
      <Button onClick={() => setValue(options[0])}>Option 1</Button>
      <Button onClick={() => setValue(options[1])}>Option 2</Button>
      <Button onClick={() => setValue(options[2])}>Option 3</Button>
    </div>
  </div>
);
```

## `itemToElement`

The Combobox takes in an `items` array and can then be formatted by
`itemToElement` and `itemToString`. `itemToElement` allows you to wrap each
dropdown item in a custom element.

```jsx
<Combobox
  onChange={() => {}}
  items={[
    { id: 'option-0', text: 'Option 0' },
    { id: 'option-1', text: 'Option 1' },
    { id: 'option-2', text: 'Option 2' },
  ]}
  itemToElement={(item) =>
    item ? (
      <span className="test" style={{ color: 'red' }}>
        {item.text} 🔥
      </span>
    ) : (
      ''
    )
  }
  label="Select an option..."
/>
```

## `itemToString`

If the `items` array is not an array of strings, you'll need to use
`itemToString` to grab the text to be used as the `Combobox` item text.

```jsx
<Combobox
  items={[
    { id: 'option-0', text: 'Option 0' },
    { id: 'option-1', text: 'Option 1' },
    { id: 'option-2', text: 'Option 2' },
  ]}
  itemToString={(item) => (item ? item.text : '')}
  onChange={() => {}}
/>
```

## `shouldFilterItem`

By default, the Combobox will _not_ filter out items that do not match the input
string. If you would like to hide items that do not match what the user is
typing, you can pass in your own filtering function to the `shouldFilterItem`
prop.

<ComboBox
  id="carbon-combobox"
  items={['Apple', 'Orange', 'Banana']}
  titleText="Label"
  helperText="Helper text"
  shouldFilterItem={(menu) =>
    menu?.item?.toLowerCase().includes(menu?.inputValue?.toLowerCase())
  }
  onChange={() => {}}
/>

```jsx
const filterItems = (menu) => {
  return menu?.item?.toLowerCase().includes(menu?.inputValue?.toLowerCase());
};

<Combobox
  titleText="Label"
  helperText="Helper text"
  items={['Apple', 'Orange', 'Banana']}
  shouldFilterItem={filterItems}
  onChange={() => {}}
/>;
```

## `allowCustomValue`

By default, if text is entered into the `Combobox` and it does not match an
item, it will be cleared on blur. However, you can change this behavior by
passing in the `allowCustomValue` prop. This will allow a user to close the menu
and accept a custom value by pressing `Enter` as well as retain the value on
blur. The `inputValue` is provided as a second argument to the `onChange`
callback.

```js
{selectedItem: undefined, inputValue: 'Apple'}
```
<Canvas
  of={ComboBoxStories.AllowCustomValue}
  additionalActions={[
    {
      title: 'Open in Stackblitz',
      onClick: () => stackblitzPrefillConfig(ComboBoxStories.AllowCustomValue),
    },
  ]}
/>

## `inputProps`

Use `inputProps` to specify native input attributes to place on the `<input>`.
These are passed to [Downshift's `getInputProps()`](https://github.com/downshift-js/downshift?tab=readme-ov-file#getinputprops)
and will override the internal input props.


### Using `maxLength`

The native `maxLength` attribute can be included in `inputProps` to define the maximum string length that the user can enter into the input.

When using `maxLength`, ensure the limit is communicated to the user through both `helperText` and `invalidText`/`warnText`.

```jsx
<ComboBox
  inputProps={{ maxLength: 5 }}
  titleText="Abbreviation"
  helperText="Provide an abbreviation that is 5 characters or less"
  invalidText="Abbreviations must be 5 characters or less"
  warnText="Abbreviations must be 5 characters or less"
/>
```


## With AI Label

<Canvas
  of={ComboBoxStories.withAILabel}
  additionalActions={[
    {
      title: 'Open in Stackblitz',
      onClick: () => stackblitzPrefillConfig(ComboBoxStories.withAILabel),
    },
  ]}
/>

## With Layer

<Canvas of={ComboBoxStories._WithLayer} />

## Component API

<ArgTypes />

## Feedback

Help us improve these docs by
[editing this file on GitHub](https://github.com/carbon-design-system/carbon/edit/main/packages/react/src/components/ComboBox/ComboBox.mdx)
