# Checkbox 复选框

`MCheckbox` 与 `MCheckboxGroup` 是一对组合：单个复选框负责「是 / 否」，复选框组负责「从若干项里挑若干项」。组件内部通过 `provide / inject` 通信，因此同一个 `MCheckbox` 放在组内外时用的是两套完全不同的取值与抛事件逻辑。

## 代码演示

### 基础用法

上面是独立使用的 `MCheckbox`（`v-model` 为 `boolean`），下面是 `MCheckboxGroup` + 带 `value` 的子项（`v-model` 为数组）。

**演示说明：** 点击任意一项，观察自己维护的两个 `ref` 如何变化。

```vue
<script setup lang="ts">
import { ref } from 'vue'

const agree = ref(false)
const picked = ref<(string | number | boolean)[]>(['read'])
</script>

<template>
  <div class="m-row-demo--col">
    <MCheckbox v-model="agree">已阅读并同意《用户协议》</MCheckbox>

    <MCheckboxGroup v-model="picked">
      <MCheckbox value="read">可读</MCheckbox>
      <MCheckbox value="write">可写</MCheckbox>
    </MCheckboxGroup>
  </div>
</template>
```

### 复选框组

组的 `color` / `size` 会下发给所有子项，`vertical` 改为纵向排列，子项可用同名属性单独覆盖。

```vue
<script setup lang="ts">
import { ref } from 'vue'

const picked = ref<(string | number | boolean)[]>(['vue', 'ts'])

const stacks = [
  { value: 'vue', label: 'Vue' },
  { value: 'react', label: 'React' },
  { value: 'ts', label: 'TypeScript' },
  { value: 'rust', label: 'Rust' },
]
</script>

<template>
  <div class="m-row-demo--col">
    <MCheckboxGroup v-model="picked" color="secondary" size="large" vertical>
      <MCheckbox v-for="stack in stacks" :key="stack.value" :value="stack.value">
        {{ stack.label }}
      </MCheckbox>
    </MCheckboxGroup>

    <span>当前选中：{{ picked.join('、') || '（空数组）' }}</span>
  </div>
</template>
```

### 半选状态

`indeterminate` 只改变勾选角标的图形与 `aria-checked`，配合「全选」联动使用。

```vue
<script setup lang="ts">
import { computed, ref } from 'vue'

const items = ['设计', '开发', '测试']
const picked = ref<(string | number | boolean)[]>(['设计'])

const allChecked = computed(() => picked.value.length === items.length)
const indeterminate = computed(() => picked.value.length > 0 && !allChecked.value)

function toggleAll(checked: boolean) {
  picked.value = checked ? [...items] : []
}
</script>

<template>
  <div class="m-row-demo--col">
    <MCheckbox
      :model-value="allChecked"
      :indeterminate="indeterminate"
      @change="toggleAll">
      全选
    </MCheckbox>

    <MCheckboxGroup v-model="picked">
      <MCheckbox v-for="item in items" :key="item" :value="item">{{ item }}</MCheckbox>
    </MCheckboxGroup>

    <span>选中 {{ picked.length }} / {{ items.length }}</span>
  </div>
</template>
```

### 禁用

独立的复选框与整组都可以禁用，禁用后点击不再触发任何事件。

```vue
<script setup lang="ts">
import { ref } from 'vue'

const checked = ref(true)
const unchecked = ref(false)
const groupPicked = ref<(string | number | boolean)[]>(['a'])
</script>

<template>
  <div class="m-row-demo--col">
    <MCheckbox v-model="checked" disabled>独立禁用（已选中）</MCheckbox>

    <MCheckbox v-model="unchecked" disabled>独立禁用（未选中）</MCheckbox>

    <MCheckboxGroup v-model="groupPicked" disabled>
      <MCheckbox value="a">整组禁用 A</MCheckbox>
      <MCheckbox value="b">整组禁用 B</MCheckbox>
    </MCheckboxGroup>
  </div>
</template>
```

### 颜色与尺寸

六种语义色与三档尺寸，组内继承时可被单项覆盖。

```vue
<script setup lang="ts">
import { reactive, ref } from 'vue'

const colors = ['primary', 'secondary', 'success', 'warning', 'error', 'info'] as const
const byColor = reactive<Record<string, boolean>>({
  primary: true,
  secondary: true,
  success: true,
  warning: true,
  error: true,
  info: true,
})

const small = ref(true)
const normal = ref(true)
const large = ref(true)

const inherited = ref<(string | number | boolean)[]>(['a'])
</script>

<template>
  <div class="m-row-demo--col">
    <div class="m-row-demo">
      <MCheckbox v-for="color in colors" :key="color" v-model="byColor[color]" :color="color">
        {{ color }}
      </MCheckbox>
    </div>

    <div class="m-row-demo">
      <MCheckbox v-model="small" size="small" color="primary">small</MCheckbox>
      <MCheckbox v-model="normal" size="default" color="primary">default</MCheckbox>
      <MCheckbox v-model="large" size="large" color="primary">large</MCheckbox>
    </div>

    <MCheckboxGroup v-model="inherited" color="error" size="large">
      <MCheckbox value="a">继承组的 error / large</MCheckbox>
      <MCheckbox value="b" color="info" size="small">单项覆盖为 info / small</MCheckbox>
    </MCheckboxGroup>
  </div>
</template>
```

