Skip to content

Commit

Permalink
Merge pull request #143 from comicrelief/116_select_field_component
Browse files Browse the repository at this point in the history
feat: SelectField component
  • Loading branch information
Heleen-cr authored Jun 26, 2018
2 parents 8858783 + cfdc82b commit 4d5eafe
Show file tree
Hide file tree
Showing 3 changed files with 206 additions and 1 deletion.
183 changes: 183 additions & 0 deletions src/components/SelectField/SelectField.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
/* eslint-env browser */
import React, { Component } from 'react';
import propTypes from 'prop-types';


class SelectField extends Component {
constructor(props) {
super(props);
this.state = {
valid: null,
message: '',
value: this.getSelectedOption(),
showErrorMessage: this.props.showErrorMessage,
};
this.setRef = (element) => {
this.inputRef = element;
};
this.validateField = this.validateField.bind(this);
this.onChangeHandler = this.onChangeHandler.bind(this);
}

/**
* Validate initial state
* (will trigger an update through the validateField function)
*/
componentDidMount() {
this.validateField();
}

/**
* When component has updated send state to parent
*/
componentDidUpdate() {
this.sendStateToParent();
}

/**
* Handle the onChange and onBlur events
* @param e
*/
onChangeHandler(e) {
const value = e.target.value;
this.setState({
value,
showErrorMessage: true,
});
this.validateField(e);
}

/**
* Get the default option, marked as selected from the props.options array
* Returns the value or undefined
*/
getSelectedOption() {
let selected = this.props.options.find(item => item.selected === true);
if (selected !== undefined) {
selected = selected.value;
}
return selected;
}

/**
* Uses isValid callback function sending state, value and field name to parent
*/
sendStateToParent() {
if (typeof this.props.isValid === 'function') {
this.props.isValid(this.state, this.state.value, this.props.name);
}
}

/**
* Create array with option tags from props.options
* @returns {Array}
*/
createOptions() {
const options = [];
this.props.options.map(item =>
options.push(<option
key={item.label}
value={item.value !== undefined ? item.value : ''}
disabled={item.disabled}
>{item.label}</option>));
return options;
}

/**
* Validate the select field and update the state with validation info
* @param e
*/
validateField(e) {
const value = e !== undefined ? e.target.value : this.inputRef.value;
if (this.props.required === true && value === '') {
this.setState({
valid: false,
message: 'This field is required',
value,
});
} else if (this.props.required === true && value) {
this.setState({
valid: true,
message: '',
value,
showErrorMessage: false,
});
} else {
this.setState({
valid: true,
message: '',
value,
showErrorMessage: false,
});
}
}

render() {
const errorClass = this.state.showErrorMessage === true ? 'form__field-error-wrapper' : '';
const extraClass = this.props.extraClass !== '' ? this.props.extraClass : '';
return (
<div
id={`field-wrapper--${this.props.id}`}
className={`form__fieldset form__field--wrapper form__field-wrapper--select ${errorClass} ${extraClass}`}
>
<label
id={`field-label--${this.props.id}`}
htmlFor={`field-select--${this.props.id}`}
className={`form__field-label ${this.props.required ? ' required' : ''}`}
>
{this.props.label}
{!this.props.required &&
<span>&nbsp;(Optional)&nbsp;</span>
}
</label>
<select
id={`field-select--${this.props.id}`}
name={this.props.name && this.props.name}
defaultValue={this.getSelectedOption()}
aria-describedby={`field-label--${this.props.id} field-error--${this.props.id}`}
onBlur={this.onChangeHandler}
onChange={this.onChangeHandler}
ref={this.setRef}
>
{ this.createOptions() }
</select>
{ (this.state.valid === false && this.state.showErrorMessage === true && this.state.message !== '') &&
<div
id={`field-error--${this.props.id}`}
className="form__field-error-container form__field-error-container--select"
aria-live="assertive"
role="status"
>
<span className="form-error">
{this.state.message}
</span>
</div>
}
</div>
);
}
}

SelectField.defaultProps = {
extraClass: '',
isValid: () => {},
showErrorMessage: false,
};

SelectField.propTypes = {
id: propTypes.string.isRequired,
name: propTypes.string.isRequired,
label: propTypes.string.isRequired,
required: propTypes.bool.isRequired,
options: propTypes.arrayOf(propTypes.shape({
label: propTypes.string.isRequired,
value: propTypes.string,
selected: propTypes.bool,
disabled: propTypes.bool,
}).isRequired).isRequired,
extraClass: propTypes.string,
isValid: propTypes.func,
showErrorMessage: propTypes.bool,
};

export default SelectField;
2 changes: 2 additions & 0 deletions src/components/index.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import FileUp from './FileUp/FileUp';
import Footer from './Footer/Footer';
import InputField from './InputField/InputField';
import SelectField from './SelectField/SelectField';
import SchoolsLookUp from './SchoolsLookUp/SchoolsLookUp';
import GrantsNearYou from './GrantsNearYou/GrantsNearYou';
import GrantsInfographics from './GrantsInfographics/GrantsInfographics';

export {
Footer,
InputField,
SelectField,
SchoolsLookUp,
FileUp,
GrantsNearYou,
Expand Down
22 changes: 21 additions & 1 deletion src/stories/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { withKnobs, text, number, boolean } from '@storybook/addon-knobs';
import { withInfo } from '@storybook/addon-info';
import Footer from '../components/Footer/Footer';
import InputField from '../components/InputField/InputField';
import SelectField from '../components/SelectField/SelectField';
import SchoolsLookUpContainer from '../components/SchoolsLookUp/SchoolsLookUpContainer';
import FileUp from '../components/FileUp/FileUp';
import GrantsNearYou from '../components/GrantsNearYou/GrantsNearYou';
Expand Down Expand Up @@ -263,14 +264,33 @@ storiesOf('Input Field', module)
/>);
}),
);
const optionsArray = [
{ label: 'Please select' },
{ label: 'item 1', value: 'itemone' },
{ label: '----------', disabled: true },
{ label: 'item 2', value: 'itemtwo', selected: true },
{ label: 'item 3', value: 'itemthree' },
];
storiesOf('Select Field', module)
.addDecorator(withKnobs)
.add('Select Field',
withInfo('Required')(() => {
id = text('id', 'selectField');
name = text('name', 'selectfield');
label = text('label', 'Select field');
required = boolean('required', true);
return (<SelectField id={id} name={name} label={label} required={required} options={optionsArray} />);
}),
);

storiesOf('File Upload', module)
.addDecorator(withKnobs)
.add('Single',
withInfo('File upload')(() => {
const maxFiles = number('Max Files', 5);
const maxSize = number('Max Size', 2000000);
const types = text('Types', 'image/*, application/pdf');
const story = <FileUp maxFiles={maxFiles} maxSize={maxSize} types={types} />;
const story = <FileUp maxFiles={maxFiles} maxSize={maxSize} types={types} onChange={() => {}} />;

specs(() => describe('File Upload', () => {
it('Should have a label and "click to upload" in it', () => {
Expand Down

0 comments on commit 4d5eafe

Please sign in to comment.