# Radio 单选框

`MRadio` 与 `MRadioGroup` 的机制与 Checkbox 一组同源，差别只在取值：组维护的是**单值**，子项通过 `group.modelValue === props.value` 判断自己是否被选中，因此天然互斥。独立使用时 `MRadio` 退化为一个布尔开关。

## 代码演示

### 基础用法

上面是独立使用的 `MRadio`（`v-model` 为 `boolean`），下面是 `MRadioGroup` + 带 `value` 的子项（`v-model` 为单值）。

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

const accepted = ref(false)
const plan = ref<string | number | boolean>('m')
</script>

<template>
  <div class="m-row-demo--col">
    <MRadio v-model="accepted">独立使用</MRadio>

    <MRadioGroup v-model="plan">
      <MRadio value="s">S</MRadio>
      <MRadio value="m">M</MRadio>
      <MRadio value="l">L</MRadio>
    </MRadioGroup>
  </div>
</template>
```

### 单选框组

组的 `name` 会下发给所有子项，`vertical` 改为纵向排列，`color` / `size` 也由组统一控制。

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

const picked = ref<string | number | boolean>('standard')

const plans = [
  { value: 'basic', label: '基础版' },
  { value: 'standard', label: '标准版' },
  { value: 'pro', label: '专业版' },
]
</script>

<template>
  <div class="m-row-demo--col">
    <MRadioGroup
      v-model="picked"
      name="plan"
      color="secondary"
      size="large"
      vertical>
      <MRadio v-for="plan in plans" :key="plan.value" :value="plan.value">
        {{ plan.label }}
      </MRadio>
    </MRadioGroup>

    <span>当前选中值：{{ picked }}</span>
  </div>
</template>
```

### 禁用

既可以禁用组内某一个选项，也可以用组的 `disabled` 一次性禁用整组。

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

const picked = ref<string | number | boolean>('standard')
const groupPicked = ref<string | number | boolean>('a')
</script>

<template>
  <div class="m-row-demo--col">
    <MRadioGroup v-model="picked" name="plan-mixed">
      <MRadio value="basic">基础版</MRadio>
      <MRadio value="standard" disabled>标准版（单项禁用）</MRadio>
      <MRadio value="pro">专业版</MRadio>
    </MRadioGroup>

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

### 颜色与尺寸

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

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

const colors = ['primary', 'secondary', 'success', 'warning', 'error', 'info'] as const
const pickedColor = ref<string | number | boolean>('primary')

const small = ref<string | number | boolean>('small')
const normal = ref<string | number | boolean>('default')
const large = ref<string | number | boolean>('large')

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

<template>
  <div class="m-row-demo--col">
    <MRadioGroup v-model="pickedColor" name="colors" vertical>
      <MRadio v-for="color in colors" :key="color" :value="color" :color="color">
        {{ color }}
      </MRadio>
    </MRadioGroup>

    <div class="m-row-demo">
      <MRadioGroup v-model="small" name="size-small" size="small">
        <MRadio value="small">small</MRadio>
      </MRadioGroup>
      <MRadioGroup v-model="normal" name="size-default" size="default">
        <MRadio value="default">default</MRadio>
      </MRadioGroup>
      <MRadioGroup v-model="large" name="size-large" size="large">
        <MRadio value="large">large</MRadio>
      </MRadioGroup>
    </div>

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

### name 继承与原生表单

组把 `name` 下发给每个子项，所以单选框组可以直接参与原生表单提交，不需要额外的隐藏字段。

**演示说明：** 点击提交，`FormData.get('plan')` 取到的就是当前选中项的 `value`。

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

const picked = ref<string | number | boolean>('standard')
const submitted = ref('（尚未提交）')

function onSubmit(event: Event) {
  const data = new FormData(event.target as HTMLFormElement)
  submitted.value = String(data.get('plan') ?? '（空）')
}
</script>

<template>
  <form class="m-row-demo--col" @submit.prevent="onSubmit">
    <MRadioGroup v-model="picked" name="plan">
      <MRadio value="basic">基础版</MRadio>
      <MRadio value="standard">标准版</MRadio>
      <MRadio value="pro">专业版</MRadio>
    </MRadioGroup>

    <div class="m-row-demo">
      <MButton variant="contained" color="primary" type="submit">提交</MButton>
      <span>FormData.get('plan') = {{ submitted }}</span>
    </div>
  </form>
