# 工具栏

## 使用方法

`Toolbar` 是 `role="toolbar"`。`label` 必填。`start`、`center`、`end` 分成三组，方向键在组与组之间移动焦点，Home 和 End 跳到两端。焦点在输入框里时，方向键先留给输入框，直到光标顶到边界。

放不下的操作交给 `OverflowList`，多出来的项收进「更多」菜单。工具栏自己不测量宽度。

```vue
<script setup lang="ts">
import { Toolbar } from '@astryx-vue/core/Toolbar'
</script>

<template>
  <Toolbar label="格式">
    <template #start>
      <button type="button">
        加粗
      </button>
      <button type="button">
        斜体
      </button>
    </template>
  </Toolbar>
</template>
```

### 结构

- **外框**: 由分区拥有的容器，提供工具栏表面、内边距，以及选中的那些分隔边。
- **工具栏**: 带工具栏语义的一行，拥有起始、可选的中间和末尾布局。

## 最佳实践

| 指引 | 实践 |
| --- | --- |
| 推荐 | 给工具栏一个能说明用途的 <code>label</code>，例如「格式」。 |
| 推荐 | 项很多时把它们放进 <code>OverflowList</code>，不要让按钮换行挤出容器。 |
| 避免 | 不要在工具栏里再套一个工具栏。方向键会停在内层边界上。 |

## 示例

### 批量操作

选中多项后，起始边显示数量，结束边放批量操作。

```vue
<script setup lang="ts">
import { Badge } from '@astryx-vue/core/Badge'
import { Button } from '@astryx-vue/core/Button'
import { CheckboxInput } from '@astryx-vue/core/CheckboxInput'
import { Toolbar } from '@astryx-vue/core/Toolbar'
import { computed, ref } from 'vue'

const rows = [
  { id: '1', name: 'Alex Johnson', role: 'Admin', status: 'Active' },
  { id: '2', name: 'Sam Rivera', role: 'Editor', status: 'Active' },
  { id: '3', name: 'Jordan Lee', role: 'Viewer', status: 'Invited' },
  { id: '4', name: 'Taylor Kim', role: 'Editor', status: 'Active' },
  { id: '5', name: 'Casey Park', role: 'Viewer', status: 'Active' },
]
const selected = ref(new Set(['1', '3', '5']))
const count = computed(() => selected.value.size)
const headerChecked = computed(() => {
  if (count.value === 0)
    return false
  if (count.value === rows.length)
    return true
  return 'indeterminate' as const
})

function setRow(id: string, checked: boolean | 'indeterminate'): void {
  const next = new Set(selected.value)
  if (checked === true)
    next.add(id)
  else
    next.delete(id)
  selected.value = next
}

function setAll(checked: boolean | 'indeterminate'): void {
  selected.value = checked === true ? new Set(rows.map(row => row.id)) : new Set()
}

function clear(): void {
  selected.value = new Set()
}
</script>

<template>
  <div class="nav-frame nav-frame--fill">
    <Toolbar v-if="count > 0" :dividers="['bottom']" label="Bulk actions" size="sm" variant="muted">
      <template #start>
        <Badge>{{ count }} selected</Badge>
        <Button is-icon-only label="Delete" size="sm" variant="ghost">
          <template #icon>
            <span class="i-carbon-trash-can" />
          </template>
        </Button>
        <Button is-icon-only label="Archive" size="sm" variant="ghost">
          <template #icon>
            <span class="i-carbon-archive" />
          </template>
        </Button>
      </template>
      <template #end>
        <Button label="Deselect all" size="sm" variant="ghost" @click="clear" />
      </template>
    </Toolbar>
    <table class="nav-bulk-table">
      <thead>
        <tr>
          <th>
            <CheckboxInput
              is-label-hidden
              label="Select all rows"
              :model-value="headerChecked"
              size="sm"
              @update:model-value="setAll"
            />
          </th>
          <th>Name</th>
          <th>Status</th>
          <th>Role</th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="row in rows" :key="row.id" :class="{ 'is-selected': selected.has(row.id) }">
          <td>
            <CheckboxInput
              is-label-hidden
              :label="`Select ${row.name}`"
              :model-value="selected.has(row.id)"
              size="sm"
              @update:model-value="setRow(row.id, $event)"
            />
          </td>
          <td>{{ row.name }}</td>
          <td>{{ row.status }}</td>
          <td>{{ row.role }}</td>
        </tr>
      </tbody>
    </table>
  </div>
</template>
```

