focus-components-v3
Version:
Focus web components to build applications (based on Material Design)
199 lines (178 loc) • 7.17 kB
JavaScript
// Dependencies
import React, {Component, PropTypes} from 'react';
import ReactDOM from 'react-dom';
import moment from 'moment';
import i18next from 'i18next';
import InputText from '../input-text';
import DatePicker from 'react-date-picker';
import isArray from 'lodash/isArray';
import uniqueId from 'lodash/uniqueId';
import closest from 'closest';
const isISOString = value => moment(value, moment.ISO_8601).isValid();
class InputDate extends Component {
constructor(props) {
super(props);
const {rawInputValue} = props;
const state = {
dropDownDate: isISOString(rawInputValue) ? moment(rawInputValue, moment.ISO_8601) : moment(),
inputDate: this._formatDate(rawInputValue),
displayPicker: false
};
this.state = state;
this._inputDateId = uniqueId('input-date-');
}
componentWillMount() {
moment.locale(this.props.locale);
document.addEventListener('click', this._onDocumentClick);
}
componentDidMount() {
const {drops, showDropdowns} = this.props;
const {inputDate: startDate} = this.state;
}
componentWillReceiveProps({rawInputValue}) {
this.setState({
dropDownDate: isISOString(rawInputValue) ? moment(rawInputValue, moment.ISO_8601) : moment(),
inputDate: this._formatDate(rawInputValue)
});
}
componentWillUnmount() {
document.removeEventListener('click', this._onDocumentClick);
}
_isInputFormatCorrect = value => this._parseInputDate(value).isValid();
_parseInputDate = inputDate => {
const {format} = this.props;
return moment(inputDate, format);
};
_formatDate = isoDate => {
let {format} = this.props;
if (isISOString(isoDate)) {
if (isArray(format)) {
format = format[0];
}
return moment(isoDate, moment.ISO_8601).format(format);
} else {
return isoDate;
}
};
_onInputChange = (inputDate, fromBlur) => {
if (this._isInputFormatCorrect(inputDate)) {
const dropDownDate = this._parseInputDate(inputDate);
this.setState({dropDownDate, inputDate});
} else {
this.setState({inputDate});
}
if(fromBlur !== true) {
this.props.onChange(inputDate);
}
};
_onInputBlur = () => {
const {inputDate} = this.state;
this._onInputChange(inputDate, true);
};
_onDropDownChange = (text, date) => {
if (date._isValid) {
this.setState({displayPicker: false}, () => {
this.props.onChange(date.toISOString());
this._onInputChange(this._formatDate(date.toISOString()), true) // Add 12 hours to avoid skipping a day due to different locales
});
}
};
_onInputFocus = () => {
this.setState({displayPicker: true});
};
_onDocumentClick = ({target}) => {
const targetClassAttr = target.getAttribute('class');
const isTriggeredFromPicker = targetClassAttr ? targetClassAttr.includes('dp-cell') : false; //this is the only way to check the target comes from picker cause at this stage, month and year div are unmounted by React.
if(!isTriggeredFromPicker) {
//if target was not triggered inside the date picker, we check it was not triggered by the input
if (closest(target, `[data-id='${this._inputDateId}']`, true) === undefined) {
this.setState({displayPicker: false}, () => this._onInputBlur());
}
}
};
_handleKeyDown = ({key}) => {
if (key === 'Tab' || key === 'Enter') {
this.setState({displayPicker: false}, () => this._onInputBlur());
}
};
getValue = () => {
const {inputDate} = this.state;
const rawValue = this._isInputFormatCorrect(inputDate) ? this._parseInputDate(inputDate).toISOString() : null;
return this.props.beforeValueGetter(rawValue);
};
validate = () => {
const {inputDate} = this.state;
const {isRequired} = this.props;
if ('' === inputDate || !inputDate) {
return ({
isValid: !isRequired,
message: 'focus.components.field.required'
});
} else {
return ({
isValid: this._isInputFormatCorrect(inputDate),
message: i18next.t('focus.components.input.date.invalid', {date: inputDate})
});
}
};
render() {
const {disabled, error, locale, name, placeholder, valid} = this.props;
const {dropDownDate, inputDate, displayPicker} = this.state;
const {_onInputBlur, _onInputChange, _onInputFocus, _onDropDownChange, _onPickerCloserClick, _handleKeyDown} = this;
const inputProps = { disabled };
return (
<div data-focus='input-date' data-id={this._inputDateId}>
<InputText error={error} name={name} onChange={_onInputChange} onKeyDown={_handleKeyDown} onFocus={_onInputFocus} placeholder={placeholder} ref='input' rawInputValue={inputDate} valid={valid} {...inputProps} />
{displayPicker &&
<div data-focus='picker-zone'>
<DatePicker
date={dropDownDate}
hideFooter
locale={locale}
onChange={_onDropDownChange}
ref='picker' />
</div>
}
</div>
);
}
}
InputDate.displayName = 'InputDate';
InputDate.propTypes = {
drops: PropTypes.oneOf(['up', 'down']).isRequired,
error: PropTypes.oneOfType([
PropTypes.string,
PropTypes.bool
]),
locale: PropTypes.string.isRequired,
name: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
beforeValueGetter: PropTypes.func.isRequired,
placeholder: PropTypes.string,
showDropdowns: PropTypes.bool.isRequired,
rawInputValue: (props, propName, componentName) => {
const prop = props[propName];
if (prop && !isISOString(prop)) {
throw new Error(`The date ${prop} provided to the component ${componentName} is not an ISO date. Please provide a valid date string.`);
}
},
valid: PropTypes.bool,
validate: PropTypes.func
};
InputDate.defaultProps = {
drops: 'down',
error: 'focus.components.input.date.error.default',
locale: 'en',
format: 'MM/DD/YYYY',
beforeValueGetter: value => value,
/**
* Default onChange prop, that will log an error.
*/
onChange() {
console.error('You did not give an onChange method to an input date, please check your code.');
},
showDropdowns: true,
valid: true,
validate: isISOString
};
export default InputDate;