feat(components): [radio-group] support options prop rendering (#21543)

* feat(components): [radio-group] support options

* test: add test case

* docs: tweak doc

* Update basic-usage.vue

* docs: tweak doc

* Update packages/components/radio/src/radio-group.vue

Co-authored-by: Noblet Ouways <91417411+Dsaquel@users.noreply.github.com>

* chore: label prop

* Update packages/components/radio/src/radio-group.ts

Co-authored-by: Noblet Ouways <91417411+Dsaquel@users.noreply.github.com>

* Update packages/components/radio/src/radio-group.vue

Co-authored-by: btea <2356281422@qq.com>

* refactor: use ts logic

* Update form.md

* Update message.ts

* refactor: fix effect lost

* refactor: use template logic and update version

* Update radio-group.ts

* Update options.vue

* Update options.vue

* refactor: rename props and  support additional attributes and  render

* chore: default key

* chore: use optionProps

* chore: fix build error

* chore: fix build error

* chore: fix build error

* chore: fix build error

* chore: fix build error

* Update radio-group.vue

* Update basic.vue

* refactor: refer checkbox

* Update pnpm-lock.yaml

* Update pnpm-workspace.yaml

* Update package.json

* Update package.json

* chore: ts error

* Update radio-group.ts

* Update radio-group.ts

* refactor: add more attr support and update version

* refactor: props consistent with select

* Update radio.md

* Update packages/components/radio/src/radio-group.vue

Co-authored-by: kooriookami <38392315+kooriookami@users.noreply.github.com>

* Update packages/components/radio/src/radio-group.vue

Co-authored-by: Noblet Ouways <91417411+Dsaquel@users.noreply.github.com>

* chore: type with single line

* chore: fix build error

* chore: delete radioRenderer testcase

---------

Co-authored-by: Noblet Ouways <91417411+Dsaquel@users.noreply.github.com>
Co-authored-by: btea <2356281422@qq.com>
Co-authored-by: kooriookami <38392315+kooriookami@users.noreply.github.com>
This commit is contained in:
snowbitx
2025-09-04 17:26:57 +08:00
committed by GitHub
parent b9ac07cb68
commit f74e403299
5 changed files with 165 additions and 15 deletions

View File

@@ -61,6 +61,14 @@ radio/radio-button-group
:::
## Options attribute ^(2.11.2)
:::demo Shortcut from basic `el-radio-group` usage. You can customize the alias of the `options` through the `props` attribute.
radio/options
:::
## Button style
Radio with button styles.
@@ -119,18 +127,20 @@ radio/with-borders
### RadioGroup Attributes
| Name | Description | Type | Default |
| --------------------------- | ------------------------------------------------- | ---------------------------------- | ------- |
| model-value / v-model | binding value | ^[string] / ^[number] / ^[boolean] | — |
| size | the size of radio buttons or bordered radios | ^[string] | default |
| disabled | whether the nesting radios are disabled | ^[boolean] | false |
| validate-event | whether to trigger form validation | ^[boolean] | true |
| text-color | font color when button is active | ^[string] | #ffffff |
| fill | border and background color when button is active | ^[string] | #409eff |
| aria-label ^(a11y) ^(2.7.2) | same as `aria-label` in RadioGroup | ^[string] | — |
| name | native `name` attribute | ^[string] | — |
| id | native `id` attribute | ^[string] | — |
| label ^(a11y) ^(deprecated) | same as `aria-label` in RadioGroup | ^[string] | — |
| Name | Description | Type | Default |
| --------------------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | ------- |
| model-value / v-model | binding value | ^[string] / ^[number] / ^[boolean] | — |
| size | the size of radio buttons or bordered radios | ^[string] | default |
| disabled | whether the nesting radios are disabled | ^[boolean] | false |
| validate-event | whether to trigger form validation | ^[boolean] | true |
| text-color | font color when button is active | ^[string] | #ffffff |
| fill | border and background color when button is active | ^[string] | #409eff |
| aria-label ^(a11y) ^(2.7.2) | same as `aria-label` in RadioGroup | ^[string] | — |
| name | native `name` attribute | ^[string] | — |
| id | native `id` attribute | ^[string] | — |
| label ^(a11y) ^(deprecated) | same as `aria-label` in RadioGroup | ^[string] | — |
| options ^(2.11.2) | data of the options, the key of `value` and `label` and `disabled` can be customize by `props` | ^[array]`Array<{[key: string]: any}>` | — |
| props ^(2.11.2) | configuration options | ^[object]`{ value?: string, label?: string, disabled?: boolean}` |
### RadioGroup Events

View File

@@ -0,0 +1,24 @@
<template>
<el-radio-group v-model="radio" :options="options" />
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const radio = ref(3)
const options = [
{
value: 3,
label: 'Option A',
},
{
value: 6,
label: 'Option B',
},
{
value: 9,
label: 'Option C',
},
]
</script>

View File

@@ -205,6 +205,77 @@ describe('Radio group', () => {
expect(radio.value).toEqual(3)
expect(radio1.classes()).toContain('is-active')
})
it('renders el-radio-group using default option fields', async () => {
const radio = ref(3)
const options = [
{
value: 3,
label: 'Option A',
},
{
value: 6,
label: 'Option B',
},
{
value: 9,
label: 'Option C',
},
]
const wrapper = mount(() => (
<RadioGroup v-model={radio.value} options={options} />
))
await nextTick()
const [radio1, radio2] = wrapper.findAll('.el-radio')
expect(radio1.classes()).toContain('is-checked')
await radio2.trigger('click')
expect(radio2.classes()).toContain('is-checked')
expect(radio.value).toEqual(6)
})
it('renders el-radio-group with custom option fields', async () => {
const radio = ref(3)
const options = [
{
id: 3,
label: 'Option A',
},
{
id: 6,
label: 'Option B',
},
{
id: 9,
label: 'Option C',
},
]
const wrapper = mount(() => (
<RadioGroup
v-model={radio.value}
options={options}
props={{ value: 'id' }}
/>
))
await nextTick()
const [radio1, radio2] = wrapper.findAll('.el-radio')
expect(radio1.classes()).toContain('is-checked')
await radio2.trigger('click')
expect(radio2.classes()).toContain('is-checked')
expect(radio.value).toEqual(6)
})
it('passes custom attributes from options to el-radio', () => {
const options = [
{ value: 'a', label: 'A', 'data-test': 'custom-attr-1' },
{ value: 'b', label: 'B', 'data-test': 'custom-attr-2' },
]
const wrapper = mount(RadioGroup, {
props: { options },
})
const [radio1, radio2] = wrapper.findAll('.el-radio')
expect(radio1.attributes('data-test')).toBe('custom-attr-1')
expect(radio2.attributes('data-test')).toBe('custom-attr-2')
})
})
describe('Radio Button', () => {

View File

@@ -1,7 +1,8 @@
import { buildProps } from '@element-plus/utils'
import { buildProps, definePropType } from '@element-plus/utils'
import { useAriaProps, useSizeProp } from '@element-plus/hooks'
import { radioEmits } from './radio'
import type { RadioPropsPublic } from './radio'
import type { ExtractPropTypes, __ExtractPublicPropTypes } from 'vue'
import type RadioGroup from './radio-group.vue'
@@ -56,6 +57,13 @@ export const radioGroupProps = buildProps({
type: Boolean,
default: true,
},
options: {
type: definePropType<radioOption[]>(Array),
},
props: {
type: definePropType<radioOptionProp>(Object),
default: () => radioDefaultProps,
},
...useAriaProps(['ariaLabel']),
} as const)
export type RadioGroupProps = ExtractPropTypes<typeof radioGroupProps>
@@ -66,3 +74,16 @@ export type RadioGroupPropsPublic = __ExtractPublicPropTypes<
export const radioGroupEmits = radioEmits
export type RadioGroupEmits = typeof radioGroupEmits
export type RadioGroupInstance = InstanceType<typeof RadioGroup> & unknown
export type radioOption = RadioPropsPublic & Record<string, any>
export const radioDefaultProps: Required<radioOptionProp> = {
label: 'label',
value: 'value',
disabled: 'disabled',
}
export type radioOptionProp = {
value?: string
label?: string
disabled?: string
}

View File

@@ -7,7 +7,13 @@
:aria-label="!isLabeledByFormItem ? ariaLabel || 'radio-group' : undefined"
:aria-labelledby="isLabeledByFormItem ? formItem!.labelId : undefined"
>
<slot />
<slot>
<el-radio
v-for="(item, index) in props.options"
:key="index"
v-bind="getOptionProps(item)"
/>
</slot>
</div>
</template>
@@ -26,9 +32,14 @@ import { useFormItem, useFormItemInputId } from '@element-plus/components/form'
import { CHANGE_EVENT, UPDATE_MODEL_EVENT } from '@element-plus/constants'
import { useId, useNamespace } from '@element-plus/hooks'
import { debugWarn } from '@element-plus/utils'
import { radioGroupEmits, radioGroupProps } from './radio-group'
import {
radioDefaultProps,
radioGroupEmits,
radioGroupProps,
} from './radio-group'
import { radioGroupKey } from './constants'
import { isEqual } from 'lodash-unified'
import ElRadio from './radio.vue'
import type { RadioGroupProps } from './radio-group'
@@ -65,6 +76,19 @@ const name = computed(() => {
return props.name || radioId.value
})
const aliasProps = computed(() => ({
...radioDefaultProps,
...props.props,
}))
const getOptionProps = (option: Record<string, any>) => {
const base = {
label: option[aliasProps.value.label],
value: option[aliasProps.value.value],
disabled: option[aliasProps.value.disabled],
}
return { ...option, ...base }
}
provide(
radioGroupKey,
reactive({