Skip to content
This repository was archived by the owner on May 19, 2025. It is now read-only.

Commit 7fbb99d

Browse files
committed
add drag and drop
1 parent 5605173 commit 7fbb99d

File tree

8 files changed

+142
-46
lines changed

8 files changed

+142
-46
lines changed

CHANGELOG.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,9 @@ Date-fns: `fns.parse(dateString)`
109109
Momentjs: `moment(dateString).toDate()`
110110

111111
- `disableDaysBeforeToday` prop removed. use `minDate={new Date()}` instead.
112-
113112
- `firstDayOfWeek` prop removed. It is auto detecting from locale prop.
114-
115113
- `init` prop removed.
116114

115+
### Added
117116

117+
- Date range selection by drag n drop.

README.md

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,15 @@
88
⚠️ Warning: the current branch represents v2 pre-release version. See [v1 branch](https://github.com/Adphorus/react-date-range/tree/v1).
99

1010
A library agnostic React component for choosing dates and date ranges. Uses [date-fns](http://date-fns.org/) for date operations.
11-
Stateless date operations, highly configurable, multiple range selection support, based on js dates.
11+
12+
### Why should you use `react-date-range`?
13+
14+
- Stateless date operations
15+
- Highly configurable
16+
- Multiple range selection
17+
- Based on native js dates
18+
- Drag n Drop selection
19+
- Keyboard friendly
1220

1321
**Live Demo :** [http://adphorus.github.io/react-date-range](http://adphorus.github.io/react-date-range)
1422

@@ -85,7 +93,7 @@ class MyComponent extends Component {
8593
### Options (DateRange, Calendar)
8694
Property | type | Default Value | Desctiption
8795
-------------------------------------|-----------|------------------|-----------------------------------------------------------------
88-
locale | object | enUS from locale | you can view full list from [here](https://github.com/Adphorus/react-date-range/tree/next/src/locale/index.js). Locales directly exported from [`date-fns/locales`](https://date-fns.org/v2.0.0-alpha.7/docs/I18n#supported-languages).
96+
locale | Object | enUS from locale | you can view full list from [here](https://github.com/Adphorus/react-date-range/tree/next/src/locale/index.js). Locales directly exported from [`date-fns/locales`](https://date-fns.org/v2.0.0-alpha.7/docs/I18n#supported-languages).
8997
className | String | | wrapper classname
9098
months | Number | 1 | rendered month count
9199
showSelectionPreview | Boolean | true | show preview on focused/hovered dates
@@ -95,14 +103,15 @@ specialDays | Date[] | [] | defines sp
95103
onPreviewChange | Func | | callback for preview changes. fn()
96104
minDate | Date | | defines minimum date. Disabled earlier dates
97105
maxDate | Date | | defines maximum date. Disabled later dates
98-
showMonthArrow | Boolean | true |
99-
ranges(Calendar) | *Object[] | [] |
106+
showMonthArrow | Boolean | true | show/hide month arrow button
100107
onChange(Calendar) | Func | | callback function for date changes. fn(date: Date)
101108
color(Calendar) | String | `#3d91ff` | defines color for selected date in Calendar
102109
date(Calendar) | Date | | date value for Calendar
103110
onChange(DateRange) | Func | | callback function for range changes. fn(changes). changes contains `startDate` and `endDate` under an object key of changed range
104111
moveRangeOnFirstSelection(DateRange) | Boolean | false | move range on startDate selection. Otherwise endDate will replace with startDate.
105-
dateDisplayFormat(DateRange) | String | `MMM D,YYYY` | selected range preview formatter. checkout [format option](https://date-fns.org/v2.0.0-alpha.7/docs/format)
112+
ranges(Calendar) | *Object[] | [] | Defines ranges. array of range object
113+
showDateDisplay(DateRange) | Boolean | true | show/hide selection display row. Uses `dateDisplayFormat` for formatter
114+
dateDisplayFormat(DateRange) | String | `MMM D,YYYY` | selected range preview formatter. checkout [date-fns's format option](https://date-fns.org/v2.0.0-alpha.7/docs/format)
106115
> *shape of range:
107116
> ```js
108117
> {
@@ -112,12 +121,12 @@ dateDisplayFormat(DateRange) | String | `MMM D,YYYY` | selected r
112121
> key: PropTypes.string,
113122
> autoFocus: PropTypes.bool,
114123
> disabled: PropTypes.bool,
124+
> show: PropTypes.bool,
125+
> showDateDisplay: PropTypes.bool,
115126
> }
116127
>```
117128
118129
TODOs
119130
120131
- make mobile friendly (integrate tap and swipe actions)
121132
- add complex booking customization example with exposed renderer props
122-
- drag and drop date selection
123-

src/Calendar.js

Lines changed: 77 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,15 @@ class Calendar extends PureComponent {
2828
this.renderDays = this.renderDays.bind(this);
2929
this.handleRangeFocusChange = this.handleRangeFocusChange.bind(this);
3030
this.renderDateDisplay = this.renderDateDisplay.bind(this);
31+
this.onSelectionStart = this.onSelectionStart.bind(this);
32+
this.onSelectionEnd = this.onSelectionEnd.bind(this);
3133
this.state = {
3234
focusedDate: calcFocusDate(null, props),
35+
drag: {
36+
status: false,
37+
range: { startDate: null, endDate: null },
38+
disablePreview: false,
39+
},
3340
};
3441
this.styles = generateStyles([coreStyles, props.classNames]);
3542
}
@@ -166,16 +173,59 @@ class Calendar extends PureComponent {
166173
</div>
167174
);
168175
}
176+
onSelectionStart(date) {
177+
this.setState({
178+
drag: {
179+
status: true,
180+
range: { startDate: date, endDate: date },
181+
disablePreview: false,
182+
},
183+
});
184+
}
185+
onSelectionEnd(date) {
186+
const { updateRange, displayMode, onChange } = this.props;
187+
if (displayMode === 'date' || !this.state.drag.status) {
188+
onChange && onChange(date);
189+
return;
190+
}
191+
const newRange = {
192+
startDate: this.state.drag.range.startDate,
193+
endDate: date,
194+
};
195+
if (displayMode !== 'dateRange' || isSameDay(newRange.startDate, date)) {
196+
this.setState({ drag: { status: false, range: {} } }, () => onChange && onChange(date));
197+
} else {
198+
this.setState({ drag: { status: false, range: {} } }, () => {
199+
updateRange && updateRange(newRange);
200+
});
201+
}
202+
}
169203
renderDays(dateOptions, focusedDate) {
170204
const now = new Date();
171-
const { specialDays } = this.props;
205+
const { specialDays, displayMode, focusedRange } = this.props;
206+
const { drag } = this.state;
172207
const minDate = this.props.minDate && startOfDay(this.props.minDate);
173208
const maxDate = this.props.maxDate && endOfDay(this.props.maxDate);
174209
const startDateOfMonth = startOfMonth(focusedDate, dateOptions);
175210
const endDateOfMonth = endOfMonth(focusedDate, dateOptions);
176211
const startDateOfCalendar = startOfWeek(startDateOfMonth, dateOptions);
177212
const endDateOfCalendar = endOfWeek(endDateOfMonth, dateOptions);
178-
213+
let ranges = this.props.ranges;
214+
if (displayMode === 'dateRange' && drag.status) {
215+
let { startDate, endDate } = drag.range;
216+
if (isBefore(endDate, startDate)) {
217+
[startDate, endDate] = [endDate, startDate];
218+
}
219+
ranges = ranges.map((range, i) => {
220+
if (i !== focusedRange[0]) return range;
221+
return {
222+
...range,
223+
startDate,
224+
endDate,
225+
};
226+
});
227+
}
228+
const showPreview = this.props.showSelectionPreview && !drag.disablePreview;
179229
return eachDayOfInterval({ start: startDateOfCalendar, end: endDateOfCalendar }).map(
180230
(day, index) => {
181231
const isStartOfMonth = isSameDay(day, startDateOfMonth);
@@ -187,9 +237,9 @@ class Calendar extends PureComponent {
187237
return (
188238
<DayCell
189239
{...this.props}
240+
ranges={ranges}
190241
day={day}
191-
preview={this.props.showSelectionPreview && this.props.preview}
192-
onSelect={this.props.onChange}
242+
preview={showPreview ? this.props.preview : null}
193243
isSunday={isSunday(day)}
194244
isSpecialDay={isSpecialDay}
195245
isToday={isSameDay(day, now)}
@@ -201,6 +251,20 @@ class Calendar extends PureComponent {
201251
disabled={isOutsideMinMax}
202252
isPassive={!isWithinInterval(day, { start: startDateOfMonth, end: endDateOfMonth })}
203253
styles={this.styles}
254+
onMouseDown={this.onSelectionStart}
255+
onMouseUp={this.onSelectionEnd}
256+
onMouseEnter={date => {
257+
if (!drag.status) return;
258+
this.setState({
259+
drag: {
260+
status: drag.status,
261+
range: { startDate: drag.range.startDate, endDate: date },
262+
disablePreview: true,
263+
},
264+
});
265+
}}
266+
dragRange={drag.range}
267+
drag={drag.status}
204268
/>
205269
);
206270
}
@@ -212,17 +276,20 @@ class Calendar extends PureComponent {
212276
return format(date, this.props.dateDisplayFormat, dateOptions);
213277
}
214278
render() {
215-
const { showDateDisplay } = this.props;
279+
const { showDateDisplay, onPreviewChange } = this.props;
216280
const dateOptions = { locale: this.props.locale };
217281
return (
218-
<div className={classnames(this.styles.calendarWrapper, this.props.className)}>
282+
<div
283+
className={classnames(this.styles.calendarWrapper, this.props.className)}
284+
onMouseUp={() => this.setState({ drag: { status: false, range: {} } })}
285+
onMouseLeave={() => {
286+
this.setState({ drag: { status: false, range: {} } });
287+
}}>
219288
{showDateDisplay && this.renderDateDisplay(dateOptions)}
220289
{this.renderMonthAndYear(this.state.focusedDate)}
221290
<div
222291
className={this.styles.months}
223-
onMouseLeave={() => {
224-
this.props.onPreviewChange && this.props.onPreviewChange();
225-
}}>
292+
onMouseLeave={() => onPreviewChange && onPreviewChange()}>
226293
{new Array(this.props.months).fill(null).map((_, i) => (
227294
<div key={i} className={this.styles.month}>
228295
<div className={this.styles.weekDays}>{this.renderWeekdays(dateOptions)}</div>
@@ -278,6 +345,7 @@ Calendar.propTypes = {
278345
showSelectionPreview: PropTypes.bool,
279346
displayMode: PropTypes.oneOf(['dateRange', 'date']),
280347
color: PropTypes.string,
348+
updateRange: PropTypes.func,
281349
};
282350

283351
export default Calendar;

src/DateRange.js

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class DateRange extends Component {
1919
};
2020
this.styles = generateStyles([coreStyles, props.classNames]);
2121
}
22-
calcNewSelection(value) {
22+
calcNewSelection(value, isSingleValue = true) {
2323
const { focusedRange } = this.state;
2424
const { ranges, onChange, maxDate, moveRangeOnFirstSelection } = this.props;
2525
const selectedRangeIndex = focusedRange[0];
@@ -29,7 +29,10 @@ class DateRange extends Component {
2929
let { startDate, endDate } = selectedRange;
3030
if (!endDate) endDate = new Date(startDate);
3131
let nextFocusRange;
32-
if (focusedRange[1] === 0) {
32+
if (!isSingleValue) {
33+
startDate = value.startDate;
34+
endDate = value.endDate;
35+
} else if (focusedRange[1] === 0) {
3336
// startDate selection
3437
const dayOffset = differenceInCalendarDays(endDate, startDate);
3538
startDate = value;
@@ -38,10 +41,13 @@ class DateRange extends Component {
3841
nextFocusRange = [focusedRange[0], 1];
3942
} else {
4043
endDate = value;
41-
// reverse dates if startDate before endDate
42-
if (isBefore(value, startDate)) {
43-
[startDate, endDate] = [endDate, startDate];
44-
}
44+
}
45+
// reverse dates if startDate before endDate
46+
if (isBefore(endDate, startDate)) {
47+
[startDate, endDate] = [endDate, startDate];
48+
}
49+
50+
if (!nextFocusRange) {
4551
const nextFocusRangeIndex = findNextRangeIndex(this.props.ranges, focusedRange[0]);
4652
nextFocusRange = [nextFocusRangeIndex, 0];
4753
}
@@ -50,13 +56,13 @@ class DateRange extends Component {
5056
nextFocusRange: nextFocusRange,
5157
};
5258
}
53-
setSelection(value) {
59+
setSelection(value, isSingleValue) {
5460
const { onChange, ranges } = this.props;
5561
const { focusedRange } = this.state;
5662
const selectedRangeIndex = focusedRange[0];
5763
const selectedRange = ranges[selectedRangeIndex];
58-
const newSelection = this.calcNewSelection(value);
5964
if (!selectedRange) return;
65+
const newSelection = this.calcNewSelection(value, isSingleValue);
6066
onChange({
6167
[selectedRange.key || `range${selectedRangeIndex + 1}`]: newSelection.range,
6268
});
@@ -84,6 +90,7 @@ class DateRange extends Component {
8490
onRangeFocusChange={this.handleRangeFocusChange}
8591
preview={this.state.preview}
8692
previewColor={selectedRange.color}
93+
updateRange={val => this.setSelection(val, false)}
8794
onPreviewChange={value => {
8895
this.updatePreview(value ? this.calcNewSelection(value).range : null);
8996
}}

0 commit comments

Comments
 (0)