## API

### MCheckbox Props

| 属性 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `value` | `CheckboxValue` | `undefined`（未提供） | 组内项值：在 `MCheckboxGroup` 内用于维护选中数组 |
| `modelValue` | `boolean` | `undefined`（未提供） | 独立使用时的选中状态（`v-model`，`boolean`）；组内忽略此值 |
| `indeterminate` | `boolean` | `false` | 半选状态：勾选角标显示为横线（通常用于「全选」联动） |
| `name` | `string` | `undefined`（未提供） | 原生 `input` 的 `name` |
| `disabled` | `boolean` | `false` | 是否禁用 |
| `color` | `CheckboxColor` | `undefined`（组内继承，独立时回落 `'primary'`） | 选中态颜色 |
| `size` | `CheckboxSize` | `undefined`（组内继承，独立时回落 `'default'`） | 尺寸 |

### MCheckboxGroup Props

| 属性 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `modelValue` | `CheckboxValue[]` | `undefined`（内部按 `[]` 处理） | 选中值数组（`v-model`） |
| `disabled` | `boolean` | `false` | 是否禁用整组 |
| `color` | `CheckboxColor` | `undefined`（内部回落 `'primary'`） | 组内 `MCheckbox` 的统一颜色 |
| `size` | `CheckboxSize` | `undefined`（内部回落 `'default'`） | 组内 `MCheckbox` 的统一尺寸 |
| `vertical` | `boolean` | `false` | 纵向排列 |

### MCheckbox Events

| 事件 | 参数 | 说明 |
| --- | --- | --- |
| `update:modelValue` | `value: boolean` | 独立使用时选中状态变化 |
| `change` | `value: boolean` | 独立使用时选中状态变化 |

### MCheckboxGroup Events

| 事件 | 参数 | 说明 |
| --- | --- | --- |
| `update:modelValue` | `value: CheckboxValue[]` | 选中数组变化（勾选 / 取消） |
| `change` | `value: CheckboxValue[]` | 选中数组变化（勾选 / 取消） |

### Slots

| 插槽 | 参数 | 说明 |
| --- | --- | --- |
| `default` | 无 | `MCheckbox` 的标签文本；`MCheckboxGroup` 的子项列表 |

### 类型定义

```ts
export type CheckboxColor =
  | 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info';
export type CheckboxSize = 'small' | 'default' | 'large';
export type CheckboxValue = string | number | boolean;

export interface CheckboxGroupContext {
  modelValue: Ref<CheckboxValue[]>;
  color: CheckboxColor;
  size: CheckboxSize;
  disabled: boolean;
  toggleValue: (value: CheckboxValue, checked: boolean) => void;
}
```

`Expose`：两个组件源码均未暴露实例方法。

### 组内使用：值从哪来，事件由谁抛

| 场景 | `v-model` 类型 | 是否必须传 `value` | 事件由谁抛出 |
| --- | --- | --- | --- |
| 独立使用 | `boolean` | 不需要 | `MCheckbox` 自己抛 `update:modelValue` / `change` |
| 放在 `MCheckboxGroup` 内 | `CheckboxValue[]`（绑定在组上） | **必须** | 只有 `MCheckboxGroup` 抛，子组件不抛 |

组内子项的选中状态由 `group.modelValue.value.includes(props.value)` 计算，勾选 / 取消统一走组的 `toggleValue(value, checked)`。

## 使用建议

::: tip 半选是纯展示状态
`indeterminate` 只影响角标图形与 `aria-checked="mixed"`，**不参与** `checked` 的计算，也不会随点击自动清除。搭配「全选」使用时，请把 `indeterminate` 写成由选中数量推导的 `computed`：

```vue
<MCheckbox
  :model-value="allChecked"
  :indeterminate="picked.length > 0 && !allChecked"
  @change="(checked) => (picked = checked ? [...all] : [])" />
```
:::

::: warning 组内不传 value 会出错
组内子项的选中判断是 `includes(props.value)`。不传 `value` 时每一项都以 `undefined` 参与比对，会出现「点一项全部选中」的错误表现。放进组就必须给 `value`。
:::

::: warning 组内子组件不再抛事件
在 `MCheckboxGroup` 里给子项写 `@change` 是无效的——子组件只在独立使用时才 `emit`，组内一律交给组的 `toggleValue` 处理。需要感知变化请监听组的事件。
:::

::: warning 组禁用会吞掉点击
组 `disabled` 为真时 `toggleValue` 直接返回，子项的 `disabled` 也会被置为禁用态。此时子项的 `@change` 不会被触发。
:::

## 相关组件

- [Radio 单选框](/components/radio)：同样「组 + 子项」的机制，但取值为单值、互斥。
- [Switch 开关](/components/switch)：只需一个布尔开关时比复选框更醒目。
- [Select 选择器](/components/select)：选项数量多到不适合全部铺开时改用下拉。
