Skip to content

Commit 6c2cb61

Browse files
jplOnGitljharb
authored andcommitted
[New] DayPickerSingleDateController: add allowUnselect prop that allows controlling whether unselecting selected days is possible
1 parent 185de16 commit 6c2cb61

File tree

4 files changed

+57
-3
lines changed

4 files changed

+57
-3
lines changed

examples/DayPickerSingleDateControllerWrapper.jsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const propTypes = forbidExtraProps({
1919
initialDate: momentPropTypes.momentObj,
2020
showInput: PropTypes.bool,
2121

22+
allowUnselect: PropTypes.bool,
2223
keepOpenOnDateSelect: PropTypes.bool,
2324
isOutsideRange: PropTypes.func,
2425
isDayBlocked: PropTypes.func,
@@ -56,6 +57,7 @@ const defaultProps = {
5657
showInput: false,
5758

5859
// day presentation and interaction related props
60+
allowUnselect: false,
5961
renderCalendarDay: undefined,
6062
renderDayContents: null,
6163
isDayBlocked: () => false,

src/components/DayPickerSingleDateController.jsx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,18 @@ import {
3434
import DayPicker from './DayPicker';
3535
import getPooledMoment from '../utils/getPooledMoment';
3636

37+
// Default value of the date property. Represents the state
38+
// when there is no date selected.
39+
// TODO: use null
40+
const DATE_UNSET_VALUE = undefined;
41+
3742
const propTypes = forbidExtraProps({
3843
date: momentPropTypes.momentObj,
3944
minDate: momentPropTypes.momentObj,
4045
maxDate: momentPropTypes.momentObj,
4146
onDateChange: PropTypes.func,
4247

48+
allowUnselect: PropTypes.bool,
4349
focused: PropTypes.bool,
4450
onFocusChange: PropTypes.func,
4551
onClose: PropTypes.func,
@@ -102,11 +108,12 @@ const propTypes = forbidExtraProps({
102108
});
103109

104110
const defaultProps = {
105-
date: undefined, // TODO: use null
111+
date: DATE_UNSET_VALUE,
106112
minDate: null,
107113
maxDate: null,
108114
onDateChange() {},
109115

116+
allowUnselect: false,
110117
focused: false,
111118
onFocusChange() {},
112119
onClose() {},
@@ -352,16 +359,19 @@ export default class DayPickerSingleDateController extends React.PureComponent {
352359
if (e) e.preventDefault();
353360
if (this.isBlocked(day)) return;
354361
const {
362+
allowUnselect,
355363
onDateChange,
356364
keepOpenOnDateSelect,
357365
onFocusChange,
358366
onClose,
359367
} = this.props;
360368

361-
onDateChange(day);
369+
const clickedDay = allowUnselect && this.isSelected(day) ? DATE_UNSET_VALUE : day;
370+
371+
onDateChange(clickedDay);
362372
if (!keepOpenOnDateSelect) {
363373
onFocusChange({ focused: false });
364-
onClose({ date: day });
374+
onClose({ date: clickedDay });
365375
}
366376
}
367377

stories/DayPickerSingleDateController.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,14 @@ storiesOf('DayPickerSingleDateController', module)
145145
onNextMonthClick={action('DayPickerSingleDateController::onNextMonthClick')}
146146
/>
147147
)))
148+
.add('with day unselection', withInfo()(() => (
149+
<DayPickerSingleDateControllerWrapper
150+
onOutsideClick={action('DayPickerSingleDateController::onOutsideClick')}
151+
onPrevMonthClick={action('DayPickerSingleDateController::onPrevMonthClick')}
152+
onNextMonthClick={action('DayPickerSingleDateController::onNextMonthClick')}
153+
allowUnselect
154+
/>
155+
)))
148156
.add('with custom input', withInfo()(() => (
149157
<DayPickerSingleDateControllerWrapper
150158
onOutsideClick={action('DayPickerSingleDateController::onOutsideClick')}

test/components/DayPickerSingleDateController_spec.jsx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -539,6 +539,40 @@ describe('DayPickerSingleDateController', () => {
539539
expect(onDateChangeStub.callCount).to.equal(1);
540540
});
541541

542+
it('props.onDateChange receives undefined when day selected', () => {
543+
const date = moment();
544+
const onDateChangeStub = sinon.stub();
545+
const wrapper = shallow((
546+
<DayPickerSingleDateController
547+
onDateChange={onDateChangeStub}
548+
onFocusChange={() => {}}
549+
date={date}
550+
allowUnselect
551+
/>
552+
));
553+
// Click same day as the provided date.
554+
wrapper.instance().onDayClick(date);
555+
expect(onDateChangeStub.callCount).to.equal(1);
556+
expect(onDateChangeStub.getCall(0).args[0]).to.equal(undefined);
557+
});
558+
559+
it('props.onDateChange receives day when allowUnselect is disabled', () => {
560+
const date = moment();
561+
const onDateChangeStub = sinon.stub();
562+
const wrapper = shallow((
563+
<DayPickerSingleDateController
564+
onDateChange={onDateChangeStub}
565+
onFocusChange={() => {}}
566+
date={date}
567+
allowUnselect={false}
568+
/>
569+
));
570+
// Click same day as the provided date.
571+
wrapper.instance().onDayClick(date);
572+
expect(onDateChangeStub.callCount).to.equal(1);
573+
expect(onDateChangeStub.getCall(0).args[0]).to.equal(date);
574+
});
575+
542576
describe('props.keepOpenOnDateSelect is false', () => {
543577
it('props.onFocusChange is called', () => {
544578
const onFocusChangeStub = sinon.stub();

0 commit comments

Comments
 (0)