# 轮播

## 使用方法

`Carousel` 把一行内容横向滚过去。根节点是 `region`，`aria-roledescription` 为 `carousel`。每个直接子节点是一张幻灯片，名称为「第 N 张幻灯片，共 M 张」。

`gap` 是间距档（`0`、`0.5`、`1`、`1.5`、`2`、`3`、`4`）。`padding` 用同一套档位，并同时设置 scroll-padding，吸附点会和内容边缘对齐。`hasSnap` 让每一项贴到起始边。`hasEdgeFade` 在仍有溢出的那一侧加上渐隐。

`hasButtons` 在还能滚动时显示上一张和下一张。不能再滚的那一侧按钮会隐藏并禁用。如果焦点正在这个按钮上，会移到另一侧。`hasLoop` 从最后一项回到第一项，内容溢出时两端按钮都保持可用。按住 Shift 滚动滚轮会改成横向滚动。

模板 ref 上可以调用 `scrollNext`、`scrollPrev`、`scrollTo`、`canScrollNext`、`canScrollPrev`。

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

<template>
  <Carousel :gap="2" has-snap label="功能卡片" style="max-width: 520px">
    <Card :width="200" :min-height="100">
      设计系统
    </Card>
    <Card :width="200" :min-height="100">
      文档
    </Card>
  </Carousel>
</template>
```

## 建议

| 建议 | 做法 |
| --- | --- |
| 推荐 | 用 `label` 给这组内容起名，例如「功能卡片」。 |
| 推荐 | 每张卡片用稳定宽度，一次滚动才看得到固定的几张。 |
| 推荐 | 希望停在某一项上、而不是停在两项中间时，打开 `hasSnap`。 |
| 避免 | 只放一项而且放得下。按钮不会出现，这一行就只是普通横排。 |
| 避免 | 内容溢出时同时关掉按钮和边缘渐隐。至少留一个，让人知道旁边还有内容。 |

### 结构

- **滚动容器**: 装着所有项的横向溢出区域。
- **项**: 默认插槽，在滚动容器里排成一行。开了 hasSnap 时，每一项会吸附到起始边。
- **边缘渐隐**: 左右边缘的渐变，表示后面还有内容。默认开启，把 hasEdgeFade 设为 false 就关掉。
- **导航按钮**: 内容还能往某个方向滚时出现的上一张和下一张按钮。默认开启，把 hasButtons 设为 false 就关掉。

## 示例

### Carousel — 卡片

一行可以横向滑动、并吸附对齐的卡片。适合功能列表、商品列表，或任何超出可用宽度的卡片。轮播会自动加上边缘渐隐和导航按钮。

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

const features = [
  { title: 'Design System', desc: 'Tokens, components, and patterns' },
  { title: 'Documentation', desc: 'API reference and usage guides' },
  { title: 'Sandbox', desc: 'Visual testing and previews' },
  { title: 'Library', desc: 'Component and hook information' },
  { title: 'Contributing', desc: 'Open source development' },
]
</script>

<template>
  <Stack direction="vertical" :gap="3" :max-width="520" style="padding: 8px">
    <Text type="body" weight="bold">
      Browse features
    </Text>
    <Carousel :gap="2" has-snap label="Feature cards">
      <Card v-for="item in features" :key="item.title" :width="200" :min-height="100">
        <Stack direction="vertical" :gap="1">
          <Text type="body" weight="bold">
            {{ item.title }}
          </Text>
          <Text type="supporting" color="secondary">
            {{ item.desc }}
          </Text>
        </Stack>
      </Card>
    </Carousel>
  </Stack>
</template>
```

### Carousel — 吸附

带导航按钮和成员卡片的吸附轮播。滚动后每张卡片贴到起始边。适合一次看一项，而不是连成一条看。

```vue
<script setup lang="ts">
import { Avatar } from '@astryx-vue/core/Avatar'
import { Badge } from '@astryx-vue/core/Badge'
import { Card } from '@astryx-vue/core/Card'
import { Carousel } from '@astryx-vue/core/Carousel'
import { Stack } from '@astryx-vue/core/Stack'
import { Text } from '@astryx-vue/core/Text'

const team = [
  { name: 'Alice Chen', role: 'Engineering Lead', color: 'blue' as const },
  { name: 'Bob Smith', role: 'Product Designer', color: 'purple' as const },
  { name: 'Carol Davis', role: 'Product Manager', color: 'green' as const },
  { name: 'Andrew Thomas', role: 'Design Manager', color: 'red' as const },
  { name: 'Gina Wilson', role: 'Software Engineer', color: 'orange' as const },
]
</script>

<template>
  <Stack direction="vertical" :gap="3" :max-width="520" style="padding: 8px">
    <Text type="body" weight="bold">
      Team members
    </Text>
    <Carousel :gap="2" has-snap has-buttons label="Team members">
      <Card v-for="person in team" :key="person.name" :width="180" :min-height="140">
        <Stack direction="vertical" :gap="3" h-align="center">
          <Avatar :name="person.name" size="lg" />
          <Stack direction="vertical" :gap="1" h-align="center">
            <Text type="body" weight="bold">
              {{ person.name }}
            </Text>
            <Badge :variant="person.color">
              {{ person.role }}
            </Badge>
          </Stack>
        </Stack>
      </Card>
    </Carousel>
  </Stack>
</template>
```

## 属性

| 属性 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `gap` | `0 \| 0.5 \| 1 \| 1.5 \| 2 \| 3 \| 4` | `1` | 项与项之间的间距，按间距档取值。 |
| `hasButtons` | `boolean` | `true` | 内容溢出时显示上一张和下一张按钮。 |
| `hasEdgeFade` | `boolean` | `true` | 在仍被裁切的一侧加上渐隐。 |
| `hasLoop` | `boolean` | `false` | 从最后一项回到第一项。内容溢出时两端按钮都保持可用。 |
| `hasSnap` | `boolean` | `false` | 每个直接子节点吸附到起始边。 |
| `padding` | `SpacingStep` | — | 滚动区域的行内内边距，并同步设置 scroll-padding。 |
| `label` | `string` | `Carousel` | 区域的无障碍名称。默认文案随语言变化，中文为「轮播」。 |

## 暴露的方法

| 名称 | 签名 | 说明 |
| --- | --- | --- |
| `scrollNext` | `() => void` | 向前滚动大约一屏。打开 `hasLoop` 时，到末尾会回到开头。 |
| `scrollPrev` | `() => void` | 向后滚动大约一屏。打开 `hasLoop` 时，到开头会回到末尾。 |
| `scrollTo` | `(index: number) => void` | 把从 0 开始的那一项滚到起始边。超出范围会夹到有效下标。只滚动轮播本身。 |
| `canScrollNext` | `() => boolean` | 是否还能向前滚。打开 `hasLoop` 时，只要内容溢出就为 true。 |
| `canScrollPrev` | `() => boolean` | 是否还能向后滚。打开 `hasLoop` 时，只要内容溢出就为 true。 |

## 插槽

- `default`：轮播项。每个顶层节点是一张幻灯片。Fragment 会展开，空白文本会跳过。

## 主题

根类名是 `astryx-carousel`。滚动区域是 `.astryx-carousel__scroller`。

- `--astryx-carousel-gap`：项间距，由 `gap` 写入。
- `--astryx-carousel-padding`：行内内边距和 scroll-padding，由 `padding` 写入。
