# 导航菜单

用于浏览网站的链接集合。

## Features

- 可以是受控或非受控的。

- 灵活的布局结构，带有管理的标签焦点。

- 支持子菜单。

- 可选的活动项指示器。

- 全面的键盘导航。

- 暴露用于高级动画的 CSS 变量。

- 支持自定义时机。

## [构造](#构造)

导入所有部分并将它们组合在一起。

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

export default () => (
  <NavigationMenu.Root>
    <NavigationMenu.List>
      <NavigationMenu.Item>
        <NavigationMenu.Trigger />

        <NavigationMenu.Content>
          <NavigationMenu.Link />
        </NavigationMenu.Content>
      </NavigationMenu.Item>

      <NavigationMenu.Item>
        <NavigationMenu.Link />
      </NavigationMenu.Item>

      <NavigationMenu.Item>
        <NavigationMenu.Trigger />

        <NavigationMenu.Content>
          <NavigationMenu.Sub>
            <NavigationMenu.List />

            <NavigationMenu.Viewport />
          </NavigationMenu.Sub>
        </NavigationMenu.Content>
      </NavigationMenu.Item>

      <NavigationMenu.Indicator />
    </NavigationMenu.List>

    <NavigationMenu.Viewport />
  </NavigationMenu.Root>
);
```

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

### [Root](#root)

包含导航菜单的所有部分。

| Prop                | Type       | Default          |
| ------------------- | ---------- | ---------------- |
| `defaultValue`      | `string`   | No default value |
| `value`             | `string`   | No default value |
| `onValueChange`     | `function` | No default value |
| `delayDuration`     | `number`   | `200`            |
| `skipDelayDuration` | `number`   | `300`            |
| `dir`               | `enum`     | No default value |
| `orientation`       | `enum`     | `"horizontal"`   |

| Data attribute       | Values                       |
| -------------------- | ---------------------------- |
| `[data-orientation]` | `"vertical" \| "horizontal"` |

### [Sub](#sub)

表示子菜单。在嵌套时要用它替代根部分以创建子菜单。

| Prop            | Type       | Default          |
| --------------- | ---------- | ---------------- |
| `defaultValue`  | `string`   | No default value |
| `value`         | `string`   | No default value |
| `onValueChange` | `function` | No default value |
| `orientation`   | `enum`     | `"horizontal"`   |

| Data attribute       | Values                       |
| -------------------- | ---------------------------- |
| `[data-orientation]` | `"vertical" \| "horizontal"` |

### [List](#list)

包含顶级菜单项。

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

| Data attribute       | Values                       |
| -------------------- | ---------------------------- |
| `[data-orientation]` | `"vertical" \| "horizontal"` |

### [Item](#item)

一个顶级菜单项，包含链接或触发器和内容组合。

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

### [Trigger](#trigger)

切换内容的按钮。

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

| Data attribute    | Values               |
| ----------------- | -------------------- |
| `[data-state]`    | `"open" \| "closed"` |
| `[data-disabled]` | 当禁用时显示         |

### [Content](#content)

包含与每个触发器关联的内容。

| Prop                   | Type       | Default          |
| ---------------------- | ---------- | ---------------- |
| `asChild`              | `boolean`  | `false`          |
| `onEscapeKeyDown`      | `function` | No default value |
| `onPointerDownOutside` | `function` | No default value |
| `onFocusOutside`       | `function` | No default value |
| `onInteractOutside`    | `function` | No default value |
| `forceMount`           | `boolean`  | No default value |

| Data attribute       | Values                                                 |
| -------------------- | ------------------------------------------------------ |
| `[data-state]`       | `"open" \| "closed"`                                   |
| `[data-motion]`      | `"to-start" \| "to-end" \| "from-start" \| "from-end"` |
| `[data-orientation]` | `"vertical" \| "horizontal"`                           |

### [Link](#link)

一个导航链接。

| Prop       | Type       | Default          |
| ---------- | ---------- | ---------------- |
| `asChild`  | `boolean`  | `false`          |
| `active`   | `boolean`  | `false`          |
| `onSelect` | `function` | No default value |

| Data attribute  | Values       |
| --------------- | ------------ |
| `[data-active]` | 当活动时显示 |

### [Indicator](#indicator)

一个可选的指示器元素，渲染在列表下面，用于突出显示当前活动的触发器。

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

| Data attribute       | Values                       |
| -------------------- | ---------------------------- |
| `[data-state]`       | `"visible" \| "hidden"`      |
| `[data-orientation]` | `"vertical" \| "horizontal"` |

### [Viewport](#viewport)

一个可选的视口元素，用于在列表之外渲染活动内容。

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

| Data attribute       | Values                       |
| -------------------- | ---------------------------- |
| `[data-state]`       | `"open" \| "closed"`         |
| `[data-orientation]` | `"vertical" \| "horizontal"` |

| CSS Variable                              | Description                                 |
| ----------------------------------------- | ------------------------------------------- |
| `--radix-navigation-menu-viewport-width`  | 视口在可见/隐藏状态下的宽度，从活动内容计算 |
| `--radix-navigation-menu-viewport-height` | 视口在可见/隐藏状态下的高度，从活动内容计算 |

## [示例](#示例)

### [垂直](#垂直)

您可以通过使用 `orientation` 属性创建一个垂直菜单。

```jsx
<NavigationMenu.Root orientation="vertical">
  <NavigationMenu.List>
    <NavigationMenu.Item>
      <NavigationMenu.Trigger>项目一</NavigationMenu.Trigger>

      <NavigationMenu.Content>项目一内容</NavigationMenu.Content>
    </NavigationMenu.Item>

    <NavigationMenu.Item>
      <NavigationMenu.Trigger>项目二</NavigationMenu.Trigger>

      <NavigationMenu.Content>项目二内容</NavigationMenu.Content>
    </NavigationMenu.Item>
  </NavigationMenu.List>
