# Toolbar

## Usage

`Toolbar` is a `role="toolbar"`. `label` is required. `start`, `center`, and `end` are three groups. Arrow keys move focus across them, and Home and End jump to the ends. While an input is being edited, arrows stay with the caret until it reaches the edge of the field.

Hand overflow to `OverflowList`. Extra actions go into a More menu. The toolbar itself does not measure width.

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

<template>
  <Toolbar label="Formatting">
    <template #start>
      <button type="button">
        Bold
      </button>
      <button type="button">
        Italic
      </button>
    </template>
  </Toolbar>
</template>
```

### Anatomy

- **Outer chrome**: Section-owned container that supplies the toolbar surface, padding, and selected divider edges.
- **Toolbar**: Named toolbar row that owns toolbar semantics and the start, optional center, and end layout.

## Best practices

| Guidance | Practice |
| --- | --- |
| Do | Name the toolbar for its job, for example “Formatting”. |
| Do | Put a long set of actions in an <code>OverflowList</code> instead of letting buttons wrap out of the bar. |
| Avoid | Do not nest a toolbar inside a toolbar. Arrow keys stop at the inner boundary. |

## Examples

### Bulk Actions

After a multi-select, the count stays at the start and batch actions stay at the end.

```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>
```

### Card Header

The card title sits on the left. Icon actions sit on the right.

```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>
```

### Sizes

Small, medium, and large side by side. Size cascades to the buttons inside.

```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>
```

### Table Filter

Search leads the row. Status, priority, and customer sit beside it, with a live result count.

```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>
```

### Tab Navigation

Tabs sit in the start group. One action sits at the end.

```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

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `label` | `string` | - | Accessible name of the toolbar. |
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Height. |
| `gap` | `SpacingStep` | `1` | Space between items in a group, on the spacing scale. |
| `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | Axis the arrow keys follow. |

## Toolbar slots

| Name | Description |
| --- | --- |
| `start` | Actions on the leading side. |
| `center` | Actions in the middle. Turns the bar into a three-column grid. |
| `end` | Actions on the trailing side. |
