# 选择

显示一个选项列表供用户选择——由一个按钮触发。

## Features

- 可以是受控或非受控。

- 提供2种定位模式。

- 支持项目、标签和项目组。

- 焦点完全管理。

- 完整的键盘导航。

- 支持自定义占位符。

- 支持类型预测。

- 支持从右到左的方向。

## [组成结构](#组成结构)

导入所有部分并组合它们。

```jsx
import { Select } from "radix-ui";

export default () => (
  <Select.Root>
    <Select.Trigger>
      <Select.Value />

      <Select.Icon />
    </Select.Trigger>

    <Select.Portal>
      <Select.Content>
        <Select.ScrollUpButton />

        <Select.Viewport>
          <Select.Item>
            <Select.ItemText />

            <Select.ItemIndicator />
          </Select.Item>

          <Select.Group>
            <Select.Label />

            <Select.Item>
              <Select.ItemText />

              <Select.ItemIndicator />
            </Select.Item>
          </Select.Group>

          <Select.Separator />
        </Select.Viewport>

        <Select.ScrollDownButton />

        <Select.Arrow />
      </Select.Content>
    </Select.Portal>
  </Select.Root>
);
```

## [API 参考](#api-参考)

### [根元素](#根元素)

包含选择的所有部分。

| Prop            | Type       | Default          |
| --------------- | ---------- | ---------------- |
| `defaultValue`  | `string`   | No default value |
| `value`         | `string`   | No default value |
| `onValueChange` | `function` | No default value |
| `defaultOpen`   | `boolean`  | No default value |
| `open`          | `boolean`  | No default value |
| `onOpenChange`  | `function` | No default value |
| `dir`           | `enum`     | No default value |
| `name`          | `string`   | No default value |
| `disabled`      | `boolean`  | No default value |
| `required`      | `boolean`  | No default value |

### [触发器](#触发器)

切换选择的按钮。`Select.Content` 将通过与触发器对齐来定位自己。

| Prop      | Type      | Default |
| --------- | --------- | ------- |
| `asChild` | `boolean` | `false` |

| Data attribute       | Values             |
| -------------------- | ------------------ |
| `[data-state]`       | `"打开" \| "关闭"` |
| `[data-disabled]`    | 禁用时存在         |
| `[data-placeholder]` | 有占位符时存在     |

### [值](#值)

反映所选值的部分。默认情况下，将渲染所选项目的文本。如果您需要更多控制，可以控制选择并传递自己的 `children`。它不应被样式化以确保正确定位。也可以提供一个可选的 `placeholder` 属性，以便选择没有值时使用。

| Prop          | Type        | Default          |
| ------------- | ----------- | ---------------- |
| `asChild`     | `boolean`   | `false`          |
| `placeholder` | `ReactNode` | No default value |

### [图标](#图标)

通常显示在值旁边的小图标，为其可打开的事实提供视觉提示。默认渲染为 ▼，但您可以通过 `asChild` 使用您自己的图标，或使用 `children`。

| Prop      | Type      | Default |
| --------- | --------- | ------- |
| `asChild` | `boolean` | `false` |

### [门户](#门户)

使用时，将内容部分传送到 `body`。

| Prop        | Type          | Default         |
| ----------- | ------------- | --------------- |
| `container` | `HTMLElement` | `document.body` |

### [内容](#内容)

选择打开时弹出的组件。

| Prop                   | Type                | Default          |
| ---------------------- | ------------------- | ---------------- |
| `asChild`              | `boolean`           | `false`          |
| `onCloseAutoFocus`     | `function`          | No default value |
| `onEscapeKeyDown`      | `function`          | No default value |
| `onPointerDownOutside` | `function`          | No default value |
| `position`             | `enum`              | `"item-aligned"` |
| `side`                 | `enum`              | `"bottom"`       |
| `sideOffset`           | `number`            | `0`              |
| `align`                | `enum`              | `"start"`        |
| `alignOffset`          | `number`            | `0`              |
| `avoidCollisions`      | `boolean`           | `true`           |
| `collisionBoundary`    | `Boundary`          | `[]`             |
| `collisionPadding`     | `number \| Padding` | `10`             |
| `arrowPadding`         | `number`            | `0`              |
| `sticky`               | `enum`              | `"partial"`      |
| `hideWhenDetached`     | `boolean`           | `false`          |