### 卡片标题

卡片标题在左，图标操作在右。

```vue
<script setup lang="ts">
import { Button } from '@astryx-vue/core/Button'
import { Card } from '@astryx-vue/core/Card'
import { Heading } from '@astryx-vue/core/Heading'
import { Toolbar } from '@astryx-vue/core/Toolbar'
</script>

<template>
  <Card width="min(100%, 28rem)">
    <Toolbar :dividers="['bottom']" label="User list actions" size="sm">
      <template #start>
        <Heading :level="4" display="inline" type="label">
          Card title
        </Heading>
      </template>
      <template #end>
        <Button is-icon-only label="Filter" size="sm" variant="ghost">
          <template #icon>
            <span class="i-carbon-filter" />
          </template>
        </Button>
        <Button is-icon-only label="Add user" size="sm">
          <template #icon>
            <span class="i-carbon-add" />
          </template>
        </Button>
      </template>
    </Toolbar>
  </Card>
</template>
```

### 尺寸

小、中、大三档并排。尺寸会传给里面的按钮。

```vue
<script setup lang="ts">
import { Button } from '@astryx-vue/core/Button'
import { Card } from '@astryx-vue/core/Card'
import { Heading } from '@astryx-vue/core/Heading'
import { Toolbar } from '@astryx-vue/core/Toolbar'

const sizes = [
  { label: 'Small', size: 'sm' as const },
  { label: 'Medium', size: 'md' as const },
  { label: 'Large', size: 'lg' as const },
]
</script>

<template>
  <div class="nav-stack">
    <Card v-for="item in sizes" :key="item.size">
      <Toolbar :label="`${item.label} toolbar`" :size="item.size">
        <template #start>
          <Heading :level="4" display="inline" type="label">
            {{ item.label }}
          </Heading>
        </template>
        <template #end>
          <Button is-icon-only label="Filter" :size="item.size" variant="ghost">
            <template #icon>
              <span class="i-carbon-filter" />
            </template>
          </Button>
          <Button label="Add" :size="item.size" />
        </template>
      </Toolbar>
    </Card>
  </div>
</template>
```

### 表格筛选

搜索在最前面，旁边是状态、优先级和客户，末尾是当前结果数量。

