There are no notes for this item.
Prop | Required? | Type | Default | Description |
---|---|---|---|---|
errorMessage | No | string | display error message for input that indicates a validation error | |
label | Yes | any | label for input field | |
placeholder | No | string | text displayed when input has no user provided value | |
name | No | string | input name attribute | |
autocomplete | No | string | input autocomplete attribute | |
required | No | bool | render marker indicating field is required | |
field | Yes | { value: string, dirty: bool } |
value of the input field and if its dirty status | |
additionalClass | No | string | extra attribute for use by CSS selector, specifically by tests | |
charMax | No | number | maximum permitted input length | |
onValueChange | Yes | func | called when input value is changed | |
type | No | string | 'text' | type attribute for input field |
<div id="reactMount" data-tpl="errorabletextinput">
<div class="usa-input-error"><label class="usa-input-error-label" for="errorable-text-input-41">Label</label><span class="usa-input-error-message" role="alert" id="errorable-text-input-41-error-message"><span class="sr-only">Error</span> Error message</span><input type="text"
aria-describedby="errorable-text-input-41-error-message" id="errorable-text-input-41" placeholder="Placeholder" name="Name" maxlength="255" /></div>
</div>
<script>
window.currentProps = {
"package": {
"name": "department-of-veteran-affairs/jean-pants",
"version": "0.1.0"
},
"assetPath": "/design-system/",
"isProduction": true,
"componentSourcePath": "./ErrorableTextInput.jsx",
"errorMessage": "Error message",
"label": "Label",
"placeholder": "Placeholder",
"name": "Name",
"field": {
"dirty": null
},
"charMax": 255
}
</script>
import React from 'react';
import ErrorableTextInput from './ErrorableTextInput';
export default function ErrorableTextInputExample(props) {
return (
<ErrorableTextInput
onValueChange={(e) => e}
{...props}/>
);
}
package:
name: department-of-veteran-affairs/jean-pants
version: 0.1.0
assetPath: /design-system/
isProduction: true
componentSourcePath: ./ErrorableTextInput.jsx
errorMessage: Error message
label: Label
placeholder: Placeholder
name: Name
field:
dirty: null
charMax: 255
import PropTypes from 'prop-types';
import React from 'react';
import _ from 'lodash';
import ToolTip from '../../../Tooltip/Tooltip';
import { makeField } from '../../../../helpers/fields';
/**
* A form input with a label that can display error messages.
*
* Props:
* `errorMessage` - Error string to display in the component.
* When defined, indicates input has a validation error.
* `label` - String for the input field label.
* `name` - String for the input name attribute.
* `toolTipText` - String with help text for user.
* `tabIndex` - Number for keyboard tab order.
* `autocomplete` - String for the input autocomplete attribute.
* `placeholder` - placeholder string for input field.
* `required` - boolean. Render marker indicating field is required.
* `field` - string. Value of the input field.
* `additionalClass` - Extra attribute for use by CSS selector, specifically
* by tests
* `onValueChange` - a function with this prototype: (newValue)
*/
class ErrorableTextInput extends React.Component {
constructor() {
super();
this.handleChange = this.handleChange.bind(this);
this.handleBlur = this.handleBlur.bind(this);
}
componentWillMount() {
this.inputId = _.uniqueId('errorable-text-input-');
}
handleChange(domEvent) {
this.props.onValueChange(
makeField(domEvent.target.value, this.props.field.dirty)
);
}
handleBlur() {
this.props.onValueChange(makeField(this.props.field.value, true));
}
render() {
// Calculate error state.
let errorSpan = '';
let maxCharacters;
let errorSpanId = undefined;
let inputErrorClass = undefined;
let labelErrorClass = 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>
);
inputErrorClass = 'usa-input-error';
labelErrorClass = 'usa-input-error-label';
}
// Addes ToolTip if text is provided.
let toolTip;
if (this.props.toolTipText) {
toolTip = (
<ToolTip
tabIndex={this.props.tabIndex}
toolTipText={this.props.toolTipText}/>
);
}
// Calculate max characters and display '(Max. XX characters)' when max is hit.
if (this.props.field.value) {
if (this.props.charMax === this.props.field.value.length) {
maxCharacters = <small>(Max. {this.props.charMax} characters)</small>;
}
}
// Calculate required.
let requiredSpan = undefined;
if (this.props.required) {
requiredSpan = <span className="form-required-span">*</span>;
}
return (
<div className={inputErrorClass}>
<label className={labelErrorClass} htmlFor={this.inputId}>
{this.props.label}
{requiredSpan}
</label>
{errorSpan}
<input
className={this.props.additionalClass}
aria-describedby={errorSpanId}
id={this.inputId}
placeholder={this.props.placeholder}
name={this.props.name}
tabIndex={this.props.tabIndex}
autoComplete={this.props.autocomplete}
type={this.props.type}
maxLength={this.props.charMax}
value={this.props.field.value}
onChange={this.handleChange}
onBlur={this.handleBlur}/>
{maxCharacters}
{toolTip}
</div>
);
}
}
ErrorableTextInput.propTypes = {
/**
* display error message for input that indicates a validation error
*/
errorMessage: PropTypes.string,
/**
* label for input field
*/
label: PropTypes.any.isRequired,
/**
* text displayed when input has no user provided value
*/
placeholder: PropTypes.string,
/**
* input name attribute
*/
name: PropTypes.string,
/**
* input autocomplete attribute
*/
autocomplete: PropTypes.string,
/**
* render marker indicating field is required
*/
required: PropTypes.bool,
/**
* value of the input field and if its dirty status
*/
field: PropTypes.shape({
value: PropTypes.string,
dirty: PropTypes.bool
}).isRequired,
/**
* extra attribute for use by CSS selector, specifically by tests
*/
additionalClass: PropTypes.string,
/**
* maximum permitted input length
*/
charMax: PropTypes.number,
/**
* called when input value is changed
*/
onValueChange: PropTypes.func.isRequired,
/**
* type attribute for input field
*/
type: PropTypes.string
};
ErrorableTextInput.defaultProps = {
type: 'text'
};
export default ErrorableTextInput;
import React from 'react';
import {
shallow,
mount
} from 'enzyme';
import { axeCheck } from '../../../../../lib/testing/helpers';
import { expect } from 'chai';
import ErrorableTextInput from './ErrorableTextInput.jsx';
import { makeField } from '../../../../helpers/fields.js';
describe('<ErrorableTextInput>', () => {
it('calls onValueChange with input value and dirty state', () => {
let valueChanged;
// shallowly render component with callback that alters valueChanged with passed argument
const wrapper = mount(<ErrorableTextInput
field={makeField('')}
label="test"
onValueChange={(value) => { valueChanged = value; }}/>);
wrapper.find('input').first().simulate('change', { target: { value: 'hello' } });
expect(valueChanged.value).to.eql('hello');
expect(valueChanged.dirty).to.eql(false);
});
it('calls onValueChange with dirty state on blur', () => {
let valueChanged;
// shallowly render component with callback that alters valueChanged with passed argument
const wrapper = mount(<ErrorableTextInput
field={makeField('')}
label="test"
onValueChange={(value) => { valueChanged = value; }}/>);
wrapper.find('input').first().simulate('blur');
expect(valueChanged.dirty).to.eql(true);
});
it('renders a label and a placeholder', () => {
const wrapper = shallow(<ErrorableTextInput
field={makeField('')}
label="test"
placeholder="Placeholder"
onValueChange={(value) => value}/>);
const label = wrapper.find('label');
const input = wrapper.find('input');
expect(label.first().text()).to.eql('test');
expect(input.first().prop('placeholder')).to.eql('Placeholder');
});
it('renders error styling when errorMessage attribute is present', () => {
const wrapper = shallow(<ErrorableTextInput
field={makeField('')}
label="test"
errorMessage="errorMessage"
onValueChange={(value) => value}/>);
const errorStyles = [
'.usa-input-error-label',
'.usa-input-error',
'.usa-input-error-message'
];
// assert that each error style corresponds to one component
errorStyles.forEach((style) =>
expect(wrapper.find(style)).to.have.lengthOf(1));
});
it('renders aria-describedby attribute when errorMessage attribute is present', () => {
const wrapper = shallow(<ErrorableTextInput
field={makeField('')}
label="test"
errorMessage="errorMessage"
onValueChange={(value) => value}/>);
const input = wrapper.find('input');
const errorMessageId = wrapper.find('.usa-input-error-message').first().prop('id');
expect(input.prop('aria-describedby')).to.eql(errorMessageId);
});
it('renders an error message when errorMessage attribute is present', () => {
const wrapper = shallow(<ErrorableTextInput
field={makeField('')}
label="test"
errorMessage="errorMessage"
onValueChange={(value) => value}/>);
const errorMessage = wrapper.find('.usa-input-error-message');
expect(errorMessage).to.have.lengthOf(1);
expect(errorMessage.text()).to.eql('Error errorMessage');
});
it('renders no error styling when errorMessage attribute is not present', () => {
const wrapper = shallow(<ErrorableTextInput
field={makeField('')}
label="test"
onValueChange={(value) => value}/>);
const errorStyles = [
'.usa-input-error-label',
'.usa-input-error',
'.usa-input-error-message'
];
// assert that each error style corresponds to one component
errorStyles.forEach((style) =>
expect(wrapper.find(style)).to.have.lengthOf(0));
});
it('renders no aria-describedby attribute when errorMessage attribute is not present', () => {
const wrapper = shallow(<ErrorableTextInput
field={makeField('')}
label="test"
onValueChange={(value) => value}/>);
expect(wrapper.find('input').prop('aria-describedby')).to.not.exist;
});
it('renders no error message when errorMessage attribute is not present', () => {
const wrapper = shallow(<ErrorableTextInput
field={makeField('')}
label="test"
onValueChange={(value) => value}/>);
expect(wrapper.find('.usa-input-error-message')).to.have.lengthOf(0);
});
it('renders a required asterick when required is true', () => {
const wrapper = shallow(<ErrorableTextInput
field={makeField('')}
label="test"
required
onValueChange={(value) => value}/>);
expect(wrapper.find('label').text()).to.eql('test*');
});
it('renders no required asterick when required is false', () => {
const wrapper = shallow(<ErrorableTextInput
field={makeField('')}
label="test"
onValueChange={(value) => value}/>);
expect(wrapper.find('label').text()).to.eql('test');
});
it('renders the input id as label\'s for attribute value', () => {
const wrapper = shallow(<ErrorableTextInput
field={makeField('')}
label="test"
onValueChange={(value) => value}/>);
const inputId = wrapper.find('input').first().prop('id');
const labelFor = wrapper.find('label').first().prop('htmlFor');
expect(inputId).to.eql(labelFor);
});
it('passes aXe check when no error present', () => {
const check = axeCheck(<ErrorableTextInput
field={makeField('')}
label="test"
placeholder="Placeholder"
onValueChange={(value) => value}/>);
return check;
});
it('passes aXe check when error present', () => {
const check = axeCheck(<ErrorableTextInput
field={makeField('')}
label="test"
placeholder="Placeholder"
errorMessage="error"
onValueChange={(value) => value}/>);
return check;
});
});