1
- import { useMemo } from "react"
1
+ import { useMemo , useState } from "react"
2
2
import { useTranslation } from "next-i18next"
3
- import { useRadio , useRadioGroup , UseRadioProps } from "@chakra -ui/react"
3
+ import * as RadioGroup from "@radix -ui/react-radio-group "
4
4
5
5
import type {
6
6
AnswerChoice ,
@@ -30,21 +30,16 @@ export const QuizRadioGroup = ({
30
30
} : QuizRadioGroupProps ) => {
31
31
const { t } = useTranslation ( "learn-quizzes" )
32
32
33
+ const [ selectedAnswer , setSelectedAnswer ] =
34
+ useState < RadioGroup . RadioGroupProps [ "value" ] > ( "" )
35
+
33
36
const handleSelection = ( answerId : AnswerKey ) => {
37
+ setSelectedAnswer ( answerId )
34
38
const isCorrect =
35
39
answerId === questions [ currentQuestionIndex ] . correctAnswerId
36
40
setCurrentQuestionAnswerChoice ( { answerId, isCorrect } )
37
41
}
38
42
39
- const {
40
- getRadioProps,
41
- getRootProps,
42
- value : selectedAnswer ,
43
- } = useRadioGroup ( {
44
- name : `quiz-question-${ currentQuestionIndex } ` ,
45
- onChange : handleSelection ,
46
- } )
47
-
48
43
const {
49
44
answers,
50
45
correctAnswerId,
@@ -65,125 +60,97 @@ export const QuizRadioGroup = ({
65
60
)
66
61
67
62
return (
68
- < fieldset className = "w-full" { ... getRootProps ( ) } >
63
+ < fieldset className = "w-full" >
69
64
< legend className = "mb-6 w-full text-center text-2xl font-bold" >
70
65
< span className = "sr-only" >
71
66
{ t ( "question-number" , { number : currentQuestionIndex + 1 } ) }
72
67
</ span >
73
68
{ t ( prompt ) }
74
69
</ legend >
75
-
76
- < div
70
+ < RadioGroup . Root
77
71
className = "md:px-12 lg:px-16"
78
72
data-testid = "question-group"
79
73
id = { questionId }
74
+ value = { selectedAnswer }
75
+ onValueChange = { handleSelection }
80
76
>
81
77
< Stack className = "gap-4" >
82
78
{ answers . map ( ( { id, label } , idx ) => {
83
79
const display =
84
80
! answerStatus || id === selectedAnswer ? "inline-flex" : "hidden"
85
-
86
81
return (
87
82
< div key = { id } className = { display } >
88
83
< CustomRadio
89
84
label = { t ( label ) }
90
85
isAnswerVisible = { ! ! answerStatus }
91
86
isSelectedCorrect = { isSelectedCorrect }
92
87
index = { idx }
93
- { ... getRadioProps ( { value : id } ) }
88
+ value = { id }
94
89
/>
95
90
</ div >
96
91
)
97
92
} ) }
98
93
</ Stack >
99
-
100
94
{ ! ! answerStatus && (
101
95
< Stack className = "mt-6 gap-2" >
102
96
< p className = "font-bold" > { t ( "explanation" ) } </ p >
103
97
104
98
< p className = "m-0" > { t ( explanation ) } </ p >
105
99
</ Stack >
106
100
) }
107
- </ div >
101
+ </ RadioGroup . Root >
108
102
</ fieldset >
109
103
)
110
104
}
111
105
112
- type CustomRadioProps = UseRadioProps & {
106
+ type CustomRadioProps = RadioGroup . RadioGroupItemProps & {
113
107
index : number
114
108
isAnswerVisible : boolean
115
109
isSelectedCorrect : boolean
116
110
label : string
117
111
}
118
112
119
113
const CustomRadio = ( {
120
- isAnswerVisible,
121
114
index,
115
+ isAnswerVisible,
122
116
isSelectedCorrect,
123
117
label,
124
- ...radioProps
118
+ ...itemProps
125
119
} : CustomRadioProps ) => {
126
- const INPUT_ID = `quiz-question-answer-${ index } `
127
- const { state, getInputProps, getRadioProps, getLabelProps } = useRadio ( {
128
- ...radioProps ,
129
- id : INPUT_ID ,
130
- } )
131
-
132
- const buttonBg = useMemo < string > ( ( ) => {
133
- if ( ! state . isChecked ) return "bg-background-highlight"
134
- if ( ! isAnswerVisible ) return "bg-primary"
135
- if ( ! isSelectedCorrect ) return "bg-error"
136
- return "bg-success"
137
- } , [ state . isChecked , isAnswerVisible , isSelectedCorrect ] )
138
-
139
- const radioInputProps = getInputProps ( { id : INPUT_ID } )
140
-
141
120
return (
142
- < >
143
- < label
144
- // `htmlFor` for proper accessibility with label and input
145
- { ...getLabelProps ( { htmlFor : INPUT_ID } ) }
146
- className = "w-full"
147
- >
148
- < HStack
149
- { ...getRadioProps ( ) }
150
- id = { radioInputProps . value }
151
- data-testid = "quiz-question-answer"
152
- data-group
153
- data-answer-visible = { isAnswerVisible || undefined }
154
- data-selected-correct = { isSelectedCorrect || undefined }
155
- // Override: `aria-hidden` is marked true in `getRadioProps`
156
- aria-hidden = "false"
121
+ < HStack
122
+ id = { itemProps . value }
123
+ data-testid = "quiz-question-answer"
124
+ data-group
125
+ data-answer-visible = { isAnswerVisible || undefined }
126
+ data-selected-correct = { isSelectedCorrect || undefined }
127
+ className = { cn (
128
+ "w-full cursor-pointer gap-2 rounded bg-background-highlight p-2 text-start text-body data-[answer-visible]:cursor-default" ,
129
+ "hover:outline hover:outline-1 hover:outline-primary hover:data-[answer-visible]:outline-none" ,
130
+ "data-[state='checked']:data-[answer-visible]:bg-error" ,
131
+ "data-[state='checked']:data-[answer-visible]:data-[selected-correct]:bg-success" ,
132
+ "data-[state='checked']:text-white" ,
133
+ "data-[state='checked']:not-[data-answer-visible]:bg-primary"
134
+ ) }
135
+ asChild
136
+ >
137
+ < RadioGroup . Item { ...itemProps } >
138
+ < Center
157
139
className = { cn (
158
- "w-full cursor-pointer gap-2 rounded p-2 text-body data-[answer-visible]:cursor-default" ,
159
- buttonBg ,
160
- "hover:outline hover:outline-1 hover:outline-primary hover:data-[answer-visible]:outline-none" ,
161
- // TODO: Upon removing custom radio props, flip remove `data` for checked
162
- "data-[checked]:data-[answer-visible]:bg-error" ,
163
- "data-[checked]:data-[answer-visible]:data-[selected-correct]:bg-success" ,
164
- "data-[checked]:text-white" ,
165
- "data-[checked]:not-[data-answer-visible]:bg-primary"
140
+ "size-6 flex-shrink-0 flex-grow-0 rounded-full bg-disabled text-white" ,
141
+ "[:is([data-state='checked'],:hover)_>_&]:text-white" ,
142
+ "[:is([data-state='checked'],:hover)_>_&]:bg-primary-action" ,
143
+ "[:is([data-state='checked'],:hover)[data-answer-visible]_>_&]:bg-white" ,
144
+ "[:is([data-state='checked'],:hover)[data-answer-visible]_>_&]:text-error" ,
145
+ "[:is([data-state='checked'],:hover)[data-answer-visible][data-selected-correct]_>_&]:text-success"
166
146
) }
167
147
>
168
- < Center
169
- className = { cn (
170
- "size-6 flex-shrink-0 flex-grow-0 rounded-full bg-disabled text-white" ,
171
- // TODO: Upon removing custom radio props, flip remove `data` for checked
172
- "[:is([data-checked],:hover)_>_&]:text-white" ,
173
- "[:is([data-checked],:hover)_>_&]:bg-primary-action" ,
174
- "[:is([data-checked],:hover)[data-answer-visible]_>_&]:bg-white" ,
175
- "[:is([data-checked],:hover)[data-answer-visible]_>_&]:text-error" ,
176
- "[:is([data-checked],:hover)[data-answer-visible][data-selected-correct]_>_&]:text-success"
177
- ) }
178
- >
179
- < p className = "text-lg font-bold leading-none" >
180
- { String . fromCharCode ( 97 + index ) . toUpperCase ( ) }
181
- </ p >
182
- </ Center >
183
- < span > { label } </ span >
184
- </ HStack >
185
- </ label >
186
- < input { ...radioInputProps } />
187
- </ >
148
+ < p className = "text-lg font-bold leading-none" >
149
+ { String . fromCharCode ( 97 + index ) . toUpperCase ( ) }
150
+ </ p >
151
+ </ Center >
152
+ < span className = "w-full" > { label } </ span >
153
+ </ RadioGroup . Item >
154
+ </ HStack >
188
155
)
189
156
}
0 commit comments