There are no notes for this item.
Prop | Required? | Type | Default | Description |
---|---|---|---|---|
errorMessage | No | string | ||
field | Yes | { value: string, dirty: bool } |
||
label | Yes | string | ||
name | No | string | ||
min | No | union | ||
max | No | union | ||
pattern | No | string | ||
placeholder | No | string | ||
required | No | bool | ||
onValueChange | Yes | func | handler for the value change with this prototype: (newValue) | |
additionalClass | No | string |
<div id="reactMount" data-tpl="errorablenumberinput">
<div class="usa-input-error"><label class="usa-input-error-label" for="errorable-number-input-38">Please pick a number</label><span class="usa-input-error-message" role="alert" id="errorable-number-input-38-error-message"><span class="sr-only">Error</span> Error message</span><input
type="number" min="1" max="10" aria-describedby="errorable-number-input-38-error-message" id="errorable-number-input-38" name="Name" placeholder="Numbers are between 1 and 10" /></div>
</div>
<script>
window.currentProps = {
"package": {
"name": "department-of-veteran-affairs/jean-pants",
"version": "0.1.0"
},
"assetPath": "/design-system/",
"isProduction": true,
"componentSourcePath": "./ErrorableNumberInput.jsx",
"errorMessage": "Error message",
"label": "Please pick a number",
"placeholder": "Numbers are between 1 and 10",
"name": "Name",
"min": 1,
"max": 10,
"field": {
"dirty": null
}
}
</script>
import React from 'react';
import ErrorableNumberInput from './ErrorableNumberInput';
export default function ErrorableNumberInputExample(props) {
return (
<ErrorableNumberInput
onValueChange={(e) => e}
{...props}/>
);
}
package:
name: department-of-veteran-affairs/jean-pants
version: 0.1.0
assetPath: /design-system/
isProduction: true
componentSourcePath: ./ErrorableNumberInput.jsx
errorMessage: Error message
label: Please pick a number
placeholder: Numbers are between 1 and 10
name: Name
min: 1
max: 10
field:
dirty: null
import PropTypes from 'prop-types';
import React from 'react';
import _ from 'lodash';
import { makeField } from '../../../../helpers/fields';
/*
* A form input with a label that can display error messages.
*
*/
class ErrorableNumberInput extends React.Component {
constructor() {
super();
this.handleChange = this.handleChange.bind(this);
this.handleBlur = this.handleBlur.bind(this);
}
componentWillMount() {
this.inputId = _.uniqueId('errorable-number-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 errorSpanId = undefined;
// TODO: Look into an alternate way of adding error styling not based on presence of errorMessage:
// There could be cases where there is an error but we don’t want a message to appear, and this
// is not clear right now
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>
);
}
// Calculate required.
let requiredSpan = undefined;
if (this.props.required) {
requiredSpan = <span className="form-required-span">*</span>;
}
return (
<div className={this.props.errorMessage ? 'usa-input-error' : undefined}>
<label
className={this.props.errorMessage !== undefined ? 'usa-input-error-label' : undefined}
htmlFor={this.inputId}>
{this.props.label}
{requiredSpan}
</label>
{errorSpan}
<input
autoComplete={this.props.autocomplete}
className={this.props.additionalClass}
aria-describedby={errorSpanId}
id={this.inputId}
name={this.props.name}
max={this.props.max}
min={this.props.min}
pattern={this.props.pattern}
placeholder={this.props.placeholder}
type="number"
value={this.props.field.value}
onChange={this.handleChange}
onBlur={this.handleBlur}/>
</div>
);
}
}
ErrorableNumberInput.propTypes = {
/* Error string to display in the component. When defined, indicates input has a validation error. */
errorMessage: PropTypes.string,
field: PropTypes.shape({
value: PropTypes.string,
dirty: PropTypes.bool
}).isRequired,
/* `label` - String for the input field label. */
label: PropTypes.string.isRequired,
/* `name` - String for name attribute. */
name: PropTypes.string,
/* minimum number value and maximum of same */
min: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
]),
max: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
]),
/* String specifying the pattern for the input. */
pattern: PropTypes.string,
/* placeholder string for input field. */
placeholder: PropTypes.string,
/* Render marker indicating field is required. */
required: PropTypes.bool,
/**
* handler for the value change with this prototype: (newValue)
*/
onValueChange: PropTypes.func.isRequired,
additionalClass: PropTypes.string
};
export default ErrorableNumberInput;
import React from 'react';
import { shallow } from 'enzyme';
import { axeCheck } from '../../../../../lib/testing/helpers';
import chaiAsPromised from 'chai-as-promised';
import chai, { expect } from 'chai';
import { makeField } from '../../../../helpers/fields.js';
import ReactTestUtils from 'react-dom/test-utils';
import ErrorableNumberInput from './ErrorableNumberInput';
chai.use(chaiAsPromised);
describe('<ErrorableNumberInput>', () => {
const testValue = makeField('');
it('ensure value changes propagate', () => {
let errorableInput;
const updatePromise = new Promise((resolve, _reject) => {
errorableInput = ReactTestUtils.renderIntoDocument(
<ErrorableNumberInput field={testValue} label="test" onValueChange={(update) => { resolve(update); }}/>
);
});
const input = ReactTestUtils.findRenderedDOMComponentWithTag(errorableInput, 'input');
input.value = '1';
ReactTestUtils.Simulate.change(input);
return expect(updatePromise).to.eventually.eql(makeField('1', false));
});
it('ensure blur makes field dirty', () => {
let errorableInput;
const updatePromise = new Promise((resolve, _reject) => {
errorableInput = ReactTestUtils.renderIntoDocument(
<ErrorableNumberInput field={testValue} label="test" onValueChange={(update) => { resolve(update); }}/>
);
});
const input = ReactTestUtils.findRenderedDOMComponentWithTag(errorableInput, 'input');
ReactTestUtils.Simulate.blur(input);
return expect(updatePromise).to.eventually.eql(makeField('', true));
});
it('no error styles when errorMessage undefined', () => {
const tree = shallow(
<ErrorableNumberInput field={testValue} label="my label" 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('foo')).to.be.false;
// No error means no aria-describedby to not confuse screen readers.
const inputs = tree.find('input');
expect(inputs).to.have.lengthOf(1);
expect(inputs.prop('aria-describedby')).to.be.undefined;
});
it('has error styles when errorMessage is set', () => {
const tree = shallow(
<ErrorableNumberInput field={testValue} label="my label" errorMessage="error message" 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 error message');
// No error means no aria-describedby to not confuse screen readers.
const inputs = tree.find('input');
expect(inputs).to.have.lengthOf(1);
expect(inputs.prop('aria-describedby')).to.not.be.undefined;
expect(inputs.prop('aria-describedby')).to.equal(errorMessages.prop('id'));
});
it('required=false does not have required asterisk', () => {
const tree = shallow(
<ErrorableNumberInput field={testValue} label="my label" onValueChange={(_update) => {}}/>);
expect(tree.find('label').text()).to.equal('my label');
});
it('required=true has required asterisk', () => {
const tree = shallow(
<ErrorableNumberInput field={testValue} label="my label" required onValueChange={(_update) => {}}/>);
const label = tree.find('label');
expect(label.text()).to.equal('my label*');
});
it('label attribute propagates', () => {
const tree = shallow(
<ErrorableNumberInput field={testValue} label="my label" 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 input id.
const inputs = tree.find('input');
expect(inputs).to.have.lengthOf(1);
expect(inputs.prop('id')).to.not.be.undefined;
expect(inputs.prop('id')).to.equal(labels.prop('htmlFor'));
});
it('passes aXe check', () => {
const check = axeCheck(<ErrorableNumberInput field={testValue} label="my label" onValueChange={(_update) => {}}/>);
return check;
});
});