|

   Veteran Tools Platform - Developer Documentation

There are no notes for this item.

PropRequired?TypeDefaultDescription
additionalFieldsetClass No string additional fieldset classes
additionalLegendClass No string additional legend classes
errorMessage No string radio button group error message
label Yes union radio button group field label
name No string name attribute
id No string
toolTipText No string help text for user
tabIndex No number keyboard tab order for radio button group
options Yes [union] array of options to populate group- each item is a string or an object representing an Expanding Group
value Yes {
  value: string,
  dirty: bool }
value object for selected field
value: string value that matches radio button value
dirty: indicates if form is dirty; should be true after any user input
onValueChange Yes func handler for the value change
required No bool toggles required field indicator
<div id="reactMount" data-tpl="errorableradiobuttons">
    <fieldset class="fieldset-input usa-input-error">
        <legend class="legend-label usa-input-error-label">Errorable Radio Buttons</legend><span class="usa-input-error-message" role="alert" id="defaultId-error-message"><span class="sr-only">Error</span> Radio Button errorMessage</span>
        <div class="form-radio-buttons"><input type="radio" autocomplete="false" id="defaultId-0" name="defaultName" value="option 1" /><label name="defaultName-0-label" for="defaultId-0">option 1</label></div>
        <div class="form-radio-buttons"><input type="radio" autocomplete="false" checked="" id="defaultId-1" name="defaultName" value="option 2" /><label name="defaultName-1-label" for="defaultId-1">option 2</label></div>
        <div class="form-expanding-group">
            <div class="form-radio-buttons"><input type="radio" autocomplete="false" id="defaultId-2" name="defaultName" value="expanding option 3" /><label name="defaultName-2-label" for="defaultId-2">option 3 label</label></div><span></span></div>
    </fieldset>
</div>
<script>
    window.currentProps = {
        "package": {
            "name": "department-of-veteran-affairs/jean-pants",
            "version": "0.1.0"
        },
        "assetPath": "/design-system/",
        "isProduction": true,
        "componentSourcePath": "./ErrorableRadioButtons.jsx",
        "id": "defaultId",
        "name": "defaultName",
        "errorMessage": "Radio Button errorMessage",
        "label": "Errorable Radio Buttons",
        "options": ["option 1", "option 2", {
            "label": "option 3 label",
            "value": "expanding option 3",
            "additional": "expanded option 3"
        }],
        "value": {
            "value": "option 2",
            "dirty": null
        },
        "required": null
    }
</script>
import React from 'react';
import ErrorableRadioButtons from './ErrorableRadioButtons';

class StateWrapper extends React.Component {
  constructor(props) {
    super(props);
    this.state = props;
  }

  render() {
    return (
        <ErrorableRadioButtons
          onValueChange={({value}) => {
            this.setState({value:{value, dirty: true}});
          }}
          {...this.state}
          />
        );
  }
}

export default function ErrorableRadioButtonsExample(props) {
  return (
    <StateWrapper
      {...props}
      >
        <ErrorableRadioButtons
          onValueChange={()=>{}}
          {...props}
         />
      </StateWrapper>
      );
}
package:
  name: department-of-veteran-affairs/jean-pants
  version: 0.1.0
assetPath: /design-system/
isProduction: true
componentSourcePath: ./ErrorableRadioButtons.jsx
id: defaultId
name: defaultName
errorMessage: Radio Button errorMessage
label: Errorable Radio Buttons
options:
  - option 1
  - option 2
  - label: option 3 label
    value: expanding option 3
    additional: expanded option 3
value:
  value: option 2
  dirty: null
