@@ -16,11 +16,11 @@ import { firstLine, indicatorSizes, layouts, sizes, styles } from './radio.style
1616/** `stack` gives each choice its own row. `grid` fits as many columns of cards as the width allows. */
1717export type RadioLayout = 'stack' | 'grid' ;
1818
19- export interface RadioGroupProps
19+ export interface RadioGroupProps < Value = string >
2020 extends Omit < React . ComponentPropsWithoutRef < 'div' > , 'defaultValue' | 'className' | 'style' > , MosaicStyleProps {
21- value ?: string ;
22- defaultValue ?: string ;
23- onValueChange ?: ( value : string ) => void ;
21+ value ?: Value ;
22+ defaultValue ?: Value ;
23+ onValueChange ?: ( value : Value ) => void ;
2424 name ?: string ;
2525 disabled ?: boolean ;
2626 /** @default 'stack' */
@@ -30,23 +30,20 @@ export interface RadioGroupProps
3030interface RadioGroupContextValue {
3131 name : string ;
3232 submits : boolean ;
33- value : string | undefined ;
34- setValue : ( value : string ) => void ;
33+ value : unknown ;
34+ setValue : ( value : unknown ) => void ;
3535 disabled : boolean | undefined ;
3636}
3737
3838const RadioGroupContext = React . createContext < RadioGroupContextValue | null > ( null ) ;
3939
40- const RadioGroup = React . forwardRef < HTMLDivElement , RadioGroupProps > ( function MosaicRadioGroup (
40+ // eslint-disable-next-line typescript/consistent-type-assertions -- The ref wrapper drops the value type parameter; the group only hands back values its caller passed in.
41+ const RadioGroup = React . forwardRef < HTMLDivElement , RadioGroupProps < unknown > > ( function MosaicRadioGroup (
4142 { value : valueProp , defaultValue, onValueChange, name : nameProp , disabled, layout = 'stack' , xstyle, ...rest } ,
4243 ref ,
4344) {
4445 const generatedName = React . useId ( ) ;
45- const [ value , setValue ] = useControllableState < string | undefined > ( valueProp , defaultValue , next => {
46- if ( next !== undefined ) {
47- onValueChange ?.( next ) ;
48- }
49- } ) ;
46+ const [ value , setValue ] = useControllableState ( valueProp , defaultValue , onValueChange ) ;
5047 const context = React . useMemo < RadioGroupContextValue > (
5148 ( ) => ( { name : nameProp ?? generatedName , submits : nameProp !== undefined , value, setValue, disabled } ) ,
5249 [ nameProp , generatedName , value , setValue , disabled ] ,
@@ -66,14 +63,14 @@ const RadioGroup = React.forwardRef<HTMLDivElement, RadioGroupProps>(function Mo
6663 />
6764 </ RadioGroupContext . Provider >
6865 ) ;
69- } ) ;
66+ } ) as < Value = string > ( props : RadioGroupProps < Value > & { ref ?: React . Ref < HTMLDivElement > } ) => React . ReactElement ;
7067
7168export interface RadioItemProps
7269 extends
7370 Omit < React . ComponentPropsWithoutRef < 'input' > , 'type' | 'size' | 'value' | 'className' | 'style' > ,
7471 MosaicStyleProps {
7572 size ?: 'sm' | 'md' ;
76- value ?: string ;
73+ value ?: unknown ;
7774}
7875
7976const RadioItem = React . forwardRef < HTMLInputElement , RadioItemProps > ( function MosaicRadioItem (
@@ -103,7 +100,7 @@ const RadioItem = React.forwardRef<HTMLInputElement, RadioItemProps>(function Mo
103100 } ) ;
104101 const group = React . useContext ( RadioGroupContext ) ;
105102 const disabled = ( fieldProps ?. disabled ?? disabledProp ) || group ?. disabled ;
106- const checked = group && value !== undefined ? group . value === value : checkedProp ;
103+ const checked = group && value !== undefined ? Object . is ( group . value , value ) : checkedProp ;
107104
108105 return (
109106 < span
@@ -115,7 +112,7 @@ const RadioItem = React.forwardRef<HTMLInputElement, RadioItemProps>(function Mo
115112 < input
116113 ref = { ref }
117114 type = 'radio'
118- value = { value }
115+ value = { value === undefined ? undefined : toFormValue ( value ) }
119116 name = { group ?. name ?? nameProp }
120117 form = { group ?. submits === false ? '' : undefined }
121118 checked = { checked }
@@ -155,6 +152,23 @@ const RadioItem = React.forwardRef<HTMLInputElement, RadioItemProps>(function Mo
155152 ) ;
156153} ) ;
157154
155+ function toFormValue ( value : unknown ) : string {
156+ if ( value === null ) {
157+ return '' ;
158+ }
159+ if ( typeof value === 'string' ) {
160+ return value ;
161+ }
162+ if ( typeof value === 'bigint' ) {
163+ return value . toString ( ) ;
164+ }
165+ try {
166+ return JSON . stringify ( value ) ;
167+ } catch {
168+ return '' ;
169+ }
170+ }
171+
158172export interface RadioSectionProps
159173 extends Omit < React . ComponentPropsWithoutRef < 'fieldset' > , 'className' | 'style' > , MosaicStyleProps {
160174 /** The section's heading, rendered as its `<legend>`. */
@@ -197,7 +211,7 @@ const RadioSection = React.forwardRef<HTMLFieldSetElement, RadioSectionProps>(fu
197211
198212export interface RadioCardProps
199213 extends Omit < React . ComponentPropsWithoutRef < 'label' > , 'className' | 'style' > , MosaicStyleProps {
200- value : string ;
214+ value : unknown ;
201215 disabled ?: boolean ;
202216}
203217
@@ -216,7 +230,7 @@ const RadioCard = React.forwardRef<HTMLLabelElement, RadioCardProps>(function Mo
216230 ref ,
217231) {
218232 const group = React . useContext ( RadioGroupContext ) ;
219- const checked = group ?. value === value ;
233+ const checked = group !== null && Object . is ( group . value , value ) ;
220234
221235 return (
222236 < FieldProvider
0 commit comments