import { useState } from "react";
import { Story, Preview, Props, Meta } from "@storybook/addon-docs/blocks";
import classNames from "classnames";
import { Box } from "../Box";
import { CloseButton } from "../CloseButton";
import { Flex } from "../Flex";
import { Icon, ICON_TYPE } from "../Icon";
import { Tag } from "../Tag";
import { IntegrationLogo } from "../IntegrationLogo";
import { Reference } from "../Reference";
import { Select } from "./Select";
import { SelectPopover } from "./SelectPopover";
import { Button } from "../Button";
import { Stack } from "../Stack";
import { Text } from "../Text";

<Meta title="Components/Forms/Select" component={Select} />

# Select

```jsx
import { Select } from "@aptible/arrow-ds";
```

export const Container = ({ children }) => (
  <div className="h-40">{children}</div>
);

export const ActionButton = () => (
  <button
    className={classNames([
      "border",
      "text-iconSm",
      "w-6",
      "h-6",
      "rounded-sm",
      "bg-transparent",
      "flex",
      "justify-center",
      "items-center",
      "transition-colors",
      "ease-in-out",
      "duration-200",
      "hover:border-brandGreen-400",
      "hover:text-brandGreen-400",
      "focus:outline-none",
      "border-brandGreen-400",
      "text-brandGreen-400",
    ])}
    title="Edit Field"
    type="button"
  >
    <Icon icon={ICON_TYPE.PEN} />
  </button>
);

export const options = [
  { label: "Cruz Hendrix", value: "cruz hendrix", isDisabled: true },
  { label: "Rowena Mercer", value: "rowena mercer" },
  { label: "Emanuel Mccoy", value: "emanuel mccoy" },
  { label: "Abdul Zimmerman", value: "abdul zimmerman" },
  { label: "Reginald Hill", value: "reginald hill" },
  { label: "Isidro Morales", value: "isidro morales" },
  { label: "Oscar Chaney", value: "oscar chaney" },
  { label: "Shari Crawford", value: "shari crawford" },
  { label: "Sherri Blackburn", value: "sherri blackburn" },
  { label: "Cecile Aguilar", value: "cecile aguilar" },
  { label: "Fredric Huynh", value: "fredric huynh" },
  { label: "Amalia Rivers", value: "amalia rivers" },
  { label: "Jefferey Mann", value: "jefferey mann" },
  { label: "Maryanne Cooke", value: "maryanne cooke" },
  { label: "Josiah Shepard", value: "josiah shepard" },
  { label: "Lesley Pierce", value: "lesley pierce" },
  { label: "Erik Zuniga", value: "erik zuniga" },
  { label: "Earl Rosales", value: "earl rosales" },
  { label: "Cornell Beasley", value: "cornell beasley" },
  { label: "Armand Downs", value: "armand downs" },
  { label: "Roslyn Mccormick", value: "roslyn mccormick" },
  { label: "Gale Sellers", value: "gale sellers" },
  { label: "Elijah Beck", value: "elijah beck" },
  { label: "Estella Hayden", value: "estella hayden" },
  { label: "Maribel Woodard", value: "maribel woodard" },
  { label: "Dannie Donaldson", value: "dannie donaldson" },
  { label: "Connie Schultz", value: "connie schultz" },
  { label: "Autumn Morrow", value: "autumn morrow" },
  { label: "Marshall Hickman", value: "marshall hickman" },
  { label: "Kara Benjamin", value: "kara benjamin" },
  { label: "Cory Roberson", value: "cory roberson" },
  { label: "Carey Becker", value: "carey becker" },
  { label: "Mallory Hubbard", value: "mallory hubbard" },
  { label: "Nigel Hoover", value: "nigel hoover" },
  { label: "Fermin Walton", value: "fermin walton" },
  { label: "Arlen Marsh", value: "arlen marsh" },
  { label: "Dallas Pittman", value: "dallas pittman" },
  { label: "Hattie Chung", value: "hattie chung" },
  { label: "Patricia Rhodes", value: "patricia rhodes" },
  { label: "Marie Higgins", value: "marie higgins" },
  { label: "Whitney Key", value: "whitney key" },
  { label: "Jacob Liu", value: "jacob liu" },
  { label: "Daisy Hanna", value: "daisy hanna" },
  { label: "Felix Berry", value: "felix berry" },
  { label: "Harley Ramirez", value: "harley ramirez" },
  { label: "Shad Garza", value: "shad garza" },
  { label: "Sadie Bowman", value: "sadie bowman" },
  { label: "Elmer Stein", value: "elmer stein" },
  { label: "Brice Farley", value: "brice farley" },
  { label: "Patrica Hart", value: "patrica hart" },
];

