# 对话输入抽屉

## 使用方法

`ChatComposerDrawer` 放在 `ChatComposer` 的 `drawer` 插槽里，用来装附件、上下文标签、预览这类补充内容。只有当你希望用户能把这部分收起来时，才传 `count`。

```ts
import { ChatComposerDrawer } from '@astryx-vue/core/Chat'
```

### 结构

- **抽屉表面**: 抽屉本身，叠在对话输入区主体之上。
- **收起开关**: 传了 count 才出现，键盘和鼠标都能操作。
- **收起摘要**: 收起时显示的东西：默认是总数徽标加一行文字，也可以换成调用方给的内容。
- **内容区**: 调用方给的补充内容；收起来时里面的东西键盘和读屏都碰不到。

## 建议

| 指引 | 实践 |
| --- | --- |
| 推荐 | `label` 用一个具体的复数名词，例如 “Attachments”，这样“展开”“收起”读出来才有意义。 |
| 推荐 | 抽屉的开关状态归页面别处管时，用 `v-model:is-collapsed` 配合 `collapsedChange`；否则用 `defaultIsCollapsed`。 |
| 避免 | 把输入区的主要操作放进抽屉。这类控件要用 `ChatComposer` 的 `footerActions` 或 `sendActions`，抽屉收起来时它们还在。 |

## 示例

常见的用法、变化和状态。

### ChatComposerDrawer — 附件

抽屉里有两行：一行是可以横向滚动的图片缩略图，一行是可以逐个删掉的文件标签。不传 count，抽屉就一直展开。

```vue
<script setup lang="ts">
import { Carousel } from '@astryx-vue/core/Carousel'
import { ChatComposer, ChatComposerDrawer } from '@astryx-vue/core/Chat'
import { HStack, VStack } from '@astryx-vue/core/Stack'
import { Thumbnail } from '@astryx-vue/core/Thumbnail'
import { Token } from '@astryx-vue/core/Token'
import { ref } from 'vue'

const images = ref([
  { id: '1', src: '/template-assets/illustrative-vertical-1.png', alt: 'River through a valley', label: 'valley.jpg' },
  { id: '2', src: '/template-assets/light-working-vertical-1.png', alt: 'Foggy mountain peak', label: 'mountain.jpg' },
  { id: '3', src: '/template-assets/light-working-vertical-2.png', alt: 'Golden retriever puppy', label: 'puppy.jpg' },
  { id: '4', src: '/template-assets/illustrative-horizontal-1.png', alt: 'Bridge at sunset', label: 'bridge.jpg' },
  { id: '5', src: '/template-assets/light-scene-horizontal-1.png', alt: 'Lakeside at dusk', label: 'lakeside.jpg' },
])

const files = ref(['quarterly-report.pdf', 'budget-forecast.xlsx'])

function removeImage(id: string): void {
  images.value = images.value.filter(item => item.id !== id)
}

function remove(name: string): void {
  files.value = files.value.filter(item => item !== name)
}
</script>

<template>
  <ChatComposer>
    <template #drawer>
      <ChatComposerDrawer>
        <VStack :gap="2" width="100%">
          <Carousel :gap="1">
            <Thumbnail
              v-for="image in images"
              :key="image.id"
              :src="image.src"
              :alt="image.alt"
              :label="image.label"
              @remove="removeImage(image.id)"
            />
          </Carousel>
          <HStack :gap="1" wrap="wrap">
            <Token
              v-for="name in files"
              :key="name"
              removable
              :label="name"
              @remove="remove(name)"
            />
          </HStack>
        </VStack>
      </ChatComposerDrawer>
    </template>
  </ChatComposer>
</template>
```

### ChatComposerDrawer — 可收起

条目很多、带收起开关的抽屉。传了 count 才有这个开关；收起来以后显示一个带总数的徽标和一行文字。

```vue
<script setup lang="ts">
import { ChatComposer, ChatComposerDrawer } from '@astryx-vue/core/Chat'
import { Token } from '@astryx-vue/core/Token'
import { ref } from 'vue'

const files = ref([
  'design-spec.pdf',
  'api-schema.json',
  'screenshot.png',
  'meeting-notes.md',
  'test-results.csv',
  'deploy-log.txt',
])

function remove(name: string): void {
  files.value = files.value.filter(item => item !== name)
}
</script>

<template>
  <ChatComposer>
    <template #drawer>
      <ChatComposerDrawer :count="files.length" label="Files">
        <Token
          v-for="name in files"
          :key="name"
          removable
          :label="name"
          @remove="remove(name)"
        />
      </ChatComposerDrawer>
    </template>
  </ChatComposer>
</template>
```

