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

[Joy UI] Add FormControl component #34187

Merged
merged 32 commits into from Sep 9, 2022
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
a8aa5b4
bind controls id
siriwatknp Sep 3, 2022
44e606b
adjust Input to use values from form control
siriwatknp Sep 3, 2022
aeccd76
add classes to form control
siriwatknp Sep 3, 2022
c231fcb
use context for input
siriwatknp Sep 3, 2022
ed7dcc1
use context for Textarea
siriwatknp Sep 3, 2022
6d2826e
fix color
siriwatknp Sep 3, 2022
d1d61cc
add composition demo to textfield
siriwatknp Sep 3, 2022
60cd8e5
use FormControl
siriwatknp Sep 3, 2022
2023929
move to accessibility section
siriwatknp Sep 3, 2022
9a3593a
add registerEffect
siriwatknp Sep 3, 2022
f31452e
add tests
siriwatknp Sep 3, 2022
012636f
run proptypes
siriwatknp Sep 3, 2022
00b9cfa
run docs:formatted
siriwatknp Sep 3, 2022
514dbd7
fix id
siriwatknp Sep 3, 2022
f428ff7
Merge branch 'master' of https://github.com/mui/material-ui into joy/…
siriwatknp Sep 7, 2022
a132c5d
fix comment
siriwatknp Sep 7, 2022
bcd6431
link checkbox with form control
siriwatknp Sep 7, 2022
0ae09e2
add label id and linked to radio group
siriwatknp Sep 7, 2022
c0386e8
List and RadioGroup integration
siriwatknp Sep 7, 2022
b198da9
simplify label
siriwatknp Sep 7, 2022
6d56271
fix content
siriwatknp Sep 7, 2022
c89191c
minor text field doc tweak
danilo-leal Sep 8, 2022
1957939
run proptypes
siriwatknp Sep 8, 2022
cc4e165
Merge branch 'master' of https://github.com/mui/material-ui into joy/…
siriwatknp Sep 8, 2022
ffe27cd
Merge branch 'joy/form-control' of github.com:siriwatknp/material-ui …
siriwatknp Sep 8, 2022
699770d
apply id to RadioGroup for completeness
siriwatknp Sep 8, 2022
dcdbef9
adjust helper text margin
siriwatknp Sep 8, 2022
ddcd4e5
integrate FormControl with Switch
siriwatknp Sep 8, 2022
9c4f069
add label example
siriwatknp Sep 9, 2022
d0bc61a
proptypes
siriwatknp Sep 9, 2022
eaa60a8
remove custom margin
siriwatknp Sep 9, 2022
b4d53bb
update demo usage
siriwatknp Sep 9, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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} sx={{ mt: 0.25 }}>
<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 @@ -41,13 +41,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 @@ -128,6 +121,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
15 changes: 15 additions & 0 deletions docs/data/joy/components/text-field/TextFieldComposition.js
@@ -0,0 +1,15 @@
import * as React from 'react';
import FormControl from '@mui/joy/FormControl';
import FormLabel from '@mui/joy/FormLabel';
import FormHelperText from '@mui/joy/FormHelperText';
import Input from '@mui/joy/Input';

export default function TextFieldComposition() {
return (
<FormControl>
<FormLabel>Label</FormLabel>
<Input placeholder="Placeholder" />
<FormHelperText>This is a helper text.</FormHelperText>
</FormControl>
);
}
15 changes: 15 additions & 0 deletions docs/data/joy/components/text-field/TextFieldComposition.tsx
@@ -0,0 +1,15 @@
import * as React from 'react';
import FormControl from '@mui/joy/FormControl';
import FormLabel from '@mui/joy/FormLabel';
import FormHelperText from '@mui/joy/FormHelperText';
import Input from '@mui/joy/Input';