<Preview>
  <Story name="Select">
    <Container>
      <Select options={options} />
    </Container>
  </Story>
</Preview>

## Props

<Props of={Select} />

## Demos

### With a default value

<Preview>
  <Story name="With a default value">
    <Container>
      <Select options={options} defaultValue={options[0]} />
    </Container>
  </Story>
</Preview>

### Disabled

<Preview>
  <Story name="Disabled">
    <Container>
      <Select options={options} isDisabled />
    </Container>
  </Story>
</Preview>

### Clearable, searchable

<Preview>
  <Story name="Clearable, searchable">
    <Container>
      <Select options={options} isClearable />
    </Container>
  </Story>
</Preview>

### Without search

Setting the `isSearchable` prop to `false` makes the component behave similar to a
native HTML select element.

<Preview>
  <Story name="Without search">
    <Container>
      <Select options={options} isSearchable={false} isClearable />
    </Container>
  </Story>
</Preview>

### Creatable

<Preview>
  <Story name="Creatable">
    {() => {
      const [creatableOptions, setCreatableOptions] = React.useState(options);
      const [value, setValue] = React.useState(undefined);
      const [isLoading, setIsLoading] = React.useState(false);
      const createOption = (label) => ({
        label,
        value: label.toLowerCase().replace(/\W/g, ""),
      });
      const handleChange = (newValue) => {
        setValue(newValue);
      };
      const handleCreate = (newValue) => {
        setIsLoading(true);
        const newOption = createOption(newValue);
        setValue(newOption);
        setTimeout(() => {
          setCreatableOptions([...creatableOptions, newOption]);
          setIsLoading(false);
        }, 1000);
      };
      return (
        <Container>
          <Select.Creatable
            options={creatableOptions}
            onChange={handleChange}
            onCreateOption={handleCreate}
            value={value}
            isDisabled={isLoading}
            isLoading={isLoading}
            isClearable
          />
        </Container>
      );
    }}
  </Story>
</Preview>

### Creatable with no options

<Preview>
  <Story name="Creatable with no options">
    {() => {
      const [creatableOptions, setCreatableOptions] = React.useState([]);
      const [value, setValue] = React.useState(undefined);
      const [isLoading, setIsLoading] = React.useState(false);
      const createOption = (label) => ({
        label,
        value: label.toLowerCase().replace(/\W/g, ""),
      });
      const handleChange = (newValue) => {
        setValue(newValue);
      };
      const handleCreate = (newValue) => {
        setIsLoading(true);
        const newOption = createOption(newValue);
        setValue(newOption);
        setTimeout(() => {
          setCreatableOptions([...creatableOptions, newOption]);
          setIsLoading(false);
        }, 1000);
      };
      return (
        <Container>
          <Select.Creatable
            options={creatableOptions}
            onChange={handleChange}
            onCreateOption={handleCreate}
            value={value}
            isDisabled={isLoading}
            isLoading={isLoading}
            isClearable
            noOptionsMessage={() =>
              "Type in the input above to create new items."
            }
          />
        </Container>
      );
    }}
  </Story>
</Preview>

### Rendered in a portal

Using a portal can help overcome z-index conflicts; typically when a select is
being rendered inside of a modal. To render in a portal, set the `menuInPortal`
prop to `true`.

