|

   Veteran Tools Platform - Developer Documentation

There are no notes for this item.

PropRequired?TypeDefaultDescription
errorMessage No string
name No string
autocomplete No string
label Yes union
options Yes [union]
required No bool
includeBlankOption No bool true
emptyDescription No string
value Yes {
  value: string,
  dirty: bool }
onValueChange Yes func
additionalClass No string
<div id="reactMount" data-tpl="errorableselect">
    <div class="usa-input-error"><label class="usa-input-error-label" for="errorable-select-40"><span class="form-required-span">*</span></label><span class="usa-input-error-message" id="errorable-select-40-error-message" role="alert">This is the error message</span><select aria-describedby="errorable-select-40-error-message"
            id="errorable-select-40" name="Attribute name"><option value=""></option><option value="first option">first option</option><option value="second option">second option</option><option value="third option">third option</option></select></div>
</div>
<script>
    window.currentProps = {
        "package": {
            "name": "department-of-veteran-affairs/jean-pants",
            "version": "0.1.0"
        },
        "assetPath": "/design-system/",
        "isProduction": true,
        "componentSourcePath": "./ErrorableSelect.jsx",
        "errorMessage": "This is the error message",
        "name": "Attribute name",
        "options": ["first option", "second option", "third option"],
        "required": true,
        "value": {
            "value": "Value",
            "dirty": true
        }
    }
</script>
import React from 'react';
import ErrorableSelect from './ErrorableSelect';

export default function ErrorableSelectExample(props) {
  return (
    <ErrorableSelect
      errorMessage={props.errorMessage}
      name={props.name}
      autocomplete={props.autocomplete}
      label={props.label}
      options={props.options}
      required={props.required}
      includeBlankOption={props.includeBlankOption}
      value={props.value}
      onValueChange={props.onValueChange}
      additionalClass={props.additionalClass}
      emptyDescription={props.emptyDescription}
  />)
}
package:
  name: department-of-veteran-affairs/jean-pants
  version: 0.1.0
assetPath: /design-system/
isProduction: true
componentSourcePath: ./ErrorableSelect.jsx
errorMessage: This is the error message
name: Attribute name
options:
  - first option
  - second option
  - third option