export default function TextFieldComposition() {
return (
<FormControl>
<FormLabel>Label</FormLabel>
<Input placeholder="Placeholder" />
<FormHelperText>This is a helper text.</FormHelperText>
</FormControl>
);
}
@@ -0,0 +1,5 @@
<FormControl>
<FormLabel>Label</FormLabel>
<Input placeholder="Placeholder" />
<FormHelperText>This is a helper text.</FormHelperText>
</FormControl>
6 changes: 6 additions & 0 deletions docs/data/joy/components/text-field/text-field.md
Expand Up @@ -72,3 +72,9 @@ Use the `startDecorator` and/or `endDecorator` props to add supporting icons or
To make the text field take up the full width of its container, use the `fullWidth` prop.

{{"demo": "TextFieldFullwidth.js"}}

### Composition

`TextField` is composed of smaller components (`FormControl`, `FormLabel`, `Input`, and `FormHelperText`) that you can leverage directly to customize your form inputs.

{{"demo": "TextFieldComposition.js"}}
5 changes: 3 additions & 2 deletions docs/data/joy/components/textarea/ExampleTextareaComment.js
@@ -1,6 +1,7 @@
import * as React from 'react';
import Box from '@mui/joy/Box';
import Button from '@mui/joy/Button';
import FormControl from '@mui/joy/FormControl';
import FormLabel from '@mui/joy/FormLabel';
import Textarea from '@mui/joy/Textarea';
import IconButton from '@mui/joy/IconButton';
Expand All @@ -17,7 +18,7 @@ export default function TextareaValidator() {
const [fontWeight, setFontWeight] = React.useState('normal');
const [anchorEl, setAnchorEl] = React.useState(null);
return (
<Box sx={{ p: 2 }}>
<FormControl>
<FormLabel>Your comment</FormLabel>
<Textarea
placeholder="Type something here…"
Expand Down Expand Up @@ -83,6 +84,6 @@ export default function TextareaValidator() {
fontStyle: italic ? 'italic' : 'initial',
}}
/>
</Box>
</FormControl>
);
}
15 changes: 15 additions & 0 deletions docs/data/joy/components/textarea/TextareaField.js
@@ -0,0 +1,15 @@
import * as React from 'react';
import FormControl from '@mui/joy/FormControl';
import FormLabel from '@mui/joy/FormLabel';
import FormHelperText from '@mui/joy/FormHelperText';
import Textarea from '@mui/joy/Textarea';

export default function TextareaField() {
return (
<FormControl>
<FormLabel>Label</FormLabel>
<Textarea placeholder="Placeholder" minRows={2} />
<FormHelperText>This is a helper text.</FormHelperText>
</FormControl>
);
}
21 changes: 21 additions & 0 deletions docs/data/joy/components/textarea/textarea.md
Expand Up @@ -78,6 +78,27 @@ It's usually more common to see textarea components using decorators at the top

{{"demo": "TextareaDecorators.js"}}

## Accessibility

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

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

{{"demo": "TextareaField.js"}}

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

```jsx
<label htmlFor="unique-id">Label</label>
<Textarea
componentsProps={{
textarea: {
id: 'unique-id',
}
}}
/>
```

## Common examples

### Comment box
Expand Down
176 changes: 176 additions & 0 deletions packages/mui-joy/src/FormControl/FormControl.test.tsx
@@ -0,0 +1,176 @@
import * as React from 'react';
import { expect } from 'chai';
import { createRenderer, describeConformance } from 'test/utils';
import { ThemeProvider } from '@mui/joy/styles';
import FormControl, { formControlClasses as classes } from '@mui/joy/FormControl';
import { unstable_capitalize as capitalize } from '@mui/utils';
import FormLabel from '@mui/joy/FormLabel';
import Input, { inputClasses } from '@mui/joy/Input';
import Select, { selectClasses } from '@mui/joy/Select';
import Textarea, { textareaClasses } from '@mui/joy/Textarea';