| Data attribute | Values                             |
| -------------- | ---------------------------------- |
| `[data-state]` | `"打开" \| "关闭"`                 |
| `[data-side]`  | `"左" \| "右" \| "底部" \| "顶部"` |
| `[data-align]` | `"start" \| "end" \| "center"`     |

| CSS Variable                              | Description                                                                             |
| ----------------------------------------- | --------------------------------------------------------------------------------------- |
| `--radix-select-content-transform-origin` | 根据内容和箭头的位置/偏移计算出的 `transform-origin`。仅在 `position="popper"` 时存在。 |
| `--radix-select-content-available-width`  | 触发器和边界边缘之间的剩余宽度。仅在 `position="popper"` 时存在。                       |
| `--radix-select-content-available-height` | 触发器和边界边缘之间的剩余高度。仅在 `position="popper"` 时存在。                       |
| `--radix-select-trigger-width`            | 触发器的宽度。仅在 `position="popper"` 时存在。                                         |
| `--radix-select-trigger-height`           | 触发器的高度。仅在 `position="popper"` 时存在。                                         |

### [视口](#视口)

包含所有项目的滚动视口。

| Prop      | Type      | Default |
| --------- | --------- | ------- |
| `asChild` | `boolean` | `false` |

### [项目](#项目)

包含选择项目的组件。

| Prop        | Type      | Default          |
| ----------- | --------- | ---------------- |
| `asChild`   | `boolean` | `false`          |
| `value*`    | `string`  | No default value |
| `disabled`  | `boolean` | No default value |
| `textValue` | `string`  | No default value |

| Data attribute       | Values                     |
| -------------------- | -------------------------- |
| `[data-state]`       | `"checked" \| "unchecked"` |
| `[data-highlighted]` | 突出显示时存在             |
| `[data-disabled]`    | 禁用时存在                 |

### [项目文本](#项目文本)

项目的文本部分。它应只包含您希望在选择该项目时显示在触发器中的文本。它不应被样式化以确保正确定位。

| Prop      | Type      | Default |
| --------- | --------- | ------- |
| `asChild` | `boolean` | `false` |

### [项目指示器](#项目指示器)

在选择该项目时渲染。您可以直接对该元素进行样式设置，或者将其用作放置图标的包装器，或两者皆有。

| Prop      | Type      | Default |
| --------- | --------- | ------- |
| `asChild` | `boolean` | `false` |

### [向上滚动按钮](#向上滚动按钮)

一个可选按钮，用作提示以显示视口溢出，同时在功能上启用向上滚动。

| Prop      | Type      | Default |
| --------- | --------- | ------- |
| `asChild` | `boolean` | `false` |

### [向下滚动按钮](#向下滚动按钮)

一个可选按钮，用作提示以显示视口溢出，同时在功能上启用向下滚动。

| Prop      | Type      | Default |
| --------- | --------- | ------- |
| `asChild` | `boolean` | `false` |

### [组](#组)

用于对多个项目进行分组。与 `Select.Label` 一起使用，以确保通过自动标签提供良好的可访问性。

| Prop      | Type      | Default |
| --------- | --------- | ------- |
| `asChild` | `boolean` | `false` |

### [标签](#标签)

用于渲染组的标签。使用箭头键无法获取焦点。

| Prop      | Type      | Default |
| --------- | --------- | ------- |
| `asChild` | `boolean` | `false` |

### [分隔符](#分隔符)

用于在选择中视觉上分隔项目。

| Prop      | Type      | Default |
| --------- | --------- | ------- |
| `asChild` | `boolean` | `false` |

### [箭头](#箭头)

可选的箭头元素，渲染在内容旁边。这可以用来帮助视觉上将触发器与 `Select.Content` 连接。必须在 `Select.Content` 中渲染。仅在 `position` 设置为 `popper` 时可用。

