diff --git a/src/components/Calendar/index.js b/src/components/Calendar/index.js index 73b9faa48..48850b170 100644 --- a/src/components/Calendar/index.js +++ b/src/components/Calendar/index.js @@ -50,6 +50,9 @@ class Calendar extends PureComponent { scrollArea: this.calcScrollArea(props), }; } + isDateInRange = date => { + return date >= this.props.minDate && date <= this.props.maxDate; + }; getMonthNames() { return [...Array(12).keys()].map(i => this.props.locale.localize.month(i)); } @@ -309,6 +312,7 @@ class Calendar extends PureComponent { } onChange={this.onDragSelectionEnd} onFocus={() => this.handleRangeFocusChange(i, 0)} + isDateInRange={this.isDateInRange} /> this.handleRangeFocusChange(i, 1)} + isDateInRange={this.isDateInRange} /> ); @@ -493,7 +498,7 @@ class Calendar extends PureComponent { isVertical ? this.styles.monthsVertical : this.styles.monthsHorizontal )}> {new Array(this.props.months).fill(null).map((_, i) => { - let monthStep = addMonths(this.state.focusedDate, i);; + let monthStep = addMonths(this.state.focusedDate, i); if (this.props.calendarFocus === 'backwards') { monthStep = subMonths(this.state.focusedDate, this.props.months - 1 - i); } diff --git a/src/components/DateInput/index.js b/src/components/DateInput/index.js index d1864c541..2c6e2a803 100644 --- a/src/components/DateInput/index.js +++ b/src/components/DateInput/index.js @@ -1,14 +1,17 @@ import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import classnames from 'classnames'; -import { format, parse, isValid, isEqual } from 'date-fns'; +import { format, parse, isValid as isValidDateFns, isEqual } from 'date-fns'; class DateInput extends PureComponent { constructor(props, context) { super(props, context); this.state = { - invalid: false, + invalid: { + invalidFormat: false, + outOfRange: false, + }, changed: false, value: this.formatDate(props), }; @@ -22,27 +25,43 @@ class DateInput extends PureComponent { } } + isValid = value => { + return { isValidFormat: isValidDateFns(value), isInRange: this.props.isDateInRange(value) }; + }; + formatDate({ value, dateDisplayFormat, dateOptions }) { - if (value && isValid(value)) { - return format(value, dateDisplayFormat, dateOptions); + if (value) { + const { isInRange, isValidFormat } = this.isValid(value); + if (isInRange && isValidFormat) { + return format(value, dateDisplayFormat, dateOptions); + } } + return ''; } update(value) { - const { invalid, changed } = this.state; + const { + invalid: { invalidFormat, outOfRange }, + changed, + } = this.state; - if (invalid || !changed || !value) { + if (invalidFormat || outOfRange || !changed || !value) { return; } const { onChange, dateDisplayFormat, dateOptions } = this.props; const parsed = parse(value, dateDisplayFormat, new Date(), dateOptions); - - if (isValid(parsed)) { + const { isInRange, isValidFormat } = this.isValid(parsed); + if (isInRange && isValidFormat) { this.setState({ changed: false }, () => onChange(parsed)); } else { - this.setState({ invalid: true }); + this.setState({ + invalid: { + invalidFormat: !isValidFormat, + outOfRange: !isInRange, + }, + }); } } @@ -55,7 +74,14 @@ class DateInput extends PureComponent { }; onChange = e => { - this.setState({ value: e.target.value, changed: true, invalid: false }); + this.setState({ + value: e.target.value, + changed: true, + invalid: { + invalidFormat: false, + outOfRange: false, + }, + }); }; onBlur = () => { @@ -65,10 +91,24 @@ class DateInput extends PureComponent { render() { const { className, readOnly, placeholder, ariaLabel, disabled, onFocus } = this.props; - const { value, invalid } = this.state; + const { + value, + invalid: { invalidFormat, outOfRange }, + } = this.state; + + const tooltipWarningMessage = invalidFormat + ? 'The date format is invalid' + : outOfRange + ? 'The date is out of range' + : ''; return ( - + - {invalid && } + {(invalidFormat || outOfRange) && } + {tooltipWarningMessage && ( + {tooltipWarningMessage} + )} ); } @@ -97,6 +140,7 @@ DateInput.propTypes = { className: PropTypes.string, onFocus: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired, + isDateInRange: PropTypes.func.isRequired, }; DateInput.defaultProps = { diff --git a/src/components/DateInput/index.scss b/src/components/DateInput/index.scss index 9bdf0ddb9..6d18f9feb 100644 --- a/src/components/DateInput/index.scss +++ b/src/components/DateInput/index.scss @@ -4,7 +4,7 @@ input { outline: none; } - + .rdrWarning { position: absolute; font-size: 1.6em; @@ -13,4 +13,35 @@ right: .25em; color: #FF0000; } + &.rdrInvalidDateInput { + border:1px solid #FF0000; + } + + .rdrTooltipWarning { + visibility: hidden; + width: 200px; + background-color: black; + color: #fff; + text-align: center; + border-radius: 6px; + padding: 5px 0; + position: absolute; + z-index: 1; + top: 130%; + left: 50%; + margin-left: -100px; + } + &:hover .rdrTooltipWarning{ + visibility: visible; + } + &:hover .rdrTooltipWarning::after { + content: " "; + position: absolute; + bottom: 100%; + left: 50%; + margin-left: -5px; + border-width: 5px; + border-style: solid; + border-color: transparent transparent black transparent; + } } \ No newline at end of file diff --git a/src/components/DateInput/index.test.js b/src/components/DateInput/index.test.js new file mode 100644 index 000000000..3ae706bed --- /dev/null +++ b/src/components/DateInput/index.test.js @@ -0,0 +1,69 @@ +import React from 'react'; +import { mount } from 'enzyme'; + +import DateInput from './index'; + +const date = new Date('01/01/2021'); + +describe('DateInput tests', () => { + const onChange = jest.fn(); + const onFocus = jest.fn(); + + test('Should set invalidFormat in state to true', () => { + const isDateInRange = jest.fn(); + + const wrapper = mount( + + ); + const input = wrapper.find('input'); + input.simulate('change', { target: { value: 'fooo' } }); + input.simulate('keydown', { key: 'Enter' }); + expect(wrapper.state().invalid.invalidFormat).toEqual(true); + }); + test('Should set outOfRange in state to true', () => { + const isDateInRange = jest.fn(() => false); + + const wrapper = mount( + + ); + const input = wrapper.find('input'); + input.simulate('change', { target: { value: 'Dec 8, 2021' } }); + input.simulate('keydown', { key: 'Enter' }); + expect(wrapper.state().invalid.outOfRange).toEqual(true); + }); + test('Should call this.props.onChange if valid date', () => { + const isDateInRange = jest.fn(() => true); + + const wrapper = mount( + + ); + const input = wrapper.find('input'); + input.simulate('change', { target: { value: 'Dec 8, 2021' } }); + input.simulate('keydown', { key: 'Enter' }); + expect(onChange).toHaveBeenCalledTimes(1); + }); +});