</NavigationMenu.Root>
```

### [灵活布局](#灵活布局)

当你需要额外控制 `Content` 的渲染位置时，使用 `Viewport` 部分。当你的设计需要调整的 DOM 结构，或需要灵活性以实现 [高级动画](/primitives/docs/components/navigation-menu#advanced-animation) 时，这非常有用。 标签焦点将自动保持。

```jsx
<NavigationMenu.Root>
  <NavigationMenu.List>
    <NavigationMenu.Item>
      <NavigationMenu.Trigger>项目一</NavigationMenu.Trigger>

      <NavigationMenu.Content>项目一内容</NavigationMenu.Content>
    </NavigationMenu.Item>

    <NavigationMenu.Item>
      <NavigationMenu.Trigger>项目二</NavigationMenu.Trigger>

      <NavigationMenu.Content>项目二内容</NavigationMenu.Content>
    </NavigationMenu.Item>
  </NavigationMenu.List>

  {/* NavigationalMenu.Content 激活时将在这里渲染 */}

  <NavigationMenu.Viewport />
</NavigationMenu.Root>
```

### [带指示器](#带指示器)

您可以使用可选的 `Indicator` 部分来突出显示当前活动的 `Trigger`，这在您希望提供动画视觉提示（例如箭头或高亮显示）以伴随 `Viewport` 时非常有用。

```jsx
// index.jsx

import { NavigationMenu } from "radix-ui";

import "./styles.css";

export default () => (
  <NavigationMenu.Root>
    <NavigationMenu.List>
      <NavigationMenu.Item>
        <NavigationMenu.Trigger>项目一</NavigationMenu.Trigger>

        <NavigationMenu.Content>项目一内容</NavigationMenu.Content>
      </NavigationMenu.Item>

      <NavigationMenu.Item>
        <NavigationMenu.Trigger>项目二</NavigationMenu.Trigger>

        <NavigationMenu.Content>项目二内容</NavigationMenu.Content>
      </NavigationMenu.Item>

      <NavigationMenu.Indicator className="NavigationMenuIndicator" />
    </NavigationMenu.List>

    <NavigationMenu.Viewport />
  </NavigationMenu.Root>
);
```

```css
/* styles.css */

.NavigationMenuIndicator {
  background-color: grey;
}

