Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(FormControl): allow required check boxes #5027

5 changes: 5 additions & 0 deletions .changeset/wild-maps-grow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@primer/react": patch
---

fix(FormControl): allow required check boxes in CheckboxGroup
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ Playground.argTypes = {
export const Default = () => (
<CheckboxGroup>
<CheckboxGroup.Label>Choices</CheckboxGroup.Label>
<FormControl>
<FormControl required>
<Checkbox value="one" defaultChecked />
<FormControl.Label>Choice one</FormControl.Label>
</FormControl>
Expand Down
2 changes: 1 addition & 1 deletion packages/react/src/FormControl/FormControl.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export const Default = () => (
<FormControl required={true}>
<FormControl.Label>Form Input Label</FormControl.Label>
<FormControl.Caption>This is a caption</FormControl.Caption>
<Checkbox />
<TextInput />
</FormControl>
)

Expand Down
7 changes: 7 additions & 0 deletions packages/react/src/FormControl/FormControl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ const FormControl = React.forwardRef<HTMLDivElement, FormControlProps>(
const inputProps = React.isValidElement(InputComponent) && InputComponent.props
const isChoiceInput =
React.isValidElement(InputComponent) && (InputComponent.type === Checkbox || InputComponent.type === Radio)
const isPartOfCheckboxGroup =
React.isValidElement(InputComponent) &&
InputComponent.type === Checkbox &&
Object.keys(choiceGroupContext).length > 0

if (InputComponent) {
if (inputProps?.id) {
Expand Down Expand Up @@ -138,11 +142,14 @@ const FormControl = React.forwardRef<HTMLDivElement, FormControlProps>(
InputComponent as React.ReactElement<{
id: string
disabled: boolean
required: boolean
['aria-describedby']: string
}>,
{
id,
disabled,
// allow individual checkboxes that are part of a checkbox group to be required
required: required && isPartOfCheckboxGroup,
['aria-describedby']: captionId as string,
},
)}
Expand Down
36 changes: 36 additions & 0 deletions packages/react/src/__tests__/FormControl.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import axe from 'axe-core'
import {
Autocomplete,
Checkbox,
CheckboxGroup,
FormControl,
Select,
Textarea,
Expand Down Expand Up @@ -390,6 +391,41 @@ describe('FormControl', () => {
expect(consoleSpy).toHaveBeenCalled()
consoleSpy.mockRestore()
})

it('should not add required prop to individual checkbox', async () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In other cases when signing up on a site, there may be some options like marketing emails and terms and conditions, where terms and conditions is required but marketing emails is not.

It makes me think what if the terms and conditions option is required (which is in most cases) and there are no other options. Does that mean individual checkboxes can also be required even if there are not a part of group?

Let me know if I am missing something here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point, @ichelsea thoughts on this?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say yes to that; Indvidual checkboxes can be required if not a part of a group of checkboxes. Are there technical limitations to this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No technical limitations, just wanted to confirm since that wasn't previously allowed. thanks for the input!

const {getByRole} = render(
<FormControl required>
broccolinisoup marked this conversation as resolved.
Show resolved Hide resolved
<FormControl.Label>{LABEL_TEXT}</FormControl.Label>
<Checkbox />
<FormControl.Caption>{CAPTION_TEXT}</FormControl.Caption>
</FormControl>,
)

expect(getByRole('checkbox')).not.toBeRequired()
})

it('should allow required prop on checkbox if part of CheckboxGroup', async () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add a test for "should allow required prop on radio button if part of RadioButtonGroup"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's my understanding from this that a radio cannot be required, not even when within a RadioButtonGroup, just the group itself can be required (at the top level). I can add a test for "does not allow required prop on radio button when part of RadioButtonGroup" if you think that'd be worth it

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries, all good!

const {getByTestId} = render(
<CheckboxGroup>
<CheckboxGroup.Label>Checkboxes</CheckboxGroup.Label>
<FormControl required>
<Checkbox value="checkOne" data-testid="checkbox-1" />
<FormControl.Label>Checkbox one</FormControl.Label>
</FormControl>
<FormControl>
<Checkbox value="checkTwo" data-testid="checkbox-2" />
<FormControl.Label>Checkbox two</FormControl.Label>
</FormControl>
<FormControl>
<Checkbox value="checkThree" />
<FormControl.Label>Checkbox three</FormControl.Label>
</FormControl>
</CheckboxGroup>,
)

expect(getByTestId('checkbox-1')).toBeRequired()
expect(getByTestId('checkbox-2')).not.toBeRequired()
})
})

it('should have no axe violations', async () => {
Expand Down
Loading