</template>
```

## API

### MRadio Props

| 属性 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `value` | `RadioValue` | `undefined`（未提供） | 单选值：在 `MRadioGroup` 内必须提供，用于与组激活值比对 |
| `modelValue` | `boolean` | `undefined`（未提供） | 独立使用时的选中状态（`v-model`，`boolean`）；组内忽略此值 |
| `name` | `string` | `undefined`（组内可继承） | 原生 `input` 的 `name`（表单语义 / 同组互斥） |
| `disabled` | `boolean` | `false` | 是否禁用 |
| `color` | `RadioColor` | `undefined`（组内继承，独立时回落 `'primary'`） | 选中态颜色 |
| `size` | `RadioSize` | `undefined`（组内继承，独立时回落 `'default'`） | 尺寸 |

### MRadioGroup Props

| 属性 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `modelValue` | `RadioValue` | `undefined`（未提供） | 当前选中值（`v-model`） |
| `name` | `string` | `undefined`（未提供） | 原生 `input` 的 `name`，下发给组内所有 `MRadio` |
| `disabled` | `boolean` | `false` | 是否禁用整组 |
| `color` | `RadioColor` | `undefined`（内部回落 `'primary'`） | 组内统一颜色 |
| `size` | `RadioSize` | `undefined`（内部回落 `'default'`） | 组内统一尺寸 |
| `vertical` | `boolean` | `false` | 纵向排列：默认横向 flex 排列 |

### MRadio Events

| 事件 | 参数 | 说明 |
| --- | --- | --- |
| `update:modelValue` | `value: boolean` | 独立使用时被选中（仅在选中时抛，取消不会抛） |
| `change` | `value: boolean` | 独立使用时被选中（仅在选中时抛） |

### MRadioGroup Events

| 事件 | 参数 | 说明 |
| --- | --- | --- |
| `update:modelValue` | `value: RadioValue` | 选中值变化（值未变化时不抛） |
| `change` | `value: RadioValue` | 选中值变化 |

### Slots

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

### 类型定义

```ts
export type RadioColor =
  | 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'info';
export type RadioSize = 'small' | 'default' | 'large';
export type RadioValue = string | number | boolean;

export interface RadioGroupContext {
  modelValue: Ref<RadioValue | undefined>;
  color: RadioColor;
  size: RadioSize;
  disabled: boolean;
  name?: string;
  updateValue: (value: RadioValue) => void;
}
```

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

### 组内使用：与 Checkbox 组的差异

| 维度 | `MCheckboxGroup` | `MRadioGroup` |
| --- | --- | --- |
| `v-model` 类型 | `CheckboxValue[]`（数组） | `RadioValue`（单值） |
| 子项选中判断 | `group.modelValue.includes(value)` | `group.modelValue === value` |
| 组内可否取消 | 可以（再次点击取消该项） | 不可以，只能切换到另一项 |

共同点：**组内子项都必须传 `value`**，且组内不再抛 `update:modelValue` / `change`，统一由组抛。

## 使用建议

::: tip 选中态会带居中的涟漪反馈
`MRadio` 默认挂载 `v-ripple`（居中模式），点击时有 Material 涟漪动画，不需要额外配置。
:::

::: warning 组内不传 value 会互斥失效
组内判断是 `group.modelValue === props.value`。不传 `value` 时每一项都用 `undefined` 参与比对，会出现「全部同时选中」或「怎么点都选不中」的表现。
:::

::: warning 同一页面里多个组请给不同的 name
`name` 会透传到原生 `input`。多个单选框组共用同一个 `name` 时，浏览器会把它们当成一个原生单选组，与组件自己的状态判断产生冲突。要么每组用不同的 `name`，要么都不传。
:::

::: warning 重复选同一项不会抛事件
`updateValue` 在 `value === props.modelValue` 时直接返回。因此点击已选中项不会触发 `change`，需要「点击即上报」的行为请在业务侧自行处理。
:::

## 相关组件

- [Checkbox 复选框](/components/checkbox)：同样的「组 + 子项」结构，但可多选。
- [RadioButtonGroup 按钮式单选组](/components/radio-button-group)：用按钮外观呈现单选的场景。
- [Select 选择器](/components/select)：选项较多、需要收起时改用下拉。