required: null
  • Content:
    import PropTypes from 'prop-types';
    import React from 'react';
    import _ from 'lodash';
    import classNames from 'classnames';
    
    import ToolTip from '../../../Tooltip/Tooltip';
    import ExpandingGroup from '../ExpandingGroup/ExpandingGroup';
    
    import { makeField } from '../../../../helpers/fields';
    
    /**
     * A radio button group with a label.
     *
     * Validation has the following props.
    
     * `additionalFieldsetClass` - String for any additional fieldset classes.
     * `additionalLegendClass` - String for any additional legend classes.
     * `errorMessage' - String Error message for the radio button group
     * `label` - String for the group field label.
     * `name` - String for the name attribute.
     * `toolTipText` - String with help text for user.
     * `tabIndex` - Number for keyboard tab order.
     * `options` - Array of options to populate group.
     * `required` - is this field required.
     * `value` - string. Value of the select field.
     * `onValueChange` - a function with this prototype: (newValue)
     */
    class ErrorableRadioButtons extends React.Component {
      constructor() {
        super();
        this.handleChange = this.handleChange.bind(this);
        this.getMatchingSubSection = this.getMatchingSubSection.bind(this);
      }
    
      componentWillMount() {
        this.inputId = this.props.id || _.uniqueId('errorable-radio-buttons-');
      }
    
      getMatchingSubSection(checked, optionValue) {
        if (checked && this.props.children) {
          const children = _.isArray(this.props.children)
            ? this.props.children
            : [this.props.children];
          const subsections = children.filter(
            child => child.props.showIfValueChosen === optionValue
          );
          return subsections.length > 0 ? subsections[0] : null;
        }
    
        return null;
      }
    
      handleChange(domEvent) {
        this.props.onValueChange(makeField(domEvent.target.value, true));
      }
    
      render() {
        // TODO: extract error logic into a utility function
        // Calculate error state.
        let errorSpan = '';
        let errorSpanId = undefined;
        if (this.props.errorMessage) {
          errorSpanId = `${this.inputId}-error-message`;
          errorSpan = (
            <span className="usa-input-error-message" role="alert" id={errorSpanId}>
              <span className="sr-only">Error</span> {this.props.errorMessage}
            </span>
          );
        }
    
        // Addes ToolTip if text is provided.
        let toolTip;
        if (this.props.toolTipText) {
          toolTip = (
            <ToolTip
              tabIndex={this.props.tabIndex}
              toolTipText={this.props.toolTipText}/>
          );
        }
    
        // Calculate required.
        let requiredSpan = undefined;
        if (this.props.required) {
          requiredSpan = <span className="form-required-span">*</span>;
        }
    
        const options = _.isArray(this.props.options) ? this.props.options : [];
        const storedValue = this.props.value.value;
        const optionElements = options.map((obj, index) => {
          let optionLabel;
          let optionValue;
          let optionAdditional;
          if (_.isString(obj)) {
            optionLabel = obj;
            optionValue = obj;
          } else {
            optionLabel = obj.label;
            optionValue = obj.value;
            if (obj.additional) {
              optionAdditional = <div>{obj.additional}</div>;
            }
          }
          const checked = optionValue === storedValue ? 'checked=true' : '';
          const matchingSubSection = this.getMatchingSubSection(
            optionValue === storedValue,
            optionValue
          );
          const radioButton = (
            <div
              key={optionAdditional ? undefined : index}
              className="form-radio-buttons">
              <input
                autoComplete="false"
                checked={checked}
                id={`${this.inputId}-${index}`}
                name={this.props.name}
                type="radio"
                value={optionValue}
                onChange={this.handleChange}/>
              <label
                name={`${this.props.name}-${index}-label`}
                htmlFor={`${this.inputId}-${index}`}>
                {optionLabel}
              </label>
              {matchingSubSection}
            </div>
          );
    
          let output = radioButton;
    
          // Return an expanding group for buttons with additional content
          if (optionAdditional) {
            output = (
              <ExpandingGroup
                additionalClass="form-expanding-group-active-radio"
                open={!!checked}
                key={index}>
                {radioButton}
                <div>{optionAdditional}</div>
              </ExpandingGroup>
            );
          }
    
          return output;
        });
    
        const fieldsetClass = classNames('fieldset-input', {
          'usa-input-error': this.props.errorMessage,
          [this.props.additionalFieldsetClass]: this.props.additionalFieldsetClass
        });
    
        const legendClass = classNames('legend-label', {
          'usa-input-error-label': this.props.errorMessage,
          [this.props.additionalLegendClass]: this.props.additionalLegendClass
        });
    
        return (
          <fieldset className={fieldsetClass}>
            <legend className={legendClass}>
              {this.props.label}
              {requiredSpan}
            </legend>
            {errorSpan}
            {optionElements}
            {toolTip}
          </fieldset>
        );
      }
    }
    
    ErrorableRadioButtons.propTypes = {
      /**
       * additional fieldset classes
       */
      additionalFieldsetClass: PropTypes.string,
      /**
       * additional legend classes
       */
      additionalLegendClass: PropTypes.string,
      /**
       * radio button group error message
       */
      errorMessage: PropTypes.string,
      /**
       * radio button group field label
       */
      label: PropTypes.oneOfType([PropTypes.string, PropTypes.element]).isRequired,
      /**
       * name attribute
       */
      name: PropTypes.string,
      id: PropTypes.string,
      /**
       * help text for user
       */
      toolTipText: PropTypes.string,
      /**
       * keyboard tab order for radio button group
       */
      tabIndex: PropTypes.number,
      /**
       * array of options to populate group- each item is a string or an object representing an Expanding Group
       *
       */
      options: PropTypes.arrayOf(
        PropTypes.oneOfType([
          PropTypes.string,
          PropTypes.shape({
            label: PropTypes.oneOfType([PropTypes.string, PropTypes.element])
              .isRequired,
            value: PropTypes.oneOfType([PropTypes.string, PropTypes.bool])
              .isRequired,
            additional: PropTypes.oneOfType([PropTypes.string, PropTypes.element])
          })
        ])
      ).isRequired,
      /**
       * value object for selected field <br/>
       * value: string value that matches radio button value </br>
       * dirty: indicates if form is dirty; should be true after any user input
       */
      value: PropTypes.shape({
        /**
         * value of the select field.
         */
        value: PropTypes.string,
        dirty: PropTypes.bool
      }).isRequired,
      /**
       * handler for the value change
       */
      onValueChange: PropTypes.func.isRequired,
      /**
       * toggles required field indicator
       */
      required: PropTypes.bool
    };
    
    export default ErrorableRadioButtons;
    
  • URL: /components/raw/errorableradiobuttons/ErrorableRadioButtons.jsx
  • Filesystem Path: src/components/form/controls/ErrorableRadioButtons/ErrorableRadioButtons.jsx
  • Size: 6.9 KB
  • Content:
    import React from 'react';
    import { expect } from 'chai';
    import {
      shallow,
      mount
    } from 'enzyme';
    import { axeCheck } from '../../../../../lib/testing/helpers';
    import ErrorableRadioButtons from './ErrorableRadioButtons.jsx';
    import { makeField } from '../../../../helpers/fields.js';
    
    describe('<ErrorableRadioButtons>', () => {
      const nonExpandingOptions = ['yes', 'no'];
      const allExpandingOptions = [{ value: 'yes', label: 'Yes', additional: 'Yes addtional' }, { value: 'no', label: 'No', additional: 'No additional' }];
      const someExpandingOptions =  ['yes', { value: 'no', label: 'No', additional: 'No additional' }, 'maybe'];
    
      it('calls onValueChange with value and dirty state', () => {
        let valueChanged;
        // shallowly render component with callback that alters valueChanged with passed argument
        const wrapper = mount(<ErrorableRadioButtons label="test" options={nonExpandingOptions} value={makeField('test')} onValueChange={(value) => {valueChanged = value;}}/>);
    
        // simulate change event on first input
        wrapper.find('input').first().simulate('change');
    
        // verify that change event value matches first value in options passed to component
        expect(valueChanged.value).to.eql(nonExpandingOptions[0]);
        expect(valueChanged.dirty).to.eql(true);
      });
    
      it('renders label htmlFor attribute with correct input id attribute value', () => {
        const wrapper = shallow(<ErrorableRadioButtons label="test" options={nonExpandingOptions} value={makeField('test')} onValueChange={(value) =>  value}/>);
    
        // gather input id and label for attributes from render component
        const inputIds = wrapper.find('input').map((inputId) => inputId.prop('id'));
        const labelFors = wrapper.find('label').map((labelFor) => labelFor.prop('htmlFor'));
    
        // assert each input id attribute value matches respective label for attribute value
        inputIds.forEach((inputId, index) => expect(inputId).to.eql(labelFors[index]));
      });
    
      it('renders a legend tag with the label attribute', () => {
        const labelValue = 'test';
        const wrapper = shallow(<ErrorableRadioButtons label={labelValue} options={nonExpandingOptions} value={makeField('test')} onValueChange={(value) =>  value}/>);
    
        // assert that legend element was rendered with label value as its text
        const legendText = wrapper.find('legend').text();
        expect(legendText).to.eql(labelValue);
      });
    
      it('passes aXe check when only non-expanding options are rendered', () => {
        const check = axeCheck(<ErrorableRadioButtons label="test" options={nonExpandingOptions} value={makeField('test')} onValueChange={(value) => value}/>);
        return check;
      });
    
      it('passes aXe check when only expanding options are rendered', () => {
        const check = axeCheck(<ErrorableRadioButtons label="test" options={allExpandingOptions} value={makeField('test')} onValueChange={(value) => value}/>);
        return check;
      });
    
      it('passes aXe check when non-expanding and expanding options rendered', () => {
        const check = axeCheck(<ErrorableRadioButtons label="test" options={someExpandingOptions} value={makeField('test')} onValueChange={(value) => value}/>);
        return check;
      });
    });
    
  • URL: /components/raw/errorableradiobuttons/ErrorableRadioButtons.unit.spec.jsx
  • Filesystem Path: src/components/form/controls/ErrorableRadioButtons/ErrorableRadioButtons.unit.spec.jsx
  • Size: 3.2 KB
  • Handle: @errorableradiobuttons
  • Preview:
  • Filesystem Path: src/components/form/controls/ErrorableRadioButtons/ErrorableRadioButtons.njk