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

Allow check fields and selectable fields to render as required #487 #581

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions src/components/CheckboxField/CheckboxField.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export const CheckboxField = React.forwardRef((props, ref) => {
isLabelVisible,
label,
labelPosition,
renderAsRequired,
required,
validationState,
validationText,
Expand All @@ -30,7 +31,7 @@ export const CheckboxField = React.forwardRef((props, ref) => {
context && context.layout === 'horizontal' ? styles.isRootLayoutHorizontal : styles.isRootLayoutVertical,
labelPosition === 'before' && styles.hasRootLabelBefore,
disabled && styles.isRootDisabled,
required && styles.isRootRequired,
(renderAsRequired || required) && styles.isRootRequired,
getRootValidationStateClassName(validationState, styles),
)}
htmlFor={id}
Expand Down Expand Up @@ -82,6 +83,7 @@ CheckboxField.defaultProps = {
id: undefined,
isLabelVisible: true,
labelPosition: 'after',
renderAsRequired: false,
required: false,
validationState: null,
validationText: null,
Expand Down Expand Up @@ -120,7 +122,11 @@ CheckboxField.propTypes = {
*/
labelPosition: PropTypes.oneOf(['before', 'after']),
/**
* If `true`, the input will be required.
* If `true`, the input will be rendered as if it was required.
*/
renderAsRequired: PropTypes.bool,
/**
* If `true`, the input will be made and rendered as required, regardless of the `renderAsRequired` prop.
*/
required: PropTypes.bool,
/**
Expand Down
72 changes: 72 additions & 0 deletions src/components/CheckboxField/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,78 @@ React.createElement(() => {
});
```

### Required State
Copy link
Member Author

Choose a reason for hiding this comment

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

But it is true that one more sentence could be helpful. I am missing the detailed explanation why would user might need this and why it is correct approach.

I rewrote the docs and reworked the example. What do you guys think now?

obrazek

Copy link
Member Author

Choose a reason for hiding this comment

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

Suggestions:

  • emphasize it's just a visual thing
  • when a checkbox can be true or false, not only true
  • consider moving it aside as an edge case

Copy link
Contributor

Choose a reason for hiding this comment

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

I like it.


The required state indicates that the input is mandatory. Required fields
display an asterisk `*` after the label by default.

```docoff-react-preview
React.createElement(() => {
const [agree, setAgree] = React.useState(true);
return (
<CheckboxField
checked={agree}
label="I agree"
onChange={() => setAgree(!agree)}
required
/>
);
});
```

However, your project may use the label color as the primary means to indicate
the required state of input fields (see
[Forms Theming](/docs/customize/theming/forms) for more). Because not checking
an input is also a valid action, it may be confusing to users to see the
optional check inputs greyed out.

For this case, there is the `renderAsRequired` prop:

```docoff-react-preview
React.createElement(() => {
const [optional, setOptional] = React.useState(false);
const [required, setRequired] = React.useState(false);
const [renderAsRequired, setRenderAsRequired] = React.useState(false);
return (
<React.Fragment>
<style>
{`
.example--themed-form-fields {
--rui-FormField__label__color: var(--rui-color-text-secondary);
--rui-FormField--required__label__color: var(--rui-color-text-primary);
--rui-FormField--required__sign: '';
}
`}
</style>
<div class="example--themed-form-fields">
<CheckboxField
checked={optional}
label="This field is optional"
onChange={() => setOptional(!optional)}
/>
<br />
<CheckboxField
checked={required}
label="This field is required and must be checked"
onChange={() => setRequired(!required)}
required
/>
<br />
<CheckboxField
checked={renderAsRequired}
label="Checked or unchecked, both states are valid"
onChange={() => setRenderAsRequired(!renderAsRequired)}
renderAsRequired
/>
</div>
</React.Fragment>
);
});
```

It renders the field as required, but doesn't add the `required` attribute to
the actual input.

### Disabled State

Disabled state makes the input unavailable.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { helpTextPropTest } from '../../../../tests/propTests/helpTextPropTest';
import { formLayoutProviderTest } from '../../../../tests/providerTests/formLayoutProviderTest';
import { isLabelVisibleTest } from '../../../../tests/propTests/isLabelVisibleTest';
import { labelPropTest } from '../../../../tests/propTests/labelPropTest';
import { renderAsRequiredPropTest } from '../../../../tests/propTests/renderAsRequiredPropTest';
import { requiredPropTest } from '../../../../tests/propTests/requiredPropTest';
import { validationStatePropTest } from '../../../../tests/propTests/validationStatePropTest';
import { validationTextPropTest } from '../../../../tests/propTests/validationTextPropTest';
Expand Down Expand Up @@ -43,6 +44,7 @@ describe('rendering', () => {
],
...isLabelVisibleTest(),
...labelPropTest(),
...renderAsRequiredPropTest,
...requiredPropTest,
...validationStatePropTest,
...validationTextPropTest,
Expand Down
68 changes: 68 additions & 0 deletions src/components/Radio/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,74 @@ have.
})
```

### Required State

The required state indicates that the input is mandatory.

```docoff-react-preview
React.createElement(() => {
const [fruit, setFruit] = React.useState('apple');
return (
<Radio
label="Your favourite fruit"
onChange={(e) => setFruit(e.target.value)}
options={[
{
label: 'Apple',
value: 'apple',
},
{
label: 'Banana',
value: 'banana',
},
{
label: 'Grapefruit',
value: 'grapefruit',
},
]}
value={fruit}
required
/>
);
})
```

However, you may find yourself in a situation where the input is not required
(i.e. making the input checked), but you also don't want to render the field as
optional because not choosing an option can be perfectly valid. For this case,
there is the `renderAsRequired` prop:

```docoff-react-preview
React.createElement(() => {
const [fruit, setFruit] = React.useState('apple');
return (
<Radio
label="Your favourite fruit"
onChange={(e) => setFruit(e.target.value)}
options={[
{
label: 'Apple',
value: 'apple',
},
{
label: 'Banana',
value: 'banana',
},
{
label: 'Grapefruit',
value: 'grapefruit',
},
]}
value={fruit}
renderAsRequired
/>
);
})
```

It renders the field as required, but doesn't add the `required` attribute to
the actual input.

### Disabled State

It's possible to disable just some options or the whole set.
Expand Down
10 changes: 8 additions & 2 deletions src/components/Radio/Radio.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const Radio = ({
label,
layout,
options,
renderAsRequired,
required,
validationState,
validationText,
Expand All @@ -33,7 +34,7 @@ export const Radio = ({
? styles.isRootLayoutHorizontal
: styles.isRootLayoutVertical,
disabled && styles.isRootDisabled,
required && styles.isRootRequired,
(renderAsRequired || required) && styles.isRootRequired,
getRootValidationStateClassName(validationState, styles),
)}
disabled={disabled}
Expand Down Expand Up @@ -116,6 +117,7 @@ Radio.defaultProps = {
id: undefined,
isLabelVisible: true,
layout: 'vertical',
renderAsRequired: false,
required: false,
validationState: null,
validationText: null,
Expand Down Expand Up @@ -181,7 +183,11 @@ Radio.propTypes = {
]),
})).isRequired,
/**
* If `true`, the input will be required.
* If `true`, the input will be rendered as if it was required.
*/
renderAsRequired: PropTypes.bool,
/**
* If `true`, the input will be made and rendered as required, regardless of the `renderAsRequired` prop.
*/
required: PropTypes.bool,
/**
Expand Down
2 changes: 2 additions & 0 deletions src/components/Radio/__tests__/Radio.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { formLayoutProviderTest } from '../../../../tests/providerTests/formLayo
import { isLabelVisibleTest } from '../../../../tests/propTests/isLabelVisibleTest';
import { labelPropTest } from '../../../../tests/propTests/labelPropTest';
import { layoutPropTest } from '../../../../tests/propTests/layoutPropTest';
import { renderAsRequiredPropTest } from '../../../../tests/propTests/renderAsRequiredPropTest';
import { requiredPropTest } from '../../../../tests/propTests/requiredPropTest';
import { validationStatePropTest } from '../../../../tests/propTests/validationStatePropTest';
import { validationTextPropTest } from '../../../../tests/propTests/validationTextPropTest';
Expand Down Expand Up @@ -83,6 +84,7 @@ describe('rendering', () => {
expect(within(rootElement).getByLabelText('option 2')).toBeDisabled();
},
],
...renderAsRequiredPropTest,
...requiredPropTest,
...validationStatePropTest,
...validationTextPropTest,
Expand Down
68 changes: 68 additions & 0 deletions src/components/SelectField/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,74 @@ React.createElement(() => {
})
```

### Required State

The required state indicates that the input is mandatory.

```docoff-react-preview
React.createElement(() => {
const [fruit, setFruit] = React.useState('apple');
return (
<SelectField
label="Your favourite fruit"
onChange={(e) => setFruit(e.target.value)}
options={[
{
label: 'Apple',
value: 'apple',
},
{
label: 'Banana',
value: 'banana',
},
{
label: 'Grapefruit',
value: 'grapefruit',
},
]}
value={fruit}
required
/>
);
});
```

However, you may find yourself in a situation where the input is not required
(i.e. selecting an option), but you also don't want to render the field as
optional because the unselected state can be perfectly valid. For this case,
there is the `renderAsRequired` prop:

```docoff-react-preview
React.createElement(() => {
const [fruit, setFruit] = React.useState('apple');
return (
<SelectField
label="Your favourite fruit"
onChange={(e) => setFruit(e.target.value)}
options={[
{
label: 'Apple',
value: 'apple',
},
{
label: 'Banana',
value: 'banana',
},
{
label: 'Grapefruit',
value: 'grapefruit',
},
]}
value={fruit}
renderAsRequired
/>
);
});
```

It renders the field as required, but doesn't add the `required` attribute to
the actual input.

### Disabled State

It's possible to disable just some options or the whole input.
Expand Down
10 changes: 8 additions & 2 deletions src/components/SelectField/SelectField.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export const SelectField = React.forwardRef((props, ref) => {
label,
layout,
options,
renderAsRequired,
required,
size,
validationState,
Expand All @@ -43,7 +44,7 @@ export const SelectField = React.forwardRef((props, ref) => {
? styles.isRootLayoutHorizontal
: styles.isRootLayoutVertical,
inputGroupContext && styles.isRootGrouped,
required && styles.isRootRequired,
(renderAsRequired || required) && styles.isRootRequired,
getRootSizeClassName(
resolveContextOrProp(inputGroupContext && inputGroupContext.size, size),
styles,
Expand Down Expand Up @@ -136,6 +137,7 @@ SelectField.defaultProps = {
id: undefined,
isLabelVisible: true,
layout: 'vertical',
renderAsRequired: false,
required: false,
size: 'medium',
validationState: null,
Expand Down Expand Up @@ -227,7 +229,11 @@ SelectField.propTypes = {
})),
]).isRequired,
/**
* If `true`, the input will be required.
* If `true`, the input will be rendered as if it was required.
*/
renderAsRequired: PropTypes.bool,
/**
* If `true`, the input will be made and rendered as required, regardless of the `renderAsRequired` prop.
*/
required: PropTypes.bool,
/**
Expand Down
2 changes: 2 additions & 0 deletions src/components/SelectField/__tests__/SelectField.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { formLayoutProviderTest } from '../../../../tests/providerTests/formLayo
import { isLabelVisibleTest } from '../../../../tests/propTests/isLabelVisibleTest';
import { labelPropTest } from '../../../../tests/propTests/labelPropTest';
import { layoutPropTest } from '../../../../tests/propTests/layoutPropTest';
import { renderAsRequiredPropTest } from '../../../../tests/propTests/renderAsRequiredPropTest';
import { requiredPropTest } from '../../../../tests/propTests/requiredPropTest';
import { sizePropTest } from '../../../../tests/propTests/sizePropTest';
import { validationStatePropTest } from '../../../../tests/propTests/validationStatePropTest';
Expand Down Expand Up @@ -107,6 +108,7 @@ describe('rendering', () => {
expect(within(rootElement).getByText('option 4')).toHaveAttribute('id', 'id__item__key');
},
],
...renderAsRequiredPropTest,
...requiredPropTest,
...sizePropTest,
...validationStatePropTest,
Expand Down
Loading
Loading