describe('<FormControl />', () => {
const { render } = createRenderer();

describeConformance(<FormControl />, () => ({
classes,
inheritComponent: 'div',
render,
ThemeProvider,
muiName: 'JoyFormControl',
refInstanceof: window.HTMLDivElement,
testComponentPropWith: 'fieldset',
testVariantProps: { color: 'success' },
skip: ['classesRoot', 'componentsProp'],
}));

describe('prop: color', () => {
it('adds a neutral class by default', () => {
const { getByTestId } = render(<FormControl data-testid="root">Hello World</FormControl>);

expect(getByTestId('root')).to.have.class(classes.colorNeutral);
});

(['primary', 'success', 'info', 'danger', 'neutral', 'warning'] as const).forEach((color) => {
it(`should render ${color}`, () => {
const { getByTestId } = render(<FormControl data-testid="root" color={color} />);

expect(getByTestId('root')).to.have.class(
classes[`color${capitalize(color)}` as keyof typeof classes],
);
});
});
});

describe('Input', () => {
it('should linked the label', () => {
const { getByLabelText } = render(
<FormControl>
<FormLabel>label</FormLabel>
<Input />
</FormControl>,
);

expect(getByLabelText('label')).toBeVisible();
});

it('should inherit color prop from FormControl', () => {
const { getByTestId } = render(
<FormControl color="success">
<Input data-testid="input" />
</FormControl>,
);

expect(getByTestId('input')).to.have.class(inputClasses.colorSuccess);
});

it('should inherit error prop from FormControl', () => {
const { getByTestId } = render(
<FormControl error>
<Input data-testid="input" />
</FormControl>,
);

expect(getByTestId('input')).to.have.class(inputClasses.colorDanger);
});

it('should inherit disabled from FormControl', () => {
const { getByRole } = render(
<FormControl disabled>
<FormLabel>label</FormLabel>
<Input />
</FormControl>,
);

expect(getByRole('textbox')).to.have.attribute('disabled');
});
});

describe('Textarea', () => {
it('should linked the label', () => {
const { getByLabelText } = render(
<FormControl>
<FormLabel>label</FormLabel>
<Textarea />
</FormControl>,
);

expect(getByLabelText('label')).toBeVisible();
});

it('should inherit color prop from FormControl', () => {
const { getByTestId } = render(
<FormControl color="success">
<Textarea data-testid="textarea" />
</FormControl>,
);

expect(getByTestId('textarea')).to.have.class(textareaClasses.colorSuccess);
});

it('should inherit error prop from FormControl', () => {
const { getByTestId } = render(
<FormControl error>
<Textarea data-testid="textarea" />
</FormControl>,
);

expect(getByTestId('textarea')).to.have.class(textareaClasses.colorDanger);
});

it('should inherit disabled from FormControl', () => {
const { getByLabelText } = render(
<FormControl disabled>
<FormLabel>label</FormLabel>
<Textarea />
</FormControl>,
);

expect(getByLabelText('label')).to.have.attribute('disabled');
});
});

describe('Select', () => {
it('should linked the label', () => {
const { getByLabelText } = render(
<FormControl>
<FormLabel>label</FormLabel>
<Select />
</FormControl>,
);

expect(getByLabelText('label')).toBeVisible();
});

it('should inherit color prop from FormControl', () => {
const { getByTestId } = render(
<FormControl color="success">
<Select data-testid="select" />
</FormControl>,
);

expect(getByTestId('select')).to.have.class(selectClasses.colorSuccess);
});

it('should inherit error prop from FormControl', () => {
const { getByTestId } = render(
<FormControl error>
<Select data-testid="select" />
</FormControl>,
);

expect(getByTestId('select')).to.have.class(selectClasses.colorDanger);
});

it('should inherit disabled from FormControl', () => {
const { getByLabelText } = render(
<FormControl disabled>
<FormLabel>label</FormLabel>
<Select />
</FormControl>,
);

expect(getByLabelText('label')).to.have.attribute('disabled');
});
});
});