.NavigationMenuIndicator[data-orientation="horizontal"] {
  height: 3px;

  transition:
    width,
    transform,
    250ms ease;
}
```

### [带子菜单](#带子菜单)

通过嵌套您的 `NavigationMenu` 并用 `Sub` 部分替换其 `Root` 来创建子菜单。 子菜单与 `Root` 导航菜单的工作方式不同，更像是 [`Tabs`](/primitives/docs/components/tabs)，因为始终应该有一个项目处于活动状态，因此请务必分配并设置 `defaultValue`。

```jsx
<NavigationMenu.Root>
  <NavigationMenu.List>
    <NavigationMenu.Item>
      <NavigationMenu.Trigger>项目一</NavigationMenu.Trigger>

      <NavigationMenu.Content>项目一内容</NavigationMenu.Content>
    </NavigationMenu.Item>

    <NavigationMenu.Item>
      <NavigationMenu.Trigger>项目二</NavigationMenu.Trigger>

      <NavigationMenu.Content>
        <NavigationMenu.Sub defaultValue="sub1">
          <NavigationMenu.List>
            <NavigationMenu.Item value="sub1">
              <NavigationMenu.Trigger>子项目一</NavigationMenu.Trigger>

              <NavigationMenu.Content>子项目一内容</NavigationMenu.Content>
            </NavigationMenu.Item>

            <NavigationMenu.Item value="sub2">
              <NavigationMenu.Trigger>子项目二</NavigationMenu.Trigger>

              <NavigationMenu.Content>子项目二内容</NavigationMenu.Content>
            </NavigationMenu.Item>
          </NavigationMenu.List>
        </NavigationMenu.Sub>
      </NavigationMenu.Content>
    </NavigationMenu.Item>
  </NavigationMenu.List>
</NavigationMenu.Root>
```

### [带客户端路由](#带客户端路由)

如果您需要使用路由包提供的 `Link` 组件，我们建议通过自定义组件与 `NavigationMenu.Link` 组合使用。 这将确保无障碍性和一致的键盘控制。以下是使用 Next.js 的示例：

```jsx
// index.jsx

import { usePathname } from "next/navigation";

import NextLink from "next/link";

import { NavigationMenu } from "radix-ui";

import "./styles.css";

const Link = ({ href, ...props }) => {
  const pathname = usePathname();

  const isActive = href === pathname;

  return (
    <NavigationMenu.Link asChild active={isActive}>
      <NextLink href={href} className="NavigationMenuLink" {...props} />
    </NavigationMenu.Link>
  );
};

export default () => (
  <NavigationMenu.Root>
    <NavigationMenu.List>
      <NavigationMenu.Item>
        <Link href="/">首页</Link>
      </NavigationMenu.Item>

      <NavigationMenu.Item>
        <Link href="/about">关于</Link>
      </NavigationMenu.Item>
    </NavigationMenu.List>
  </NavigationMenu.Root>
);
```

```css
/* styles.css */

.NavigationMenuLink {
  text-decoration: none;
}

.NavigationMenuLink[data-active] {
  text-decoration: underline;
}
```

### [高级动画](#高级动画)

我们暴露了 `--radix-navigation-menu-viewport-[width|height]` 和 `data-motion['from-start'|'to-start'|'from-end'|'to-end']` 属性，以允许您根据进入/退出方向动画化 `Viewport` 的大小和 `Content` 的位置。

将这些与 `position: absolute;` 结合使用，可以在移动项目之间时创建平滑的重叠动画效果。

```jsx
// index.jsx

import { NavigationMenu } from "radix-ui";

import "./styles.css";

export default () => (
  <NavigationMenu.Root>
    <NavigationMenu.List>
      <NavigationMenu.Item>
        <NavigationMenu.Trigger>项目一</NavigationMenu.Trigger>

        <NavigationMenu.Content className="NavigationMenuContent">
          项目一内容
        </NavigationMenu.Content>
      </NavigationMenu.Item>

      <NavigationMenu.Item>
        <NavigationMenu.Trigger>项目二</NavigationMenu.Trigger>

        <NavigationMenu.Content className="NavigationMenuContent">
          项目二内容
        </NavigationMenu.Content>
      </NavigationMenu.Item>
    </NavigationMenu.List>

    <NavigationMenu.Viewport className="NavigationMenuViewport" />
  </NavigationMenu.Root>
);
```

```css
/* styles.css */

.NavigationMenuContent {
  position: absolute;

  top: 0;

  left: 0;

  animation-duration: 250ms;

  animation-timing-function: ease;
}