<Preview>
  <Story name="Rendered in a portal">
    {() => {
      const menuRef = React.useRef(null);
      const handleBlur = () => {
        // Do something with the ref
        console.log(menuRef.current);
      };
      return (
        <Container>
          <Select
            options={options}
            menuInPortal
            menuRef={menuRef}
            onBlur={handleBlur}
            isClearable
          />
        </Container>
      );
    }}
  </Story>
</Preview>

### MultiSelect

When using a multiselect, it’s advised to set the `closeMenuOnSelect` prop to
`false`. This will keep the menulist popover open as a user selects multiple
options. The user can close the popover manually by clicking away from the select.

<Preview>
  <Story name="MultiSelect">
    <Container>
      <Select options={options} isMulti isClearable closeMenuOnSelect={false} />
    </Container>
  </Story>
</Preview>

### MultiSelect show selected options

When using a multiselect, keep the selected options visible in the menulist by
setting the `hideSelectedOptions` prop to `false`. This will highlight each
selected option rather than remove it from the list.

<Preview>
  <Story name="MultiSelect show selected options">
    <Container>
      <Select
        options={options}
        isMulti
        isClearable
        closeMenuOnSelect={false}
        hideSelectedOptions={false}
      />
    </Container>
  </Story>
</Preview>

### MultiSelect with default value

<Preview>
  <Story name="MultiSelect with default value">
    <Container>
      <Select
        options={options}
        isMulti
        isClearable
        closeMenuOnSelect={false}
        defaultValue={options[0]}
      />
    </Container>
  </Story>
</Preview>

### MultiSelect with default value and disabled

<Preview>
  <Story name="MultiSelect with default value and disabled">
    <Container>
      <Select
        options={options}
        isMulti
        isClearable
        closeMenuOnSelect={false}
        defaultValue={options[0]}
        isDisabled
      />
    </Container>
  </Story>
</Preview>

### MultiSelect with custom tokens

