# 侧边栏

侧边栏由你自己写：遍历文档树，用 `Sidebar` 组件渲染每一项。结构、顺序和每一项的样子都在你的代码里。

## 完整的例子

```astro
---
import { Sidebar } from '@minidocs/astro/components';
import { navState } from '@minidocs/astro/runtime';
import { tree } from '../lib/docs';

const { id } = Astro.props;
const nav = navState(tree, id);
---

<Sidebar.Root id="sidebar" label="文档">
  <Sidebar.List>
    {tree.map(function item(node) {
      return (
        <Sidebar.Item>
          {node.children.length > 0 ? (
            <Sidebar.Group {...nav.of(node)}>
              <Fragment slot="label">{node.label}</Fragment>
              <Sidebar.List>{node.children.map(item)}</Sidebar.List>
            </Sidebar.Group>
          ) : (
            <Sidebar.Link {...nav.of(node)}>{node.label}</Sidebar.Link>
          )}
        </Sidebar.Item>
      );
    })}
  </Sidebar.List>
</Sidebar.Root>
```

`tree.map` 的回调起了名字 `item`，遇到有子节点的分组时，用 `node.children.map(item)` 渲染下一层，所以树有多少层都能渲染出来。多语言站点把 `tree` 换成 `tree[locale]`。

## `nav.of(node)`

`navState(tree, id)` 记下当前页面在树里的位置，`nav.of(node)` 返回渲染一个节点要用的值：

| 字段        | 含义                 |
| --------- | ------------------ |
| `href`    | 页面或外部链接的地址，分组没有    |
| `current` | 这个节点是不是当前页面        |
| `open`    | 当前页面是不是这个节点本身或在它下面 |

这些字段和组件的参数同名，所以可以直接展开。也可以改写其中的值，比如让某个分组始终展开：

```astro
<Sidebar.Group {...nav.of(node)} open>
```

每个组件的参数见[组件与样式](/docs/reference/components.md#%E4%BE%A7%E8%BE%B9%E6%A0%8F)。

## 分组的几种样子

### 小标题

第一层的分组常常只是一个小标题，下面直接列出页面，不能折叠。用 `Sidebar.Label` 加 `Sidebar.List`。下面的写法在第一层用小标题，更深的层级用可折叠的分组：

```astro
{tree.map(function item(node) {
  return (
    <Sidebar.Item>
      {node.children.length === 0 ? (
        <Sidebar.Link {...nav.of(node)}>{node.label}</Sidebar.Link>
      ) : tree.includes(node) ? (
        <>
          <Sidebar.Label>{node.label}</Sidebar.Label>
          <Sidebar.List>{node.children.map(item)}</Sidebar.List>
        </>
      ) : (
        <Sidebar.Group {...nav.of(node)}>
          <Fragment slot="label">{node.label}</Fragment>
          <Sidebar.List>{node.children.map(item)}</Sidebar.List>
        </Sidebar.Group>
      )}
    </Sidebar.Item>
  );
})}
```

### 可折叠的分组

`Sidebar.Group` 渲染成可以折叠的分组，点击标题展开或收起，上面的完整例子用的就是它。分组的标题写在 `label` slot 里。

### 手风琴

给同一层的分组传相同的 `name`，展开一个时其他的会自动收起。有自己页面的分组不参与：

```astro
<Sidebar.Group {...nav.of(node)} name="sections">
```

### 有自己页面的分组

用 `page(id, children)` 定义的分组本身也是一个页面，`nav.of(node)` 会给它 `href`。它的标题在不同页面上表现不同：

| 当前页面    | 点击标题         |
| ------- | ------------ |
| 其他页面    | 跳转到分组的页面，并展开 |
| 分组下面的页面 | 跳转到分组的页面     |
| 分组自己的页面 | 收起或展开        |

## 跨页面保持状态

从一个页面跳到另一个页面时，侧边栏会保持原来的样子：展开的分组和滚动位置都不变。当前页面所在的分组总是展开的，这样你总能在侧边栏里看到自己在哪。

## 窄屏下的菜单

`Sidebar.Trigger` 是打开和关闭侧边栏的按钮，`target` 是侧边栏的 `id`：

```astro
<Sidebar.Trigger target="sidebar" aria-label="菜单">☰</Sidebar.Trigger>
<Sidebar.Root id="sidebar" label="文档">...</Sidebar.Root>
```

侧边栏放在 `Layout.Sidebar` 里时，默认样式在窄屏下把它变成从左侧滑出的抽屉，按 Esc 或点击外面会关闭，见[页面布局](/docs/guides/layout.md)。