.NavigationMenuContent[data-motion="from-start"] {
  animation-name: enterFromLeft;
}

.NavigationMenuContent[data-motion="from-end"] {
  animation-name: enterFromRight;
}

.NavigationMenuContent[data-motion="to-start"] {
  animation-name: exitToLeft;
}

.NavigationMenuContent[data-motion="to-end"] {
  animation-name: exitToRight;
}

.NavigationMenuViewport {
  position: relative;

  width: var(--radix-navigation-menu-viewport-width);

  height: var(--radix-navigation-menu-viewport-height);

  transition:
    width,
    height,
    250ms ease;
}

@keyframes enterFromRight {
  from {
    opacity: 0;

    transform: translateX(200px);
  }

  to {
    opacity: 1;

    transform: translateX(0);
  }
}

@keyframes enterFromLeft {
  from {
    opacity: 0;

    transform: translateX(-200px);
  }

  to {
    opacity: 1;

    transform: translateX(0);
  }
}

@keyframes exitToRight {
  from {
    opacity: 1;

    transform: translateX(0);
  }

  to {
    opacity: 0;

    transform: translateX(200px);
  }
}

@keyframes exitToLeft {
  from {
    opacity: 1;

    transform: translateX(0);
  }

  to {
    opacity: 0;

    transform: translateX(-200px);
  }
}
```

## [无障碍性](#无障碍性)

遵循 [`navigation` 角色要求](https://www.w3.org/TR/wai-aria-1.2/#navigation)。

### [与菜单栏的区别](#与菜单栏的区别)

`NavigationMenu` 不应与 `menubar` 混淆，尽管此原语在口头上使用 `menu` 来指代一组导航链接，但它并未使用 WAI-ARIA `menu` 角色。 这是因为 `menu` 和 `menubars` 的行为与最常见于桌面应用程序窗口的本机操作系统菜单类似，因此它们具有复杂的功能，例如复合焦点管理和首字符导航。

这些功能通常被认为是 [网站导航中不必要的](https://github.com/w3c/aria-practices/issues/353)，在最坏的情况下，可能会混淆熟悉成熟网站模式的用户。

请参见 W3C [Disclosure Navigation Menu](https://w3c.github.io/aria-practices/examples/disclosure/disclosure-navigation.html) 示例以获取更多信息。

### [链接使用和 aria-current](#链接使用和-aria-current)

在菜单中使用 `NavigationMenu.Link` 进行所有导航链接很重要，这不仅适用于主列表，也适用于通过 `NavigationMenu.Content` 渲染的任何内容。这将确保一致的键盘交互和无障碍性，同时也提供了 `active` 属性以设置 `aria-current` 和活动样式。 有关与第三方路由组件一起使用的更多信息，请参见 [此示例](/primitives/docs/components/navigation-menu#with-client-side-routing)。

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

| Key                     | Description                                                                                                                                                                       |
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Space``Enter`          | 当焦点在 `NavigationMenu.Trigger` 上时，打开内容。                                                                                                                                |
| `Tab`                   | 将焦点移动到下一个可聚焦元素。                                                                                                                                                    |
| `ArrowDown`             | 当 `horizontal` 并且焦点在打开的 `NavigationMenu.Trigger` 上时，将焦点移入 `NavigationMenu.Content`。 将焦点移到下一个 `NavigationMenu.Trigger` 或 `NavigationMenu.Link`。        |
| `ArrowUp`               | 将焦点移到上一个 `NavigationMenu.Trigger` 或 `NavigationMenu.Link`。                                                                                                              |
| `ArrowRight``ArrowLeft` | 当 `vertical` 并且焦点在打开的 `NavigationMenu.Trigger` 上时，将焦点移入其 `NavigationMenu.Content`。 将焦点移到下一个/上一个 `NavigationMenu.Trigger` 或 `NavigationMenu.Link`。 |
| `Home``End`             | 将焦点移到第一个/最后一个 `NavigationMenu.Trigger` 或 `NavigationMenu.Link`。                                                                                                     |
| `Esc`                   | 关闭打开的 `NavigationMenu.Content` 并将焦点移到其 `NavigationMenu.Trigger`。                                                                                                     |

<!--$-->

<!--/$-->