| Prop      | Type      | Default |
| --------- | --------- | ------- |
| `asChild` | `boolean` | `false` |
| `width`   | `number`  | `10`    |
| `height`  | `number`  | `5`     |

## [示例](#示例)

### [更改定位模式](#更改定位模式)

默认情况下，`Select` 的行为类似于原生 MacOS 菜单，通过相对于活动项目来定位 `Select.Content`。如果您更喜欢类似于 `Popover` 或 `DropdownMenu` 的替代定位方法，那么您可以将 `position` 设置为 `popper`，并使用其他对齐选项，例如 `side`、`sideOffset` 等。

```jsx
// index.jsx

import { Select } from "radix-ui";

export default () => (
  <Select.Root>
    <Select.Trigger>…</Select.Trigger>

    <Select.Portal>
      <Select.Content position="popper" sideOffset={5}>
        …
      </Select.Content>
    </Select.Portal>
  </Select.Root>
);
```

### [限制内容大小](#限制内容大小)

在 `Select.Content` 上使用 `position="popper"` 时，您可能希望限制内容的宽度，以便与触发器的宽度匹配。您也可能想限制它的高度，以免超过视口。

我们暴露了几个 CSS 自定义属性，例如 `--radix-select-trigger-width` 和 `--radix-select-content-available-height` 以支持此操作。使用它们来限制内容的维度。

```jsx
// index.jsx

import { Select } from "radix-ui";

import "./styles.css";

export default () => (
  <Select.Root>
    <Select.Trigger>…</Select.Trigger>

    <Select.Portal>
      <Select.Content className="SelectContent" position="popper" sideOffset={5}>
        …
      </Select.Content>
    </Select.Portal>
  </Select.Root>
);
```

```css
/* styles.css */

.SelectContent {
  width: var(--radix-select-trigger-width);

  max-height: var(--radix-select-content-available-height);
}
```

### [带有禁用项目](#带有禁用项目)

您可以通过 `data-disabled` 属性为禁用的项目添加特殊样式。

```jsx
// index.jsx

import { Select } from "radix-ui";

import "./styles.css";

export default () => (
  <Select.Root>
    <Select.Trigger>…</Select.Trigger>

    <Select.Portal>
      <Select.Content>
        <Select.Viewport>
          <Select.Item className="SelectItem" disabled>
            …
          </Select.Item>

          <Select.Item>…</Select.Item>

          <Select.Item>…</Select.Item>
        </Select.Viewport>
      </Select.Content>
    </Select.Portal>
  </Select.Root>
);
```

```css
/* styles.css */

.SelectItem[data-disabled] {
  color: "gainsboro";
}
```

### [带有占位符](#带有占位符)

您可以在 `Value` 上使用 `placeholder` 属性，以便在选择没有值时使用。`Trigger` 上也有一个 `data-placeholder` 属性，以帮助进行样式设置。

```jsx
// index.jsx

import { Select } from "radix-ui";

import "./styles.css";

export default () => (
  <Select.Root>
    <Select.Trigger className="SelectTrigger">
      <Select.Value placeholder="选择一个选项" />

      <Select.Icon />
    </Select.Trigger>

    <Select.Portal>
      <Select.Content>…</Select.Content>
    </Select.Portal>
  </Select.Root>
);
```

```css
/* styles.css */

.SelectTrigger[data-placeholder] {
  color: "gainsboro";
}
```

### [带分隔符](#带分隔符)

使用 `Separator` 部分在项目之间添加分隔符。

```jsx
<Select.Root>
  <Select.Trigger>…</Select.Trigger>

  <Select.Portal>
    <Select.Content>
      <Select.Viewport>
        <Select.Item>…</Select.Item>

        <Select.Item>…</Select.Item>

        <Select.Item>…</Select.Item>

        <Select.Separator />

        <Select.Item>…</Select.Item>

        <Select.Item>…</Select.Item>
      </Select.Viewport>
    </Select.Content>
  </Select.Portal>
</Select.Root>
```

### [带有分组项目](#带有分组项目)

使用 `Group` 和 `Label` 部分在一个部分中对项目进行分组。

