mirror of
https://github.com/nextui-org/nextui.git
synced 2025-12-08 19:26:11 +00:00
fix(radio): ensure radio input correctly references description (#3301)
* fix(radio): ensure radio input correctly references description * refactor: tweak test
This commit is contained in:
parent
edd48a09cc
commit
42183353a1
5
.changeset/dry-foxes-melt.md
Normal file
5
.changeset/dry-foxes-melt.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
"@nextui-org/radio": patch
|
||||
---
|
||||
|
||||
Fix ensure radio input correctly references description (#2932)
|
||||
@ -213,6 +213,56 @@ describe("Radio", () => {
|
||||
|
||||
expect(radio2).toBeChecked();
|
||||
});
|
||||
|
||||
it("should support help text description", () => {
|
||||
const {getByRole} = render(
|
||||
<RadioGroup description="Help text" label="Options">
|
||||
<Radio value="1">Option 1</Radio>
|
||||
</RadioGroup>,
|
||||
);
|
||||
|
||||
const group = getByRole("radiogroup");
|
||||
|
||||
expect(group).toHaveAttribute("aria-describedby");
|
||||
|
||||
const groupDescriptionId = group.getAttribute("aria-describedby");
|
||||
const groupDescriptionElement = document.getElementById(groupDescriptionId as string);
|
||||
|
||||
expect(groupDescriptionElement).toHaveTextContent("Help text");
|
||||
});
|
||||
|
||||
it("should support help text description for the individual radios", () => {
|
||||
const {getByLabelText} = render(
|
||||
<RadioGroup description="Help text" label="Options">
|
||||
<Radio description="Help text for option 1" value="1">
|
||||
Option 1
|
||||
</Radio>
|
||||
<Radio description="Help text for option 2" value="2">
|
||||
Option 2
|
||||
</Radio>
|
||||
</RadioGroup>,
|
||||
);
|
||||
|
||||
const option1 = getByLabelText("Option 1");
|
||||
|
||||
expect(option1).toHaveAttribute("aria-describedby");
|
||||
const option1Description = option1
|
||||
.getAttribute("aria-describedby")
|
||||
?.split(" ")
|
||||
.map((d) => document.getElementById(d)?.textContent)
|
||||
.join(" ");
|
||||
|
||||
expect(option1Description).toBe("Help text for option 1 Help text");
|
||||
|
||||
const option2 = getByLabelText("Option 2");
|
||||
const option2Description = option2
|
||||
.getAttribute("aria-describedby")
|
||||
?.split(" ")
|
||||
.map((d) => document.getElementById(d)?.textContent)
|
||||
.join(" ");
|
||||
|
||||
expect(option2Description).toBe("Help text for option 2 Help text");
|
||||
});
|
||||
});
|
||||
|
||||
describe("validation", () => {
|
||||
|
||||
@ -9,8 +9,6 @@ const Radio = forwardRef<"input", RadioProps>((props, ref) => {
|
||||
const {
|
||||
Component,
|
||||
children,
|
||||
slots,
|
||||
classNames,
|
||||
description,
|
||||
getBaseProps,
|
||||
getWrapperProps,
|
||||
@ -18,6 +16,7 @@ const Radio = forwardRef<"input", RadioProps>((props, ref) => {
|
||||
getLabelProps,
|
||||
getLabelWrapperProps,
|
||||
getControlProps,
|
||||
getDescriptionProps,
|
||||
} = useRadio({...props, ref});
|
||||
|
||||
return (
|
||||
@ -30,9 +29,7 @@ const Radio = forwardRef<"input", RadioProps>((props, ref) => {
|
||||
</span>
|
||||
<div {...getLabelWrapperProps()}>
|
||||
{children && <span {...getLabelProps()}>{children}</span>}
|
||||
{description && (
|
||||
<span className={slots.description({class: classNames?.description})}>{description}</span>
|
||||
)}
|
||||
{description && <span {...getDescriptionProps()}>{description}</span>}
|
||||
</div>
|
||||
</Component>
|
||||
);
|
||||
|
||||
@ -87,27 +87,33 @@ export function useRadio(props: UseRadioProps) {
|
||||
const inputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const labelId = useId();
|
||||
const descriptionId = useId();
|
||||
|
||||
const isRequired = useMemo(() => groupContext.isRequired ?? false, [groupContext.isRequired]);
|
||||
const isInvalid = groupContext.isInvalid;
|
||||
|
||||
const ariaRadioProps = useMemo(() => {
|
||||
const ariaLabel =
|
||||
otherProps["aria-label"] || typeof children === "string" ? (children as string) : undefined;
|
||||
const ariaDescribedBy =
|
||||
otherProps["aria-describedby"] || typeof description === "string"
|
||||
? (description as string)
|
||||
: undefined;
|
||||
[otherProps["aria-describedby"], descriptionId].filter(Boolean).join(" ") || undefined;
|
||||
|
||||
return {
|
||||
id,
|
||||
isRequired,
|
||||
isDisabled: isDisabledProp,
|
||||
"aria-label": ariaLabel,
|
||||
"aria-label": otherProps["aria-label"],
|
||||
"aria-labelledby": otherProps["aria-labelledby"] || labelId,
|
||||
"aria-describedby": ariaDescribedBy,
|
||||
};
|
||||
}, [labelId, id, isDisabledProp, isRequired]);
|
||||
}, [
|
||||
id,
|
||||
isDisabledProp,
|
||||
isRequired,
|
||||
description,
|
||||
otherProps["aria-label"],
|
||||
otherProps["aria-labelledby"],
|
||||
otherProps["aria-describedby"],
|
||||
descriptionId,
|
||||
]);
|
||||
|
||||
const {
|
||||
inputProps,
|
||||
@ -117,8 +123,7 @@ export function useRadio(props: UseRadioProps) {
|
||||
} = useReactAriaRadio(
|
||||
{
|
||||
value,
|
||||
children,
|
||||
...groupContext,
|
||||
children: typeof children === "function" ? true : children,
|
||||
...ariaRadioProps,
|
||||
},
|
||||
groupContext.groupState,
|
||||
@ -251,22 +256,30 @@ export function useRadio(props: UseRadioProps) {
|
||||
[slots, classNames?.control],
|
||||
);
|
||||
|
||||
const getDescriptionProps: PropGetter = useCallback(
|
||||
(props = {}) => ({
|
||||
...props,
|
||||
id: descriptionId,
|
||||
className: slots.description({class: classNames?.description}),
|
||||
}),
|
||||
[slots, classNames?.description],
|
||||
);
|
||||
|
||||
return {
|
||||
Component,
|
||||
children,
|
||||
slots,
|
||||
classNames,
|
||||
description,
|
||||
isSelected,
|
||||
isDisabled,
|
||||
isInvalid,
|
||||
isFocusVisible,
|
||||
description,
|
||||
getBaseProps,
|
||||
getWrapperProps,
|
||||
getInputProps,
|
||||
getLabelProps,
|
||||
getLabelWrapperProps,
|
||||
getControlProps,
|
||||
getDescriptionProps,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user