```vue
<script setup lang="ts">
import { Button } from '@astryx-vue/core/Button'
import { Selector } from '@astryx-vue/core/Selector'
import { Text } from '@astryx-vue/core/Text'
import { TextInput } from '@astryx-vue/core/TextInput'
import { Toolbar } from '@astryx-vue/core/Toolbar'
import { computed, ref } from 'vue'

const jobs = [
  { customer: 'Northgate Dental', id: 'SJ-2136', job: 'VAV zones not cooling', priority: 'High', status: 'In progress' },
  { customer: 'Riverside Club', id: 'SJ-2135', job: 'Exhaust fan noise', priority: 'Normal', status: 'On hold' },
  { customer: 'Summit Medical', id: 'SJ-2134', job: 'Humidifier board fault', priority: 'High', status: 'Overdue' },
  { customer: 'Northgate Dental', id: 'SJ-2133', job: 'Economizer stuck shut', priority: 'Normal', status: 'Scheduled' },
  { customer: 'Harborview Hotel', id: 'SJ-2101', job: 'Contactor replacement', priority: 'Low', status: 'Completed' },
]
const query = ref('')
const status = ref<string | null>(null)
const priority = ref<string | null>(null)
const customer = ref<string | null>(null)
const statusOptions = ['Scheduled', 'In progress', 'On hold', 'Overdue', 'Completed'].map(value => ({ label: value, value }))
const priorityOptions = ['High', 'Normal', 'Low'].map(value => ({ label: value, value }))
const customerOptions = ['Northgate Dental', 'Riverside Club', 'Summit Medical', 'Harborview Hotel'].map(value => ({ label: value, value }))
const visible = computed(() => jobs.filter((job) => {
  if (query.value && !job.job.toLowerCase().includes(query.value.toLowerCase()))
    return false
  if (status.value && job.status !== status.value)
    return false
  if (priority.value && job.priority !== priority.value)
    return false
  if (customer.value && job.customer !== customer.value)
    return false
  return true
}))

function clearFilters(): void {
  query.value = ''
  status.value = null
  priority.value = null
  customer.value = null
}
</script>

<template>
  <div class="nav-frame nav-frame--fill nav-filter">
    <Toolbar label="Table filter" size="sm">
      <template #start>
        <TextInput v-model="query" is-label-hidden label="Search jobs" placeholder="Search jobs" size="sm" />
        <Selector v-model="status" label="Status" :options="statusOptions" placeholder="Status" size="sm" />
        <Selector v-model="priority" label="Priority" :options="priorityOptions" placeholder="Priority" size="sm" />
        <Selector v-model="customer" label="Customer" :options="customerOptions" placeholder="Customer" size="sm" />
        <Button v-if="query || status || priority || customer" label="Clear all" size="sm" variant="ghost" @click="clearFilters" />
      </template>
      <template #end>
        <Text color="secondary" type="supporting">
          {{ visible.length }} of {{ jobs.length }}
        </Text>
      </template>
    </Toolbar>
    <table class="nav-bulk-table">
      <thead>
        <tr>
          <th>Job</th>
          <th>Customer</th>
          <th>Status</th>
          <th>Priority</th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="job in visible" :key="job.id">
          <td>{{ job.job }}</td>
          <td>{{ job.customer }}</td>
          <td>{{ job.status }}</td>
          <td>{{ job.priority }}</td>
        </tr>
      </tbody>
    </table>
  </div>
</template>
```

### 标签导航

起始区是标签，结束区是一个操作。

```vue
<script setup lang="ts">
import { Button } from '@astryx-vue/core/Button'
import { Card } from '@astryx-vue/core/Card'
import { Tab, TabList } from '@astryx-vue/core/TabList'
import { Toolbar } from '@astryx-vue/core/Toolbar'
import { ref } from 'vue'

const tab = ref('overview')
</script>

<template>
  <Card width="min(100%, 32rem)">
    <Toolbar :dividers="['bottom']" label="Section navigation">
      <template #start>
        <TabList v-model:value="tab" role="tablist">
          <Tab value="overview">
            Overview
          </Tab>
          <Tab value="analytics">
            Analytics
          </Tab>
          <Tab value="settings">
            Settings
          </Tab>
        </TabList>
      </template>
      <template #end>
        <Button is-icon-only label="New item">
          <template #icon>
            <span class="i-carbon-add" />
          </template>
        </Button>
      </template>
    </Toolbar>
  </Card>
</template>
```

## Toolbar Props

| 名称 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `label` | `string` | - | 工具栏的无障碍名称。 |
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | 高度档。 |
| `gap` | `SpacingStep` | `1` | 每一组里各项的间距，使用间距刻度。 |
| `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | 方向键沿哪一根轴移动焦点。 |

## Toolbar Slots

| 名称 | 说明 |
| --- | --- |
| `start` | 起始一侧的操作。 |
| `center` | 中间的操作。存在时三组按网格排列。 |
| `end` | 结束一侧的操作。 |
