diff --git a/examples/DayPickerSingleDateControllerWrapper.jsx b/examples/DayPickerSingleDateControllerWrapper.jsx index 02d94e931b..44a0a5e1fb 100644 --- a/examples/DayPickerSingleDateControllerWrapper.jsx +++ b/examples/DayPickerSingleDateControllerWrapper.jsx @@ -19,6 +19,7 @@ const propTypes = forbidExtraProps({ initialDate: momentPropTypes.momentObj, showInput: PropTypes.bool, + allowUnselect: PropTypes.bool, keepOpenOnDateSelect: PropTypes.bool, isOutsideRange: PropTypes.func, isDayBlocked: PropTypes.func, @@ -56,6 +57,7 @@ const defaultProps = { showInput: false, // day presentation and interaction related props + allowUnselect: false, renderCalendarDay: undefined, renderDayContents: null, isDayBlocked: () => false, diff --git a/src/components/DayPickerSingleDateController.jsx b/src/components/DayPickerSingleDateController.jsx index 89be9f6ed8..de492c3a24 100644 --- a/src/components/DayPickerSingleDateController.jsx +++ b/src/components/DayPickerSingleDateController.jsx @@ -34,12 +34,18 @@ import { import DayPicker from './DayPicker'; import getPooledMoment from '../utils/getPooledMoment'; +// Default value of the date property. Represents the state +// when there is no date selected. +// TODO: use null +const DATE_UNSET_VALUE = undefined; + const propTypes = forbidExtraProps({ date: momentPropTypes.momentObj, minDate: momentPropTypes.momentObj, maxDate: momentPropTypes.momentObj, onDateChange: PropTypes.func, + allowUnselect: PropTypes.bool, focused: PropTypes.bool, onFocusChange: PropTypes.func, onClose: PropTypes.func, @@ -102,11 +108,12 @@ const propTypes = forbidExtraProps({ }); const defaultProps = { - date: undefined, // TODO: use null + date: DATE_UNSET_VALUE, minDate: null, maxDate: null, onDateChange() {}, + allowUnselect: false, focused: false, onFocusChange() {}, onClose() {}, @@ -352,16 +359,19 @@ export default class DayPickerSingleDateController extends React.PureComponent { if (e) e.preventDefault(); if (this.isBlocked(day)) return; const { + allowUnselect, onDateChange, keepOpenOnDateSelect, onFocusChange, onClose, } = this.props; - onDateChange(day); + const clickedDay = allowUnselect && this.isSelected(day) ? DATE_UNSET_VALUE : day; + + onDateChange(clickedDay); if (!keepOpenOnDateSelect) { onFocusChange({ focused: false }); - onClose({ date: day }); + onClose({ date: clickedDay }); } } diff --git a/stories/DayPickerSingleDateController.js b/stories/DayPickerSingleDateController.js index e7fd81a5f0..ef05c08305 100644 --- a/stories/DayPickerSingleDateController.js +++ b/stories/DayPickerSingleDateController.js @@ -145,6 +145,14 @@ storiesOf('DayPickerSingleDateController', module) onNextMonthClick={action('DayPickerSingleDateController::onNextMonthClick')} /> ))) + .add('with day unselection', withInfo()(() => ( + + ))) .add('with custom input', withInfo()(() => ( { expect(onDateChangeStub.callCount).to.equal(1); }); + it('props.onDateChange receives undefined when day selected', () => { + const date = moment(); + const onDateChangeStub = sinon.stub(); + const wrapper = shallow(( + {}} + date={date} + allowUnselect + /> + )); + // Click same day as the provided date. + wrapper.instance().onDayClick(date); + expect(onDateChangeStub.callCount).to.equal(1); + expect(onDateChangeStub.getCall(0).args[0]).to.equal(undefined); + }); + + it('props.onDateChange receives day when allowUnselect is disabled', () => { + const date = moment(); + const onDateChangeStub = sinon.stub(); + const wrapper = shallow(( + {}} + date={date} + allowUnselect={false} + /> + )); + // Click same day as the provided date. + wrapper.instance().onDayClick(date); + expect(onDateChangeStub.callCount).to.equal(1); + expect(onDateChangeStub.getCall(0).args[0]).to.equal(date); + }); + describe('props.keepOpenOnDateSelect is false', () => { it('props.onFocusChange is called', () => { const onFocusChangeStub = sinon.stub();