# Fab 悬浮按钮

悬浮在界面之上、始终可达的主操作入口。`MFab` 有两种形态：默认的圆形图标按钮，以及 `extended` 打开的图标加文字胶囊。传入 `position` 后按钮会脱离文档流，固定在视口角落后随页面滚动保持可见。

## 代码演示

### 基础用法

不传 `position` 时按钮留在文档流里，跟普通块级元素一样参与布局。

**演示说明：** `size` 的档位是 `small` / `medium` / `large`（注意不是 `default`），对应 40 / 56 / 72px。

```vue
<script setup lang="ts">
import { toast } from 'miao-design'

function handleClick(label: string) {
  toast.success(`点击了 ${label}`)
}
</script>

<template>
  <!-- 不传 position 时按钮留在文档流里，这里用舞台容器框住演示区域 -->
  <div class="m-demo-stage" style="min-height: 88px">
    <div class="m-row-demo">
      <MFab color="primary" size="small" aria-label="新增" @click="handleClick('small')" />
      <MFab color="secondary" size="medium" aria-label="新增" @click="handleClick('medium')" />
      <MFab color="success" size="large" aria-label="新增" @click="handleClick('large')" />
    </div>
  </div>
</template>
```

### 四角定位

`position` 取四个角之一时按钮改用 `fixed` 定位，距视口边缘 24px。

**演示说明：** 真实使用时按钮固定在视口角落；这里为了演示放在相对定位容器内，舞台上的 `transform` 会让 `fixed` 按钮以舞台为参照。

```vue
<script setup lang="ts">
import { toast } from 'miao-design'

const corners = [
  { position: 'top-left', color: 'info' },
  { position: 'top-right', color: 'warning' },
  { position: 'bottom-left', color: 'secondary' },
  { position: 'bottom-right', color: 'primary' },
] as const

function handleClick(position: string) {
  toast.success(`position="${position}"`)
}
</script>

<template>
  <!-- position 生效时按钮是 fixed 定位；给舞台加 transform 让它们以舞台为定位参照，不遮挡页面 -->
  <div class="m-demo-stage" style="transform: translateZ(0)">
    <MFab
      v-for="corner in corners"
      :key="corner.position"
      :position="corner.position"
      :color="corner.color"
      :aria-label="corner.position"
      @click="handleClick(corner.position)"
    />
  </div>
</template>
```

### 扩展模式

`extended` 把按钮变成图标加文字的胶囊，适合能明确写出动作名的场景。

**演示说明：** `icon` 插槽可以替换内置的加号图标，默认插槽渲染文字标签。

```vue
<script setup lang="ts">
import { mdiAccountPlusOutline, mdiCloudUploadOutline, mdiPencilOutline } from '@mdi/js'
import { toast } from 'miao-design'

const actions = [
  { icon: mdiPencilOutline, label: '写一篇' },
  { icon: mdiAccountPlusOutline, label: '加成员' },
  { icon: mdiCloudUploadOutline, label: '上传文件' },
]

function handleClick(label: string) {
  toast.success(label)
}
</script>

<template>
  <div class="m-demo-stage">
    <div class="m-row-demo">
      <MFab
        v-for="action in actions"
        :key="action.label"
        color="primary"
        :extended="true"
        :aria-label="action.label"
        @click="handleClick(action.label)"
      >
        <template #icon>
          <MIcon :path="action.icon" :size="20" />
        </template>
        {{ action.label }}
      </MFab>
    </div>
  </div>
</template>
```

### 禁用

`disabled` 会同时设置原生 `disabled` 与禁用样式，点击事件不再触发。

**演示说明：** 点击下方按钮可以实时切换禁用状态，观察阴影与背景色的变化。

```vue
<script setup lang="ts">
import { ref } from 'vue'
import { mdiPlusCircleOutline } from '@mdi/js'

const disabled = ref(true)
</script>

<template>
  <div class="m-demo-stage">
    <div class="m-row-demo">
      <MFab color="primary" size="small" :disabled="disabled" aria-label="新增" />
      <MFab color="primary" size="medium" :disabled="disabled" aria-label="新增" />
      <MFab color="primary" size="large" :disabled="disabled" aria-label="新增" />
      <MFab color="primary" :disabled="disabled" :extended="true" aria-label="新建">
        <template #icon>
          <MIcon :path="mdiPlusCircleOutline" :size="20" />
        </template>
        新建
      </MFab>
    </div>
    <div class="m-row-demo">
      <MButton variant="outlined" color="primary" @click="disabled = !disabled">
        {{ disabled ? '恢复可用' : '切换为禁用' }}
      </MButton>
    </div>
  </div>
</template>
```

## API

### Props

| 属性 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `color` | `'primary' \| 'secondary' \| 'success' \| 'warning' \| 'error' \| 'info'` | `'primary'` | 语义色 |
| `size` | `'small' \| 'medium' \| 'large'` | `'medium'` | 尺寸，对应 40 / 56 / 72px |
| `position` | `'bottom-right' \| 'bottom-left' \| 'top-right' \| 'top-left' \| ''` | `''` | 悬浮定位；传值后使用 `fixed` 定位并贴到对应角落，距视口边缘 24px；空字符串表示留在文档流 |
| `extended` | `boolean` | `false` | 扩展模式：图标与文字横排的胶囊形 |
| `disabled` | `boolean` | `false` | 是否禁用 |
| `ariaLabel` | `string` | `undefined` | 无障碍标签，纯图标无文字时建议传入 |

### Events

| 事件 | 参数 | 说明 |
| --- | --- | --- |
| `click` | `event: MouseEvent` | 点击事件；`disabled` 时不触发 |

### Slots

| 插槽 | 参数 | 说明 |
| --- | --- | --- |
| `icon` | 无 | 自定义图标；未提供时渲染内置加号图标 |
| `default` | 无 | 文字标签；`extended` 为 `true` 或存在默认插槽内容时渲染 |

## 使用建议

::: tip 纯图标按钮记得写 aria-label
默认插槽为空时按钮里只有一个加号图标，屏幕阅读器读不出含义，请通过 `ariaLabel` 补上动作名。

```vue
<MFab color="primary" position="bottom-right" aria-label="新建任务" />

<MFab color="primary" :extended="true" aria-label="新建任务">
  <template #icon>
    <MIcon :path="mdiCloudUploadOutline" :size="20" />
  </template>
  新建任务
</MFab>
```
:::

::: tip 组件自带涟漪
`MFab` 内部已经挂好了 `v-ripple`，按下即有 Material 水波纹，不需要再自己加指令。
:::

::: warning position 会让按钮脱离文档流
`position` 非空时按钮是 `position: fixed`，会脱离父容器的布局参与，父级的 `overflow: hidden` 也拦不住它。一个页面里通常只放一个位置固定的 FAB；同类动作不要在多处重复固定，否则会互相叠加遮挡。

另外 `position: fixed` 只认「视口」或「带 transform / filter / contain 的祖先」作为定位参照，普通的 `position: relative` 祖先不会把它框住——这也是文档演示里给舞台加 `transform` 的原因。
:::

## 相关组件

- [Button 按钮](/components/button)：常规尺寸的操作用 `MButton`，FAB 用于全局唯一的强操作。
- [Icon 图标](/components/icon)：`icon` 插槽里通常放一个 `MIcon`。