This examples shows how you can define a custom token component and styles for a
multiselect. Find more information about using custom components on the
[react-select docs](https://react-select.com/components).

<Preview>
  <Story name="MultiSelect with custom tokens">
    {() => {
      const CustomTag = ({ label, value, className = "", closeButton }) => {
        const textClassNames = "font-medium h-full items-center px-1";
        return (
          <Tag
            className={`bg-gray-400 p-0 justify-start${
              className ? ` ${className}` : ""
            }`}
            as="span"
          >
            <Flex
              as="span"
              className={`${textClassNames} bg-white rounded-l-full`}
            >
              {label}
            </Flex>
            <Flex as="span" className={textClassNames}>
              {value}
            </Flex>
            {closeButton}
          </Tag>
        );
      };
      const MultiValue = (props) => {
        const { onClick, ...restRemoveProps } = props.removeProps;
        const label = props.data.label.split(":")[0];
        const value = props.data.label.split(":")[1];
        return (
          <CustomTag
            label={label}
            value={value}
            className="m-1"
            closeButton={
              <CloseButton
                className="text-iconSm w-3 h-3 text-gray-600 mr-1"
                onClick={props.removeProps.onClick}
                {...restRemoveProps}
              />
            }
          />
        );
      };
      return (
        <Container>
          <Select
            options={[
              { value: "color_red", label: "Color:Red" },
              { value: "color_green", label: "Color:Green" },
              { value: "color_blue", label: "Color:Blue" },
            ]}
            components={{ MultiValue }}
            formatOptionLabel={({ label }) => {
              return (
                <CustomTag
                  label={label.split(":")[0]}
                  value={label.split(":")[1]}
                />
              );
            }}
            isMulti
            isClearable
            closeMenuOnSelect={false}
            hideSelectedOptions={false}
          />
        </Container>
      );
    }}
  </Story>
</Preview>

### Async

The Async component is available to load options from a remote source as the
user types.

The options can resolve from a callback or from a returned promise.

Multiselect is also supported.

See https://react-select.com/async for more info and examples.

<Preview>
  <Story name="Async">
    {() => {
      const [, setInputValue] = React.useState("");
      const filterOptions = (val) => {
        return options.filter((i) =>
          i.label.toLowerCase().includes(val.toLowerCase()),
        );
      };
      const loadOptions = (val, callback) => {
        setTimeout(() => {
          callback(filterOptions(val));
        }, 1000);
      };
      return (
        <Container>
          <Select.Async
            loadOptions={loadOptions}
            onInputChange={(val) => setInputValue(val.replace(/\W/g, ""))}
            cacheOptions
            defaultOptions
            isClearable
          />
        </Container>
      );
    }}
  </Story>
</Preview>

### Select with custom option label and single value

In this example, we want to show a logo next to each option within the menulist.
However, we don’t want the logo to appear next to the selected value. This can be
accomplished by using `formatOptionLabel` to create the custom options and then
override the `SingleValue` component to only display the selected option’s label.

[Edit in CodeSandbox](https://codesandbox.io/s/select-with-custom-option-label-and-single-value-ietek)

<Preview>
  <Story name="Select with custom option label and single value">
    <Container>
      <Select
        isClearable
        options={[
          {
            label: "AWS",
            value: "aws",
          },
          {
            label: "Gitlab",
            value: "gitlab",
          },
          {
            label: "Okta",
            value: "okta",
          },
        ]}
        components={{
          SingleValue: (props) => {
            return <Box>{props.data.label}</Box>;
          },
        }}
        formatOptionLabel={({ label, value }) => {
          return (
            <Box className="flex justify-between">
              <Box>{label}</Box>
              <IntegrationLogo size={12} logo={value} />
            </Box>
          );
        }}
      />
    </Container>
  </Story>
</Preview>

### In a popover, single select

<Preview>
  <Story name="Select popover">
    {() => {
      const [isEditing, setIsEditing] = useState(false);
      const [value, setValue] = useState(null);
      const onChange = (newVal) => {
        setValue(newVal);
        setIsEditing(false);
      };
      return (
        <Box className="relative" style={{ height: "400px" }}>
          {isEditing ? (
            <SelectPopover
              readView={
                <Flex style={{ minHeight: "26px" }} className="items-center">
                  {value ? value.label : null}
                </Flex>
              }
              cancelButton={<ActionButton />}
              onCancelEdit={() => setIsEditing(false)}
              optionsMessage="Use the search field above to find an Owner if it is not shown in this list."
              isCreatable
              selectProps={{
                options,
                value,
                onChange,
              }}
            />
          ) : (
            <Stack>
              <Text>{(value && value.label) || "No items selected"}</Text>
              <Button onClick={() => setIsEditing(true)}>Edit</Button>
            </Stack>
          )}
        </Box>
      );
    }}
  </Story>
</Preview>

### In a popover, multiselect

<Preview>
  <Story name="Select popover multiselect">
    {() => {
      const [isEditing, setIsEditing] = useState(false);
      const [value, setValue] = useState([]);
      return (
        <Box className="relative" style={{ height: "400px" }}>
          {isEditing ? (
            <SelectPopover
              readView={
                <Flex style={{ minHeight: "26px" }} className="items-center">
                  <Reference.Group>
                    {value.map((v) => (
                      <Reference key={v.value}>{v.label}</Reference>
                    ))}
                  </Reference.Group>
                </Flex>
              }
              cancelButton={<ActionButton />}
              onCancelEdit={() => setIsEditing(false)}
              optionsMessage="Use the search field above to find an Owner if it is not shown in this list."
              isCreatable
              selectProps={{
                options,
                value,
                onChange: setValue,
                isMulti: true,
              }}
            />
          ) : (
            <Stack>
              <Text>
                {value.length > 0
                  ? value.map((v) => v.label).join(", ")
                  : "No items selected"}
              </Text>
              <Button onClick={() => setIsEditing(true)}>Edit</Button>
            </Stack>
          )}
        </Box>
      );
    }}
  </Story>
</Preview>
