1
+ import { ReactNode } from '@tanstack/react-router' ;
1
2
import { ComponentProps } from 'react' ;
2
3
import {
3
4
Controller ,
5
+ ControllerProps ,
4
6
ControllerRenderProps ,
5
7
FieldPath ,
6
8
FieldValues ,
7
9
} from 'react-hook-form' ;
10
+ import { isNonNullish } from 'remeda' ;
8
11
9
12
import { cn } from '@/lib/tailwind/utils' ;
13
+ import { getUiState } from '@/lib/ui-state' ;
10
14
11
15
import { Radio , RadioGroup } from '@/components/ui/radio-group' ;
12
16
13
17
import { useFormField } from '../form-field' ;
14
- import { FieldCommonProps } from '../form-field-controller' ;
18
+ import { FieldProps } from '../form-field-controller' ;
15
19
import { FormFieldError } from '../form-field-error' ;
16
20
21
+ type RadioOptionProps = {
22
+ value : string ;
23
+ label : string ;
24
+ disabled ?: boolean ;
25
+ } ;
26
+
17
27
export type FieldRadioGroupProps <
18
28
TFieldValues extends FieldValues = FieldValues ,
19
29
TName extends FieldPath < TFieldValues > = FieldPath < TFieldValues > ,
20
- > = FieldCommonProps < TFieldValues , TName > & {
30
+ > = FieldProps < TFieldValues , TName > & {
21
31
type : 'radio-group' ;
22
- options : Array < {
23
- value : string ;
24
- label : string ;
25
- disabled ?: boolean ;
26
- } > ;
32
+ options : Array < RadioOptionProps > ;
33
+ renderRadio ?: (
34
+ radioOptions : RadioOptionProps & { checked : boolean } ,
35
+ fieldOptions : Parameters <
36
+ Pick < ControllerProps < TFieldValues , TName > , 'render' > [ 'render' ]
37
+ > [ 0 ]
38
+ ) => ReactNode ;
27
39
containerProps ?: ComponentProps < 'div' > ;
28
40
} & RemoveFromType <
29
41
Omit <
@@ -48,52 +60,83 @@ export const FieldRadioGroup = <
48
60
shouldUnregister,
49
61
control,
50
62
containerProps,
63
+ renderRadio,
51
64
...rest
52
65
} = props ;
53
66
54
67
const ctx = useFormField ( ) ;
55
68
69
+ const ui = getUiState ( ( set ) => {
70
+ if ( isNonNullish ( renderRadio ) ) {
71
+ return set ( 'render-radio' , { renderRadio } ) ;
72
+ }
73
+
74
+ return set ( 'default' ) ;
75
+ } ) ;
76
+
56
77
return (
57
78
< Controller
58
79
name = { name }
59
80
control = { control }
60
81
disabled = { disabled }
61
82
defaultValue = { defaultValue }
62
83
shouldUnregister = { shouldUnregister }
63
- render = { ( { field : { onChange, onBlur, ...field } , fieldState } ) => (
64
- < div
65
- { ...containerProps }
66
- className = { cn (
67
- 'flex flex-1 flex-col gap-1' ,
68
- containerProps ?. className
69
- ) }
70
- >
71
- < RadioGroup
72
- id = { ctx . id }
73
- aria-invalid = { fieldState . error ? true : undefined }
74
- aria-describedby = {
75
- ! fieldState . error
76
- ? `${ ctx . descriptionId } `
77
- : `${ ctx . descriptionId } ${ ctx . errorId } `
78
- }
79
- { ...rest }
80
- onValueChange = { onChange }
81
- { ...field }
84
+ render = { ( controllerRenderOptions ) => {
85
+ const {
86
+ field : { onChange, onBlur, ...field } ,
87
+ fieldState,
88
+ } = controllerRenderOptions ;
89
+
90
+ return (
91
+ < div
92
+ { ...containerProps }
93
+ className = { cn (
94
+ 'flex flex-1 flex-col gap-1' ,
95
+ containerProps ?. className
96
+ ) }
82
97
>
83
- { options . map ( ( option ) => (
84
- < Radio
85
- key = { `${ ctx . id } -${ option . value } ` }
86
- value = { option . value }
87
- disabled = { option . disabled }
88
- onBlur = { onBlur }
89
- >
90
- { option . label }
91
- </ Radio >
92
- ) ) }
93
- </ RadioGroup >
94
- < FormFieldError />
95
- </ div >
96
- ) }
98
+ < RadioGroup
99
+ id = { ctx . id }
100
+ aria-invalid = { fieldState . error ? true : undefined }
101
+ aria-describedby = {
102
+ ! fieldState . error
103
+ ? `${ ctx . descriptionId } `
104
+ : `${ ctx . descriptionId } ${ ctx . errorId } `
105
+ }
106
+ { ...rest }
107
+ onValueChange = { onChange }
108
+ { ...field }
109
+ >
110
+ { ui
111
+ . match ( 'render-radio' , ( { renderRadio } ) =>
112
+ options . map ( ( option ) =>
113
+ renderRadio (
114
+ {
115
+ ...option ,
116
+ checked : option . value === field . value ,
117
+ } ,
118
+ controllerRenderOptions
119
+ )
120
+ )
121
+ )
122
+ . match ( 'default' , ( ) =>
123
+ options . map ( ( option ) => (
124
+ < Radio
125
+ key = { `${ ctx . id } -${ option . value } ` }
126
+ value = { option . value }
127
+ disabled = { option . disabled }
128
+ onBlur = { onBlur }
129
+ >
130
+ { option . label }
131
+ </ Radio >
132
+ ) )
133
+ )
134
+ . exhaustive ( ) }
135
+ </ RadioGroup >
136
+ < FormFieldError />
137
+ </ div >
138
+ ) ;
139
+ } }
97
140
/>
98
141
) ;
99
142
} ;
0 commit comments