Reputation: 13
Trying to build a 5-star rating component just like the example link.
peer-hover
seems to work, yet peer-checked
doesn't work as peer-hover
does.
(items
contain an array [1,2,3,4,5], by the way)
Could you point out the reason why this problem happens?
import { RadioGroup } from '@headlessui/react'
import { useController } from "react-hook-form";
import { classNames } from '../libs/frontend/utils'
import { StarIcon } from '@heroicons/react/24/outline';
import { StarIcon as StarIconSolid } from '@heroicons/react/20/solid';
export const RadioGroupStars = (props) => {
const {
field: { value, onChange }
} = useController(props);
const { items } = props;
return (
<>
<RadioGroup
value={value}
onChange={onChange}
className="w-full my-1">
<RadioGroup.Label className="sr-only"> Choose a option </RadioGroup.Label>
<div className="flex flex-row-reverse justify-center gap-1">
{items.map((item) => (
<RadioGroup.Option
key={item}
value={item}
className={({ active, checked }) =>
classNames(
'cursor-pointer text-gray-200',
'flex-1 hover:text-yellow-600',
'peer',
'peer-hover:text-yellow-600 peer-checked:text-yellow-500',
active ? 'text-yellow-500' : '',
checked ? 'text-yellow-500' : '',
)
}
>
<RadioGroup.Label as={StarIconSolid} className='' />
</RadioGroup.Option>
))}
</div>
</RadioGroup>
</>
);
}
Upvotes: 0
Views: 1782
Reputation: 1217
i used below code with react and tailwing, using simple controls as much as possible
export default function RatingControl() {
const [rating, setRating] = useState(0);
return (
<div className="grid w-full sm:col-span-3 sm:grid-cols-10">
<dt className="pb-2 font-bold text-gray-500 sm:col-span-10 ">
Rate Survey
</dt>
{Array.from({ length: 5 }, (_, k) => k + 1).map((item) => (
<button
key={`buttonItem${item}`}
type="button"
onClick={() => {
setRating(item);
}}
className={classNames(
rating !== item ? 'bg-black' : 'bg-gray-800',
' mr-4 mt-2 lg:mt-0 rounded-md text-lg font-semibold text-gray-500 shadow-sm hover:text-white hover:bg-gray-800 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white'
)}
>
{item}
</button>
))}
</div>
);
}
Upvotes: 0
Reputation: 7447
I think the reason peer-checked
not working could be because RadioGroup.Option
outputs a div
wrapping the icon as Label
(instead of a input
), therefore the pseudo-class :checked
is not applied, while peer-hover
does work.
Because the component has access to selected value
and the rating values are comparable:
items contain an array [1,2,3,4,5]
RadioGroup.Option
could compare own value with the selected value as a condition to render with different classes (or equivalently, compare the index
).
Because this list also uses flex-row-reverse
to implement the siblings hover, consider to reverse()
the items
before map()
to keep the iterated items in correct order.
Tested the example in live on: stackblitz (omitted logic for react-hook-form
for simplicity):
<div className="flex flex-row-reverse justify-center gap-1">
{[...items].reverse().map((item) => (
<RadioGroup.Option
key={item}
value={item}
className={({ active, checked }) =>
classNames(
"cursor-pointer text-gray-200",
"flex-1 hover:text-yellow-400",
"peer",
"peer-hover:text-yellow-400",
active ? "text-yellow-500" : "",
checked ? "text-yellow-500" : "",
// 👇 Add a compare with selected value here
value >= item ? "text-yellow-500" : ""
)
}
>
<RadioGroup.Label as={BsStarFill} className="w-6 h-6" />
</RadioGroup.Option>
))}
</div>
On a side note, because RadioGroup
requires a setValue
(a state set
function) for its onChange
prop, not too sure if the field.onChange
returned by useController()
would work with it.
If not, perhaps consider to host a state in the component and sync with useController, so that its functions could be still be used.
Upvotes: 0