### ChatComposerDrawer — 确认反馈

抽屉里放一个确认提问和几个带字母的可选项。用户必须先明确选一个才能继续时用它。

```vue
<script setup lang="ts">
import { Badge } from '@astryx-vue/core/Badge'
import { ChatComposer, ChatComposerDrawer } from '@astryx-vue/core/Chat'
import { List, ListItem } from '@astryx-vue/core/List'
import { VStack } from '@astryx-vue/core/Stack'
import { Text } from '@astryx-vue/core/Text'
import { ref } from 'vue'

const options = [
  { key: 'A', label: 'Yes' },
  { key: 'B', label: 'Yes, and don’t ask again for `git add` commands' },
  { key: 'C', label: 'No, and tell me what to do differently' },
]

const selected = ref<string | null>(null)
</script>

<template>
  <ChatComposer>
    <template #drawer>
      <ChatComposerDrawer :count="1" label="User feedback requested">
        <VStack :gap="1" width="100%">
          <List>
            <ListItem>
              <template #label>
                <Text weight="bold">
                  Do you want to proceed?
                </Text>
              </template>
            </ListItem>
            <ListItem
              v-for="option in options"
              :key="option.key"
              :label="option.label"
              :is-selected="selected === option.key"
              @click="selected = option.key"
            >
              <template #startContent>
                <Badge :variant="selected === option.key ? 'info' : 'neutral'" :label="option.key" />
              </template>
            </ListItem>
          </List>
        </VStack>
      </ChatComposerDrawer>
    </template>
  </ChatComposer>
</template>
```

### ChatComposerDrawer — 带进度

抽屉配一条放在页眉的上下文进度条。附件吃掉了一部分可用字数时，用它把用量显示出来。

```vue
<script setup lang="ts">
import { ChatComposer, ChatComposerDrawer } from '@astryx-vue/core/Chat'
import { Icon } from '@astryx-vue/core/Icon'
import { IconButton } from '@astryx-vue/core/IconButton'
import { ProgressBar } from '@astryx-vue/core/ProgressBar'
import { Token } from '@astryx-vue/core/Token'
import { ref } from 'vue'

const files = ref(['design-spec.pdf', 'api-schema.json', 'screenshot.png'])

function remove(name: string): void {
  files.value = files.value.filter(item => item !== name)
}
</script>

<template>
  <ChatComposer>
    <template #drawer>
      <ChatComposerDrawer :count="files.length" label="Attachments">
        <Token
          v-for="name in files"
          :key="name"
          removable
          :label="name"
          @remove="remove(name)"
        />
      </ChatComposerDrawer>
    </template>
    <template #headerActions>
      <IconButton label="Mention" variant="ghost" size="sm">
        <Icon name="heroicons:at-symbol" />
      </IconButton>
      <IconButton label="Attach" variant="ghost" size="sm">
        <Icon name="heroicons:paper-clip" />
      </IconButton>
    </template>
    <template #headerContext>
      <ProgressBar label="Context usage" :value="42" :width="96" is-label-hidden />
    </template>
  </ChatComposer>
</template>
```

## 属性

| 属性 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `count` | `number` | — | 条目数量。传了它才出现收起开关，收起后也用它显示总数。不传，抽屉就一直展开。 |
| `label` | `string` | 本地化的“Items” | 收起后那行摘要用的名字，也是展开和收起两个无障碍名字里的一部分。 |
| `v-model:is-collapsed` | `boolean` | — | 受控的收起状态。绑上以后抽屉完全按这个值显示，开关只是提出变更请求。 |
| `defaultIsCollapsed` | `boolean` | `false` | 没绑 `isCollapsed` 时，初始是不是收起的。 |

## 事件

| 事件 | 参数 | 说明 |
| --- | --- | --- |
| `collapsedChange` | `boolean` | 开关希望你切换到的收起状态。`v-model:is-collapsed` 绑的就是它。 |

## 插槽

| 插槽 | 说明 |
| --- | --- |
| （默认） | 补充内容：附件标签、缩略图、提问等。排成会自动换行的一行。 |
| `collapsedSummary` | 换掉收起时显示的总数徽标和那行文字。展开收起的无障碍名字仍归开关管。 |