```jsx
<Select.Root>
  <Select.Trigger>…</Select.Trigger>

  <Select.Portal>
    <Select.Content>
      <Select.Viewport>
        <Select.Group>
          <Select.Label>标签</Select.Label>

          <Select.Item>…</Select.Item>

          <Select.Item>…</Select.Item>

          <Select.Item>…</Select.Item>
        </Select.Group>
      </Select.Viewport>
    </Select.Content>
  </Select.Portal>
</Select.Root>
```

### [带有复杂项目](#带有复杂项目)

您可以在项目中使用自定义内容。

```jsx
import { Select } from "radix-ui";

export default () => (
  <Select.Root>
    <Select.Trigger>…</Select.Trigger>

    <Select.Portal>
      <Select.Content>
        <Select.Viewport>
          <Select.Item>
            <Select.ItemText>
              <img src="…" />
              阿道夫·赫斯
            </Select.ItemText>

            <Select.ItemIndicator>…</Select.ItemIndicator>
          </Select.Item>

          <Select.Item>…</Select.Item>

          <Select.Item>…</Select.Item>
        </Select.Viewport>
      </Select.Content>
    </Select.Portal>
  </Select.Root>
);
```

### [控制触发器中显示的值](#控制触发器中显示的值)

默认情况下，触发器将自动显示所选项目 `ItemText` 的内容。您可以通过选择将内容放入/放出 `ItemText` 部分来控制显示的内容。

如果您需要更多灵活性，可以使用 `value`/`onValueChange` 属性控制组件，并将 `children` 传递给 `SelectValue`。请确保您放入的内容是可访问的。

```jsx
const countries = { france: "🇫🇷", "united-kingdom": "🇬🇧", spain: "🇪🇸" };

export default () => {
  const [value, setValue] = React.useState("france");

  return (
    <Select.Root value={value} onValueChange={setValue}>
      <Select.Trigger>
        <Select.Value aria-label={value}>{countries[value]}</Select.Value>

        <Select.Icon />
      </Select.Trigger>

      <Select.Portal>
        <Select.Content>
          <Select.Viewport>
            <Select.Item value="france">
              <Select.ItemText>法国</Select.ItemText>

              <Select.ItemIndicator>…</Select.ItemIndicator>
            </Select.Item>

            <Select.Item value="united-kingdom">
              <Select.ItemText>英国</Select.ItemText>

              <Select.ItemIndicator>…</Select.ItemIndicator>
            </Select.Item>

            <Select.Item value="spain">
              <Select.ItemText>西班牙</Select.ItemText>

              <Select.ItemIndicator>…</Select.ItemIndicator>
            </Select.Item>
          </Select.Viewport>
        </Select.Content>
      </Select.Portal>
    </Select.Root>
  );
};
```

### [带有自定义滚动条](#带有自定义滚动条)

默认情况下，原生滚动条是隐藏的，因为我们推荐使用 `ScrollUpButton` 和 `ScrollDownButton` 部分以获得最佳用户体验。如果您不想使用这些部分，请将选择与我们的 [滚动区域](scroll-area) 原语组合使用。

```jsx
// index.jsx

import { Select, ScrollArea } from "radix-ui";

import "./styles.css";

export default () => (
  <Select.Root>
    <Select.Trigger>…</Select.Trigger>

    <Select.Portal>
      <Select.Content>
        <ScrollArea.Root className="ScrollAreaRoot" type="auto">
          <Select.Viewport asChild>
            <ScrollArea.Viewport className="ScrollAreaViewport">
              <StyledItem>…</StyledItem>

              <StyledItem>…</StyledItem>

              <StyledItem>…</StyledItem>
            </ScrollArea.Viewport>
          </Select.Viewport>

          <ScrollArea.Scrollbar className="ScrollAreaScrollbar" orientation="vertical">
            <ScrollArea.Thumb className="ScrollAreaThumb" />
          </ScrollArea.Scrollbar>
        </ScrollArea.Root>
      </Select.Content>
    </Select.Portal>
  </Select.Root>
);
```