required: true
value:
  value: Value
  dirty: true
  • Content:
    import PropTypes from 'prop-types';
    import React from 'react';
    import _ from 'lodash';
    import ToolTip from '../../../Tooltip/Tooltip';
    import { makeField } from '../../../../helpers/fields';
    
    /**
     * A form select with a label that can display error messages.
     */
    
    class ErrorableSelect extends React.Component {
      constructor() {
        super();
        this.handleChange = this.handleChange.bind(this);
      }
    
      componentWillMount() {
        this.selectId = _.uniqueId('errorable-select-');
      }
    
      handleChange(domEvent) {
        this.props.onValueChange(makeField(domEvent.target.value, true));
      }
    
      render() {
        const selectedValue = this.props.value.value;
    
        // Calculate error state.
        let errorSpan = '';
        let errorSpanId = undefined;
        if (this.props.errorMessage) {
          errorSpanId = `${this.selectId}-error-message`;
          errorSpan = (
            <span
              className="usa-input-error-message"
              id={`${errorSpanId}`}
              role="alert">
              {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>;
        }
    
        // Calculate options for select
        let reactKey = 0;
        // TODO(awong): Remove this hack to handle options prop and use invariants instead.
        const options = _.isArray(this.props.options) ? this.props.options : [];
        const optionElements = options.map(obj => {
          let label;
          let value;
          if (_.isString(obj)) {
            label = obj;
            value = obj;
          } else {
            label = obj.label;
            value = obj.value;
          }
          return (
            <option key={++reactKey} value={value}>
              {label}
            </option>
          );
        });
    
        return (
          <div className={this.props.errorMessage ? 'usa-input-error' : undefined}>
            <label
              className={
                this.props.errorMessage !== undefined
                  ? 'usa-input-error-label'
                  : this.props.labelClass
              }
              htmlFor={this.selectId}>
              {this.props.label}
              {requiredSpan}
            </label>
            {errorSpan}
            <select
              className={this.props.selectClass || this.props.additionalClass}
              aria-describedby={errorSpanId}
              id={this.selectId}
              name={this.props.name}
              autoComplete={this.props.autocomplete}
              value={selectedValue}
              onChange={this.handleChange}>
              {this.props.includeBlankOption && (
                <option value="">{this.props.emptyDescription}</option>
              )}
              {optionElements}
            </select>
            {toolTip}
          </div>
        );
      }
    }
    
    ErrorableSelect.propTypes = {
      // Error string to display in the component.
      // When defined, indicates select has a validation error.
      errorMessage: PropTypes.string,
    
      // Select name attribute.
      name: PropTypes.string,
    
      // Select autocomplete attribute.
      autocomplete: PropTypes.string,
    
      // Select field label.
      label: PropTypes.oneOfType([PropTypes.string, PropTypes.element]).isRequired,
    
      // Array of options to populate select.
      options: PropTypes.arrayOf(
        PropTypes.oneOfType([
          PropTypes.string,
          PropTypes.shape({
            label: PropTypes.string,
            value: PropTypes.number
          }),
          PropTypes.shape({
            label: PropTypes.string,
            value: PropTypes.string
          })
        ])
      ).isRequired,
    
      // Render marker indicating field is required.
      required: PropTypes.bool,
    
      // is there an empty selectable thing
      includeBlankOption: PropTypes.bool,
    
      // Description that shows up for the blank option, when includeBlankOption is true
      emptyDescription: PropTypes.string,
    
      /* `value` - object containing:
       *   - `value`: Value of the select field.
       *   - `dirty`: boolean. Whether a field has been touched by the user.
       */
      value: PropTypes.shape({
        value: PropTypes.string,
        dirty: PropTypes.bool
      }).isRequired,
    
      // a function with this prototype: (newValue)
      onValueChange: PropTypes.func.isRequired,
    
      // Additional css class that is added to the select element.
      additionalClass: PropTypes.string
    };
    
    ErrorableSelect.defaultProps = {
      includeBlankOption: true
    };
    
    export default ErrorableSelect;
    
  • URL: /components/raw/errorableselect/ErrorableSelect.jsx
  • Filesystem Path: src/components/form/controls/ErrorableSelect/ErrorableSelect.jsx
  • Size: 4.5 KB
  • Content:
    import React from 'react';
    import { mount,  shallow } from 'enzyme';
    import chaiAsPromised from 'chai-as-promised';
    import chai, { expect } from 'chai';
    import { axeCheck } from '../../../../../lib/testing/helpers';
    
    import ErrorableSelect from './ErrorableSelect.jsx';
    import { makeField } from '../../../../helpers/fields.js';
    
    chai.use(chaiAsPromised);
    
    describe('<ErrorableSelect>', () => {
      const testValue = makeField('');
      const options = [{ value: 1, label: 'first' }, { value: 2, label: 'second' }];
    
      it('calls onValueChange with input value', () => {
        let valueChanged;
        // render component with callback that alters valueChanged with passed argument
        const wrapper = mount(<ErrorableSelect
          label="my label"
          options={options}
          value={testValue}
          onValueChange={(value) => { valueChanged = value; }}/>);
    
        wrapper.find('select').first().simulate('change', { target: { value: 'hello' } });
        expect(valueChanged.value).to.eql('hello');
      });
    
      it('no error styles when errorMessage undefined', () => {
        const tree = shallow(<ErrorableSelect label="my label" options={options} value={testValue} onValueChange={(_update) => {}}/>);
    
        // No error classes.
        expect(tree.find('.usa-input-error')).to.have.lengthOf(0);
        expect(tree.find('.usa-input-error-label')).to.have.lengthOf(0);
        expect(tree.find('.usa-input-error-message')).to.have.lengthOf(0);
    
        // Ensure no unnecessary class names on label w/o error.
        const labels = tree.find('label');
        expect(labels).to.have.lengthOf(1);
        expect(labels.hasClass('')).to.be.true;
    
        // No error means no aria-describedby to not confuse screen readers.
        const selects = tree.find('select');
        expect(selects).to.have.lengthOf(1);
        expect(selects.find('aria-describedby')).to.have.lengthOf(0);
      });
    
      it('should pass aXe check when errorMessage is undefined', () => {
        return axeCheck(<ErrorableSelect label="my label" options={options} value={testValue} onValueChange={(_update) => {}}/>);
      });
    
      it('has error styles when errorMessage is set', () => {
        const tree = shallow(<ErrorableSelect label="my label" options={options} errorMessage="error message" value={testValue} onValueChange={(_update) => {}}/>);
    
        // Ensure all error classes set.
        expect(tree.find('.usa-input-error')).to.have.lengthOf(1);
    
        const labels = tree.find('.usa-input-error-label');
        expect(labels).to.have.lengthOf(1);
        expect(labels.text()).to.equal('my label');
    
        const errorMessages = tree.find('.usa-input-error-message');
        expect(errorMessages).to.have.lengthOf(1);
        expect(errorMessages.text()).to.equal('error message');
    
        // No error means no aria-describedby to not confuse screen readers.
        const selects = tree.find('select');
        expect(selects).to.have.lengthOf(1);
    
        const idNum = selects.props().id.split('-')[2];
        expect(selects.prop('aria-describedby')).to.not.be.undefined;
        expect(selects.prop('aria-describedby')).to.equal(`errorable-select-${idNum}-error-message`);
      });
    
      it('should pass aXe check when errorMessage is set', () => {
        return axeCheck(<ErrorableSelect label="my label" options={options} errorMessage="error message" value={testValue} onValueChange={(_update) => {}}/>);
      });
    
      it('required=false does not have required asterisk', () => {
        const tree = shallow(<ErrorableSelect label="my label" options={options} value={testValue} onValueChange={(_update) => {}}/>);
        expect(tree.find('label').text()).to.equal('my label');
      });
    
      it('should pass aXe check when it is not required', () => {
        return axeCheck(<ErrorableSelect label="my label" options={options} value={testValue} onValueChange={(_update) => {}}/>);
      });
    
      it('required=true has required asterisk', () => {
        const tree = shallow(<ErrorableSelect label="my label" options={options} required value={testValue} onValueChange={(_update) => {}}/>);
        expect(tree.find('label').text()).to.equal('my label*');
      });
    
      it('should pass aXe check when it is required', () => {
        return axeCheck(<ErrorableSelect label="my label" options={options} required value={testValue} onValueChange={(_update) => {}}/>);
      });
    
      it('label attribute propagates', () => {
        const tree = shallow(<ErrorableSelect label="my label" options={options} value={testValue} onValueChange={(_update) => {}}/>);
    
        // Ensure label text is correct.
        const labels = tree.find('label');
        expect(labels).to.have.lengthOf(1);
        expect(labels.text()).to.equal('my label');
    
        // Ensure label htmlFor is attached to select id.
        const selects = tree.find('select');
        const idNum = selects.props().id.split('-')[2];
        expect(selects).to.have.lengthOf(1);
        expect(selects.find('id')).to.not.be.undefined;
        expect(selects.prop('id')).to.equal(`errorable-select-${idNum}`);
      });
    });
    
  • URL: /components/raw/errorableselect/ErrorableSelect.unit.spec.jsx
  • Filesystem Path: src/components/form/controls/ErrorableSelect/ErrorableSelect.unit.spec.jsx
  • Size: 4.8 KB
  • Handle: @errorableselect
  • Preview:
  • Filesystem Path: src/components/form/controls/ErrorableSelect/ErrorableSelect.njk