Skip to content

Commit

Permalink
[Joy UI] Add FormControl component (#34187)
Browse files Browse the repository at this point in the history
  • Loading branch information
siriwatknp committed Sep 9, 2022
1 parent adda22c commit 420e4a7
Show file tree
Hide file tree
Showing 47 changed files with 1,177 additions and 256 deletions.
13 changes: 13 additions & 0 deletions docs/data/joy/components/checkbox/HelperTextCheckbox.js
@@ -0,0 +1,13 @@
import * as React from 'react';
import Checkbox from '@mui/joy/Checkbox';
import FormControl from '@mui/joy/FormControl';
import FormHelperText from '@mui/joy/FormHelperText';

export default function HelperTextCheckbox() {
return (
<FormControl>
<Checkbox label="Label" />
<FormHelperText>A description for the checkbox.</FormHelperText>
</FormControl>
);
}
6 changes: 6 additions & 0 deletions docs/data/joy/components/checkbox/checkbox.md
Expand Up @@ -87,6 +87,12 @@ It has no accessibility or UX implications.

{{"demo": "IndeterminateCheckbox.js"}}

### Helper text

To add a description to the checkbox, use `FormControl` and `FormHelperText`. The checkbox will be linked to the helper text via `aria-describedby` attribute.

{{"demo": "HelperTextCheckbox.js"}}

### Group

To group multiple checkboxes, use `role="group"` on the wrapper component.
Expand Down
21 changes: 6 additions & 15 deletions docs/data/joy/components/radio/ControlledRadioButtonsGroup.js
@@ -1,8 +1,8 @@
import * as React from 'react';
import Box from '@mui/joy/Box';
import FormControl from '@mui/joy/FormControl';
import FormLabel from '@mui/joy/FormLabel';
import Radio from '@mui/joy/Radio';
import RadioGroup from '@mui/joy/RadioGroup';
import Typography from '@mui/joy/Typography';

export default function ControlledRadioButtonsGroup() {
const [value, setValue] = React.useState('female');
Expand All @@ -12,28 +12,19 @@ export default function ControlledRadioButtonsGroup() {
};

return (
<Box>
<Typography
id="demo-controlled-radio-buttons-group"
level="body3"
textTransform="uppercase"
fontWeight="xl"
sx={{ letterSpacing: '0.15rem' }}
mb={2}
>
Gender
</Typography>
<FormControl>
<FormLabel>Gender</FormLabel>
<RadioGroup
aria-labelledby="demo-controlled-radio-buttons-group"
defaultValue="female"
name="controlled-radio-buttons-group"
value={value}
onChange={handleChange}
sx={{ my: 1 }}
>
<Radio value="female" label="Female" />
<Radio value="male" label="Male" />
<Radio value="other" label="Other" />
</RadioGroup>
</Box>
</FormControl>
);
}
2 changes: 2 additions & 0 deletions docs/data/joy/components/radio/ExamplePaymentChannels.js
Expand Up @@ -30,6 +30,7 @@ export default function ExamplePaymentChannels() {
Pay with
</Typography>
<Switch
component="label"
size="sm"
endDecorator="Row view"
checked={row}
Expand All @@ -49,6 +50,7 @@ export default function ExamplePaymentChannels() {
defaultValue="Paypal"
>
<List
component="div"
variant="outlined"
row={row}
sx={{
Expand Down
28 changes: 9 additions & 19 deletions docs/data/joy/components/radio/OverlayRadio.js
@@ -1,35 +1,26 @@
import * as React from 'react';
import Avatar from '@mui/joy/Avatar';
import Box from '@mui/joy/Box';
import FormControl from '@mui/joy/FormControl';
import FormLabel from '@mui/joy/FormLabel';
import Radio from '@mui/joy/Radio';
import RadioGroup from '@mui/joy/RadioGroup';
import Typography from '@mui/joy/Typography';
import Sheet from '@mui/joy/Sheet';
import Typography from '@mui/joy/Typography';

export default function OverlayRadio() {
return (
<Box>
<Typography
id="member"
mb={2}
level="body3"
textTransform="uppercase"
fontWeight="xl"
sx={{ letterSpacing: '0.15rem' }}
>
Members
</Typography>
<FormControl>
<FormLabel>Members</FormLabel>
<RadioGroup
overlay
name="member"
aria-labelledby="member"
defaultValue="person1"
row
sx={{ gap: 2 }}
sx={{ gap: 2, mt: 1 }}
>
{[1, 2, 3].map((num) => (
<Sheet
component="label"
key={num}
variant="outlined"
sx={{
Expand All @@ -43,7 +34,6 @@ export default function OverlayRadio() {
}}
>
<Radio
id={`person${num}`}
value={`person${num}`}
sx={{
mt: -1,
Expand All @@ -53,11 +43,11 @@ export default function OverlayRadio() {
'--Radio-action-radius': (theme) => theme.vars.radius.md,
}}
/>
<Avatar src={`/static/images/avatar/${num}.jpg`} />
<FormLabel htmlFor={`person${num}`}>Person {num}</FormLabel>
<Avatar alt={`person${num}`} src={`/static/images/avatar/${num}.jpg`} />
<Typography level="body2">Person {num}</Typography>
</Sheet>
))}
</RadioGroup>
</Box>
</FormControl>
);
}
27 changes: 8 additions & 19 deletions docs/data/joy/components/radio/RadioButtonsGroup.js
@@ -1,31 +1,20 @@
import * as React from 'react';
import Box from '@mui/joy/Box';
import FormControl from '@mui/joy/FormControl';
import FormLabel from '@mui/joy/FormLabel';
import FormHelperText from '@mui/joy/FormHelperText';
import Radio from '@mui/joy/Radio';
import RadioGroup from '@mui/joy/RadioGroup';
import Typography from '@mui/joy/Typography';

export default function RadioButtonsGroup() {
return (
<Box>
<Typography
id="demo-radio-buttons-group-label"
level="body3"
textTransform="uppercase"
fontWeight="xl"
sx={{ letterSpacing: '0.15rem' }}
mb={2}
>
Gender
</Typography>
<RadioGroup
aria-labelledby="demo-radio-buttons-group-label"
defaultValue="female"
name="radio-buttons-group"
>
<FormControl>
<FormLabel>Gender</FormLabel>
<RadioGroup defaultValue="female" name="radio-buttons-group" sx={{ my: 1 }}>
<Radio value="female" label="Female" />
<Radio value="male" label="Male" />
<Radio value="other" label="Other" />
</RadioGroup>
</Box>
<FormHelperText>This is a helper text.</FormHelperText>
</FormControl>
);
}
34 changes: 9 additions & 25 deletions docs/data/joy/components/radio/RadioFocus.js
@@ -1,41 +1,25 @@
import * as React from 'react';
import Box from '@mui/joy/Box';
import FormControl from '@mui/joy/FormControl';
import FormLabel from '@mui/joy/FormLabel';
import FormHelperText from '@mui/joy/FormHelperText';
import Radio, { radioClasses } from '@mui/joy/Radio';
import RadioGroup from '@mui/joy/RadioGroup';
import Typography from '@mui/joy/Typography';

export default function RadioFocus() {
return (
<Box>
<Typography
id="demo-radio-buttons-group-focus"
level="body3"
textTransform="uppercase"
fontWeight="xl"
sx={{ letterSpacing: '0.15rem' }}
mb={2}
>
Focus
</Typography>
<RadioGroup
aria-labelledby="demo-radio-buttons-group-focus"
aria-describedby="demo-radio-buttons-group-focus-description"
name="radio-buttons-group-focus"
>
<FormControl>
<FormLabel>Focus</FormLabel>
<RadioGroup name="radio-buttons-group-focus" sx={{ my: 1 }}>
<Radio value="default" label="Default" />
<Radio
value="relative"
label="Position relative"
sx={{ [`& .${radioClasses.radio}`]: { position: 'relative' } }}
/>
</RadioGroup>
<Typography
level="body3"
mt={2}
id="demo-radio-buttons-group-focus-description"
>
<FormHelperText>
Select an option and use keyboard ↑↓ to see the focus outline
</Typography>
</Box>
</FormHelperText>
</FormControl>
);
}
2 changes: 1 addition & 1 deletion docs/data/joy/components/radio/RadioPositionEnd.js
Expand Up @@ -10,7 +10,7 @@ import Apartment from '@mui/icons-material/Apartment';

export default function RadioPositionEnd() {
return (
<RadioGroup name="people" defaultValue="Individual">
<RadioGroup aria-label="Your plan" name="people" defaultValue="Individual">
<List
sx={{
minWidth: 240,
Expand Down
21 changes: 5 additions & 16 deletions docs/data/joy/components/radio/RadioUsage.js
Expand Up @@ -2,6 +2,7 @@ import * as React from 'react';
import JoyUsageDemo, {
prependLinesSpace,
} from 'docs/src/modules/components/JoyUsageDemo';
import FormControl from '@mui/joy/FormControl';
import FormLabel from '@mui/joy/FormLabel';
import RadioGroup from '@mui/joy/RadioGroup';
import Radio from '@mui/joy/Radio';
Expand Down Expand Up @@ -39,31 +40,19 @@ export default function RadioUsage() {
${prependLinesSpace(code, 2)}
</RadioGroup>`}
renderDemo={({ row, ...props }) => (
<div>
<FormLabel
id="radio-button-usage-label"
sx={{
mb: 2,
fontWeight: 'xl',
textTransform: 'uppercase',
fontSize: 'xs',
letterSpacing: '0.15rem',
color: 'text.secondary',
}}
>
Pizza crust
</FormLabel>
<FormControl>
<FormLabel>Pizza crust</FormLabel>
<RadioGroup
row={row}
defaultValue="1"
name="radio-button-usage"
aria-labelledby="radio-button-usage-label"
sx={{ mt: 1 }}
>
<Radio label="Regular crust" value="1" {...props} />
<Radio label="Deep dish" value="2" {...props} />
<Radio label="Thin crust" value="3" {...props} disabled />
</RadioGroup>
</div>
</FormControl>
)}
/>
);
Expand Down
2 changes: 1 addition & 1 deletion docs/data/joy/components/radio/radio.md
Expand Up @@ -102,7 +102,7 @@ Here are a few tips to make sure you have an accessible radio button component:

- Every form control should have proper labels.
This includes radio buttons, checkboxes, and switches.
In most cases, this is done by using the `<label>` element (see Material UI's [`FormControlLabel`](/material-ui/api/form-control-label/) as reference).
In most cases, this is done by using the `FormControl` and `FormLabel` element.
- When a label can't be used, make sure to add an attribute, such as `aria-label`, `aria-labelledby`, and/or `title`, directly on the input component.
You can also use the `inputProps` prop to add them.

Expand Down
31 changes: 10 additions & 21 deletions docs/data/joy/components/select/SelectFieldDemo.js
@@ -1,5 +1,5 @@
import * as React from 'react';
import Box from '@mui/joy/Box';
import FormControl from '@mui/joy/FormControl';
import FormLabel from '@mui/joy/FormLabel';
import FormHelperText from '@mui/joy/FormHelperText';
import Select from '@mui/joy/Select';
Expand All @@ -8,31 +8,20 @@ import Option from '@mui/joy/Option';
export default function SelectFieldDemo() {
const [value, setValue] = React.useState('dog');
return (
<Box sx={{ width: 240 }}>
<FormLabel htmlFor="select-field-pet">Favorite pet</FormLabel>
<Select
id="select-field-pet"
defaultValue="dog"
componentsProps={{
button: {
// screen readers will announce "Favorite pet, dog selected" when the select is focused.
'aria-label': `Favorite pet, ${value} selected.`,
// and this in the next sentence.
'aria-describedby': 'select-field-pet-helper',
},
}}
value={value}
onChange={setValue}
sx={{ mt: 0.25 }}
<FormControl sx={{ width: 240 }}>
<FormLabel
// screen readers will announce "Favorite pet, dog selected" when the select is focused.
aria-label={`Favorite pet, ${value} selected.`}
>
Favorite pet
</FormLabel>
<Select defaultValue="dog" value={value} onChange={setValue}>
<Option value="dog">Dog</Option>
<Option value="cat">Cat</Option>
<Option value="fish">Fish</Option>
<Option value="bird">Bird</Option>
</Select>
<FormHelperText id="select-field-pet-helper">
This is a helper text.
</FormHelperText>
</Box>
<FormHelperText>This is a helper text.</FormHelperText>
</FormControl>
);
}
31 changes: 24 additions & 7 deletions docs/data/joy/components/select/select.md
Expand Up @@ -45,13 +45,6 @@ The `Select` component is similar to the native HTML's `<select>` and `<option>`

{{"demo": "SelectBasic.js"}}

### Field

Use the `FormLabel` component to add a label to the select component.
Make sure to provide an appropriate `aria-label` and an id to the button's `aria-describedby`.

{{"demo": "SelectFieldDemo.js"}}

### Decorators

Use the `startDecorator` and/or `endDecorator` props to add supporting icons or elements to the select.
Expand Down Expand Up @@ -132,6 +125,30 @@ That way, you'll have a consistent height and will be able to leverage nested CS

:::

## Accessibility

In order for the select to be accessible, **it should be linked to a label**.

The `FormControl` automatically generates a unique id that links the select with the `FormLabel` component:

{{"demo": "SelectFieldDemo.js"}}

Alternatively, you can do it manually by targeting the button slot:

```jsx
<label htmlFor="unique-id">Label</label>
<Select
componentsProps={{
button: {
id: 'unique-id',
}
}}
>
<Option value="option1">Option I</Option>
<Option value="option2">Option II</Option>
</Select>
```

## Common examples

### Clear action
Expand Down

0 comments on commit 420e4a7

Please sign in to comment.