Skip to content

Commit f632a0e

Browse files
authored
Merge pull request #14914 from TylerAPfledderer/refactor/quiz-radio-remove-chakra-hooks
[ShadCN]: Replace Chakra radio hooks with RadioGroup Radix
2 parents 3aab0b5 + 395127d commit f632a0e

File tree

1 file changed

+46
-79
lines changed

1 file changed

+46
-79
lines changed
Lines changed: 46 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { useMemo } from "react"
1+
import { useMemo, useState } from "react"
22
import { useTranslation } from "next-i18next"
3-
import { useRadio, useRadioGroup, UseRadioProps } from "@chakra-ui/react"
3+
import * as RadioGroup from "@radix-ui/react-radio-group"
44

55
import type {
66
AnswerChoice,
@@ -30,21 +30,16 @@ export const QuizRadioGroup = ({
3030
}: QuizRadioGroupProps) => {
3131
const { t } = useTranslation("learn-quizzes")
3232

33+
const [selectedAnswer, setSelectedAnswer] =
34+
useState<RadioGroup.RadioGroupProps["value"]>("")
35+
3336
const handleSelection = (answerId: AnswerKey) => {
37+
setSelectedAnswer(answerId)
3438
const isCorrect =
3539
answerId === questions[currentQuestionIndex].correctAnswerId
3640
setCurrentQuestionAnswerChoice({ answerId, isCorrect })
3741
}
3842

39-
const {
40-
getRadioProps,
41-
getRootProps,
42-
value: selectedAnswer,
43-
} = useRadioGroup({
44-
name: `quiz-question-${currentQuestionIndex}`,
45-
onChange: handleSelection,
46-
})
47-
4843
const {
4944
answers,
5045
correctAnswerId,
@@ -65,125 +60,97 @@ export const QuizRadioGroup = ({
6560
)
6661

6762
return (
68-
<fieldset className="w-full" {...getRootProps()}>
63+
<fieldset className="w-full">
6964
<legend className="mb-6 w-full text-center text-2xl font-bold">
7065
<span className="sr-only">
7166
{t("question-number", { number: currentQuestionIndex + 1 })}
7267
</span>
7368
{t(prompt)}
7469
</legend>
75-
76-
<div
70+
<RadioGroup.Root
7771
className="md:px-12 lg:px-16"
7872
data-testid="question-group"
7973
id={questionId}
74+
value={selectedAnswer}
75+
onValueChange={handleSelection}
8076
>
8177
<Stack className="gap-4">
8278
{answers.map(({ id, label }, idx) => {
8379
const display =
8480
!answerStatus || id === selectedAnswer ? "inline-flex" : "hidden"
85-
8681
return (
8782
<div key={id} className={display}>
8883
<CustomRadio
8984
label={t(label)}
9085
isAnswerVisible={!!answerStatus}
9186
isSelectedCorrect={isSelectedCorrect}
9287
index={idx}
93-
{...getRadioProps({ value: id })}
88+
value={id}
9489
/>
9590
</div>
9691
)
9792
})}
9893
</Stack>
99-
10094
{!!answerStatus && (
10195
<Stack className="mt-6 gap-2">
10296
<p className="font-bold">{t("explanation")}</p>
10397

10498
<p className="m-0">{t(explanation)}</p>
10599
</Stack>
106100
)}
107-
</div>
101+
</RadioGroup.Root>
108102
</fieldset>
109103
)
110104
}
111105

112-
type CustomRadioProps = UseRadioProps & {
106+
type CustomRadioProps = RadioGroup.RadioGroupItemProps & {
113107
index: number
114108
isAnswerVisible: boolean
115109
isSelectedCorrect: boolean
116110
label: string
117111
}
118112

119113
const CustomRadio = ({
120-
isAnswerVisible,
121114
index,
115+
isAnswerVisible,
122116
isSelectedCorrect,
123117
label,
124-
...radioProps
118+
...itemProps
125119
}: 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-
141120
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
157139
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"
166146
)}
167147
>
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>
188155
)
189156
}

0 commit comments

Comments
 (0)