```css
/* styles.css */

.ScrollAreaRoot {
  width: 100%;

  height: 100%;
}

.ScrollAreaViewport {
  width: 100%;

  height: 100%;
}

.ScrollAreaScrollbar {
  width: 4px;

  padding: 5px 2px;
}

.ScrollAreaThumb {
  background: rgba(0, 0, 0, 0.3);

  border-radius: 3px;
}
```

## [可访问性](#可访问性)

遵循 [ListBox WAI-ARIA 设计模式](https://www.w3.org/WAI/ARIA/apg/patterns/listbox)。

查看 W3C [选择仅组合框](https://www.w3.org/TR/wai-aria-practices/examples/combobox/combobox-select-only.html) 示例以获取更多信息。

### [键盘交互](#键盘交互)

| Key        | Description                                                                               |
| ---------- | ----------------------------------------------------------------------------------------- |
| `空格`     | 当焦点在 `Select.Trigger` 上时，打开选择并聚焦所选项目。 当焦点在项目上时，选择该项目。   |
| `回车`     | 当焦点在 `Select.Trigger` 上时，打开选择并聚焦第一个项目。 当焦点在项目上时，选择该项目。 |
| `箭头向下` | 当焦点在 `Select.Trigger` 上时，打开选择。 当焦点在项目上时，将焦点移动到下一个项目。     |
| `箭头向上` | 当焦点在 `Select.Trigger` 上时，打开选择。 当焦点在项目上时，将焦点移动到上一个项目。     |
| `Esc`      | 关闭选择并将焦点移到 `Select.Trigger`。                                                   |

### [标签](#标签-1)

使用我们的 [标签](label) 组件为选择提供视觉和可访问的标签。

```jsx
import { Select, Label } from "radix-ui";

export default () => (
  <>
    <Label>
      国家
      <Select.Root>…</Select.Root>
    </Label>

    {/* 或 */}

    <Label htmlFor="country">国家</Label>

    <Select.Root>
      <Select.Trigger id="country">…</Select.Trigger>

      <Select.Portal>
        <Select.Content>…</Select.Content>
      </Select.Portal>
    </Select.Root>
  </>
);
```

## [自定义 API](#自定义-api)

通过将原语部分抽象到自己的组件中创建自己的 API。

### [抽象到 `Select` 和 `SelectItem`](#抽象到-select-和-selectitem)

此示例抽象了大部分部分。

#### 用法

```jsx
import { Select, SelectItem } from "./your-select";

export default () => (
  <Select defaultValue="2">
    <SelectItem value="1">项目 1</SelectItem>

    <SelectItem value="2">项目 2</SelectItem>

    <SelectItem value="3">项目 3</SelectItem>
  </Select>
);
```

#### 实现

```jsx
// your-select.jsx

import * as React from "react";

import { Select as SelectPrimitive } from "radix-ui";

import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "@radix-ui/react-icons";

export const Select = React.forwardRef(({ children, ...props }, forwardedRef) => {
  return (
    <SelectPrimitive.Root {...props}>
      <SelectPrimitive.Trigger ref={forwardedRef}>
        <SelectPrimitive.Value />

        <SelectPrimitive.Icon>
          <ChevronDownIcon />
        </SelectPrimitive.Icon>
      </SelectPrimitive.Trigger>

      <SelectPrimitive.Portal>
        <SelectPrimitive.Content>
          <SelectPrimitive.ScrollUpButton>
            <ChevronUpIcon />
          </SelectPrimitive.ScrollUpButton>

          <SelectPrimitive.Viewport>{children}</SelectPrimitive.Viewport>

          <SelectPrimitive.ScrollDownButton>
            <ChevronDownIcon />
          </SelectPrimitive.ScrollDownButton>
        </SelectPrimitive.Content>
      </SelectPrimitive.Portal>
    </SelectPrimitive.Root>
  );
});

export const SelectItem = React.forwardRef(({ children, ...props }, forwardedRef) => {
  return (
    <SelectPrimitive.Item {...props} ref={forwardedRef}>
      <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>

      <SelectPrimitive.ItemIndicator>
        <CheckIcon />
      </SelectPrimitive.ItemIndicator>
    </SelectPrimitive.Item>
  );
});
```

<!--$-->

<!--/$-->
