跳到主要内容

RadioGroup 单选按钮组

RadioGroup 是一个用于呈现一组单选按钮的组件。用户可以从多个选项中选择一个选项。

代码演示

基本使用

基础使用 key: value:

收起源代码
import { Space, RadioGroup, RadioOption } from '@dance-ui/ui'
import { useState } from 'react'
import React from 'react'

const radioOpts: Array<RadioOption | RadioOption[] | null> = [
{ key: 'id', label: '我是id,value值为test1,有前后缀', prefix: 'prefix_', suffix: '_suffix', value: 'test1' },
{
key: 'input1',
isInput: true,
suffix: '_suffix',
inputClass: 'w-[10.625rem]',
placeholder: '请输入---自定义value值',
},
{
key: 'onClick',
label: '点击事件',
value: 'onClick1',
onClick: (e: any) => {
alert('Hello')
},
},
{
key: 'onClick',
label: '点击事件2',
value: 'onClick2',
onClick: (e: any) => {
alert('Hello World')
},
},
[
{
key: 'subGroup-1',
label: "I'm subGroup-1",
value: 'subGroup-1',
},
{
key: 'subGroup-2',
label: "I'm subGroup-2",
value: 'subGroup-2',
},
{
key: 'customValue',
isInput: true,
value: 'initValue',
placeholder: '千万不要输入"-"',
beforeOnChange: (value: string) => {
if (value.includes('-')) {
alert('不允许输入"-"')
return false
}
return true
},
},
],
]
export default () => {
const [value, setValue] = useState('')
const [key, setSelectedRadioKey] = useState('')
return (
<Space direction="vertical">
<Space direction="vertical">
<p>
基础使用 key: {key} value: {value}
</p>
<RadioGroup
defaultValue="test1"
options={radioOpts}
labelClass="min-w-[10.125rem]"
value={value}
onChange={(value: string, key?: string) => {
setSelectedRadioKey(key ?? '')
setValue(value)
}}
/>
</Space>
</Space>
)
}

API

RadioGroup

属性说明类型默认值
options单选按钮组的选项Array<RadioOption \| RadioOption[] \| null>-
name单选按钮的 name 属性string-
value当前选中的值OptionValueType-
defaultValue默认选中的值string-
onChange选项变化时的回调函数(value: string, key?: string) => void-
className组件的类名string-
dotClass单选按钮点的类名string-
dotContainerClass单选按钮点容器的类名string-
labelClass标签的类名string-

RadioOption

属性说明类型默认值
label选项的标签string \| ReactNode-
key选项的键string-
value选项的值OptionValueType-
suffix选项的后缀string-
prefix选项的前缀string-
onClick选项点击时的回调函数MouseEventHandler<HTMLSpanElement>-
beforeOnChange在改变之前的回调函数(value: string) => boolean-
isInput是否为输入值boolean-
defaultValue输入的默认值string-
inputClass输入的类名string-
placeholder输入的占位符string-
className选项的类名string-
labelClass选项标签的类名string-
labelStyle选项标签的样式CSSProperties-
dotClass选项点的类名string-
dotContainerClass选项点容器的类名string-

组件源码

组件源码
/* eslint-disable @typescript-eslint/no-unnecessary-type-assertion */
// TODO: Fix ts error
import { CSSProperties, Fragment, MouseEventHandler, ReactNode, Ref, forwardRef, useCallback, useState } from 'react'
import { twMerge } from 'tailwind-merge'
import { RadioItem } from './RadioItem'

export type OptionValueType = string
export type RadioOption = {
label?: string | ReactNode
key?: string
value?: OptionValueType
suffix?: string
prefix?: string
onClick?: MouseEventHandler<HTMLSpanElement>
beforeOnChange?: (value: string) => boolean
// input value
isInput?: boolean
defaultValue?: string
inputClass?: string
placeholder?: string
// classes
className?: string
labelClass?: string
labelStyle?: CSSProperties
dotClass?: string
dotContainerClass?: string
}

export type RadioGroupProps = {
options: Array<RadioOption | RadioOption[] | null>
name: string
onChange: (value: string, key?: string) => void
value?: OptionValueType
defaultValue?: string
className?: string
dotClass?: string
dotContainerClass?: string
labelClass?: string
}

const RadioGroup = forwardRef(
(
{
options,
name,
value: externalValue,
defaultValue,
onChange,
className,
dotClass,
dotContainerClass,
labelClass,
}: RadioGroupProps,
ref: Ref<HTMLDivElement>,
) => {
const [selectedValue, setSelectedValue] = useState<string | null>(defaultValue ?? null)
const [inputValues, setInputValues] = useState<Record<string, string>>({})
const isControlled = externalValue !== undefined
const currentValue = isControlled ? externalValue : selectedValue ?? ''

const handleRadioChange = useCallback(
(value?: string, key?: string) => {
if (!value) return
if (!isControlled) {
setSelectedValue(value)
}
onChange(value, key)
},
[isControlled, onChange],
)

const handleInputChange = (key: string, value: string) => {
setInputValues((prev) => ({
...prev,
[key]: value,
}))
}

const renderOptions = useCallback(
() =>
options.map((option, idx) => {
if (!option) return null
const isArray = Array.isArray(option)
if (isArray && (option as RadioOption[]).length === 0) return null

if (isArray) {
const opts = option as RadioOption[]
const realValues = opts.map((opt) => {
const { value = '', isInput, key, prefix = '', suffix = '' } = opt
const realValue = `${prefix}${isInput ? inputValues[key ?? 'default'] : value}${suffix}`
return realValue
})
return (
<Fragment key={idx}>
{opts.map((opt, idx) => {
const isSelected = currentValue ? realValues.includes(currentValue) : false

// const isSelected = realValues.includes(currentValue)
return (
<RadioItem
showDot={idx === 0}
key={opt?.key ?? opt?.value}
option={opt}
name={name}
currentValue={currentValue ?? ''}
onValueChange={handleRadioChange}
inputValues={inputValues}
onInputChange={handleInputChange}
dotContainerClass={dotContainerClass}
dotClass={dotClass}
labelClass={labelClass}
dotIsSelected={isSelected}
/>
)
})}
</Fragment>
)
}
const opt = option as RadioOption

return (
<RadioItem
key={opt?.key ?? opt?.value}
option={opt}
name={name}
currentValue={currentValue ?? ''}
onValueChange={handleRadioChange}
inputValues={inputValues}
onInputChange={handleInputChange}
dotContainerClass={dotContainerClass}
dotClass={dotClass}
labelClass={labelClass}
/>
)
}),
[currentValue, dotClass, dotContainerClass, handleRadioChange, inputValues, labelClass, name, options],
)
return (
<div className={twMerge('flex flex-wrap items-center gap-3', className)} ref={ref}>
{renderOptions()}
</div>
)
},
)
RadioGroup.defaultProps = {}

export default RadioGroup