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

feat(radio-button): add validation props #13317

Merged
merged 11 commits into from
Mar 23, 2023
15 changes: 15 additions & 0 deletions packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -5912,6 +5912,15 @@ Map {
"disabled": Object {
"type": "bool",
},
"helperText": Object {
"type": "node",
},
"invalid": Object {
"type": "bool",
},
"invalidText": Object {
"type": "node",
},
"labelPosition": Object {
"args": Array [
Array [
Expand Down Expand Up @@ -5956,6 +5965,12 @@ Map {
],
"type": "oneOfType",
},
"warn": Object {
"type": "bool",
},
"warnText": Object {
"type": "node",
},
},
"render": [Function],
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,48 @@ export default {
type: 'boolean',
},
},
helperText: {
description:
'Provide text that is used alongside the control label for additional help',
control: {
type: 'text',
},
defaultValue: 'Helper text',
},
invalid: {
description: 'Specify whether the RadioButtonGroup is invalid',
control: {
type: 'boolean',
},
},
invalidText: {
description:
'Provide the text that is displayed when the control is in an invalid state',
control: {
type: 'text',
},
defaultValue: 'Invalid selection',
},
orientation: {
description: 'Provide how radio buttons should be displayed',
control: 'select',
options: ['horizontal', 'vertical'],
},
warn: {
description: 'Specify whether the control is currently in warning state',
control: {
type: 'boolean',
},
defaultValue: false,
},
warnText: {
description:
'Provide the text that is displayed when the control is in warning state',
control: {
type: 'text',
},
defaultValue: 'Please notice the warning',
},
},
};

Expand Down Expand Up @@ -65,7 +107,7 @@ export const Skeleton = () => {

export const Playground = (args) => {
return (
<RadioButtonGroup labelText="Radio Button group" {...args}>
<RadioButtonGroup legendText="Radio Button group" {...args}>
<RadioButton
labelText="Radio button label"
value="radio-1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import React, { createContext, useState } from 'react';
import classNames from 'classnames';
import { Legend } from '../Text';
import { usePrefix } from '../../internal/usePrefix';
import { WarningFilled, WarningAltFilled } from '@carbon/icons-react';

export const RadioButtonGroupContext = createContext();

Expand All @@ -19,13 +20,18 @@ const RadioButtonGroup = React.forwardRef(function RadioButtonGroup(
className,
defaultSelected,
disabled,
helperText,
invalid = false,
invalidText,
labelPosition = 'right',
legendText,
name,
onChange = () => {},
orientation = 'horizontal',
readOnly,
valueSelected,
warn = false,
warnText,
...rest
},
ref
Expand Down Expand Up @@ -75,27 +81,58 @@ const RadioButtonGroup = React.forwardRef(function RadioButtonGroup(
}
}

const showWarning = !readOnly && !invalid && warn;
const showHelper = !invalid && !disabled && !warn;

const wrapperClasses = classNames(`${prefix}--form-item`, className);

const fieldsetClasses = classNames(`${prefix}--radio-button-group`, {
[`${prefix}--radio-button-group--${orientation}`]:
orientation === 'vertical',
[`${prefix}--radio-button-group--label-${labelPosition}`]: labelPosition,
[`${prefix}--radio-button-group--readonly`]: readOnly,
[`${prefix}--radio-button-group--invalid`]: !readOnly && invalid,
[`${prefix}--radio-button-group--warning`]: showWarning,
});

const wrapperClasses = classNames(`${prefix}--form-item`, className);
const helperClasses = classNames(`${prefix}--form__helper-text`, {
[`${prefix}--form__helper-text--disabled`]: disabled,
});

const helper = helperText ? (
<div className={helperClasses}>{helperText}</div>
) : null;

return (
<div className={wrapperClasses} ref={ref}>
<fieldset
className={fieldsetClasses}
disabled={disabled}
data-invalid={invalid ? true : undefined}
aria-readonly={readOnly}
{...rest}>
{legendText && (
<Legend className={`${prefix}--label`}>{legendText}</Legend>
)}
{getRadioButtons()}
</fieldset>
<div className={`${prefix}--checkbox__validation-msg`}>
{!readOnly && invalid && (
<>
<WarningFilled className={`${prefix}--checkbox__invalid-icon`} />
<div className={`${prefix}--form-requirement`}>{invalidText}</div>
</>
)}
{showWarning && (
<>
<WarningAltFilled
className={`${prefix}--checkbox__invalid-icon ${prefix}--checkbox__invalid-icon--warning`}
/>
<div className={`${prefix}--form-requirement`}>{warnText}</div>
</>
)}
{showHelper && helper}
</div>
</div>
);
});
Expand All @@ -121,6 +158,21 @@ RadioButtonGroup.propTypes = {
*/
disabled: PropTypes.bool,

/**
* Provide text that is used alongside the control label for additional help
*/
helperText: PropTypes.node,

/**
* Specify whether the control is currently invalid
*/
invalid: PropTypes.bool,

/**
* Provide the text that is displayed when the control is in an invalid state
*/
invalidText: PropTypes.node,

/**
* Provide where label text should be placed
*/
Expand Down Expand Up @@ -157,6 +209,16 @@ RadioButtonGroup.propTypes = {
* Specify the value that is currently selected in the group
*/
valueSelected: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),

/**
* Specify whether the control is currently in warning state
*/
warn: PropTypes.bool,

/**
* Provide the text that is displayed when the control is in warning state
*/
warnText: PropTypes.node,
};

export default RadioButtonGroup;
50 changes: 50 additions & 0 deletions packages/styles/scss/components/radio-button/_radio-button.scss
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,56 @@ $radio-border-width: 1px !default;
user-select: text;
}

// invalid + warn
.#{$prefix}--radio-button-group--invalid
.#{$prefix}--radio-button
+ .#{$prefix}--radio-button__label
.#{$prefix}--radio-button__appearance {
border-color: $support-error;
}

.#{$prefix}--checkbox__validation-msg {
display: flex;
align-items: flex-end;
margin-top: $spacing-03;
}

.#{$prefix}--checkbox__invalid-icon {
margin: 0 rem(1px) 0 rem(3px);
fill: $support-error;
}

.#{$prefix}--checkbox__invalid-icon--warning {
fill: $support-warning;
}

.#{$prefix}--checkbox__invalid-icon--warning path:first-of-type {
fill: #000000;
}

.#{$prefix}--checkbox__validation-msg .#{$prefix}--form__helper-text {
margin-top: 0;
}

.#{$prefix}--radio-button-group--invalid
+ .#{$prefix}--checkbox__validation-msg
.#{$prefix}--form-requirement,
.#{$prefix}--radio-button-group--warning
+ .#{$prefix}--checkbox__validation-msg
.#{$prefix}--form-requirement {
display: block;
overflow: visible;
max-height: 100%;
margin-top: 0;
margin-left: $spacing-03;
}

.#{$prefix}--radio-button-group--invalid
+ .#{$prefix}--checkbox__validation-msg
.#{$prefix}--form-requirement {
color: $text-error;
}

// Focus

.#{$prefix}--radio-button:focus
Expand Down