diff --git a/CHANGELOG.md b/CHANGELOG.md index 97650569e..38f1b531d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -113,5 +113,14 @@ Momentjs: `moment(dateString).toDate()` - `init` prop removed. ### Added +- `DefinedRanges` component: It's a set of date presets. Receives `inputRanges`, `staticRanges` for setting date ranges. +- `DateRangePicker` component. It's combined version of `DateRange` with `DefinedRanges` component. +- Date range selection by drag. +- Infinite scroll feature. Sample usage: +```js + const horizontalScroll={enabled: true, monthHeight: 300, monthWidth: 300 }; + const verticalScroll={enabled: true, monthHeight: 220, longMonthHeight: 240 }; + + +``` -- Date range selection by drag n drop. diff --git a/README.md b/README.md index 4b7c5c8ac..b433c83b5 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,8 @@ specialDays | Date[] | [] | defines sp onPreviewChange | Func | | callback for preview changes. fn() minDate | Date | | defines minimum date. Disabled earlier dates maxDate | Date | | defines maximum date. Disabled later dates +direction | String | 'vertical' | defines maximum date. Disabled later dates +scroll | Object | { enabled: false }| infinite scroll behaviour configuration. Checkout [Infinite Scroll](#infinite-scrolled-mode) section showMonthArrow | Boolean | true | show/hide month arrow button onChange(Calendar) | Func | | callback function for date changes. fn(date: Date) color(Calendar) | String | `#3d91ff` | defines color for selected date in Calendar @@ -112,6 +114,8 @@ moveRangeOnFirstSelection(DateRange) | Boolean | false | move range ranges(Calendar) | *Object[] | [] | Defines ranges. array of range object showDateDisplay(DateRange) | Boolean | true | show/hide selection display row. Uses `dateDisplayFormat` for formatter 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) + + > *shape of range: > ```js > { @@ -126,6 +130,24 @@ dateDisplayFormat(DateRange) | String | `MMM D,YYYY` | selected r > } >``` +#### Infinite Scrolled Mode + + To enable infinite scroll set `scroll={{enabled: true}}` basically. Infinite scroll feature is affected by `direction`(rendering direction for months) and `months`(for rendered months count) props directly. + If you prefer, you can overwrite calendar sizes with `calendarWidth`/`calendarHeight` or each month's height/withs with `monthWidth`/`monthHeight`/`longMonthHeight` at `scroll` prop. + +```js + // shape of scroll prop + scroll: { + enabled: PropTypes.bool, + monthHeight: PropTypes.number, + longMonthHeight: PropTypes.number, // some months has 1 more row than others + monthWidth: PropTypes.number, // just used when direction="horizontal" + calendarWidth: PropTypes.number, // defaults monthWidth * months + calendarHeight: PropTypes.number, // defaults monthHeight * months + }), +``` + + TODOs - make mobile friendly (integrate tap and swipe actions) diff --git a/demo/src/components/Main.js b/demo/src/components/Main.js index 12243b743..6cc8d1fae 100644 --- a/demo/src/components/Main.js +++ b/demo/src/components/Main.js @@ -67,7 +67,13 @@ export default class Main extends Component { this.state = { dateRange: { selection: { - startDate: addDays(new Date(), 1115), + startDate: new Date(), + endDate: null, + }, + }, + dateRangePickerI: { + selection: { + startDate: new Date(), endDate: null, }, }, @@ -81,7 +87,7 @@ export default class Main extends Component { endDate: addDays(new Date(), 8), }, }, - datePickerInternational: addDays(new Date(), 1167), + datePickerInternational: new Date(), locale: 'ja', dateRangePicker: { selection: { @@ -129,7 +135,6 @@ export default class Main extends Component {
+
+ + +
+
+ + +
+
+
diff --git a/package.json b/package.json index 7c799db45..afc34ffe9 100644 --- a/package.json +++ b/package.json @@ -5,12 +5,13 @@ "main": "dist/index.js", "scripts": { "dev": "NODE_ENV=development & webpack-dev-server --hot --inline", - "prebuild": "rm -rf dist/* & rm -rf demo/dist/*", + "clear": "rm -rf dist/* & rm -rf demo/dist/*", "build": "NODE_ENV=production & yarn build-library & yarn build-demo", "build-demo": "webpack -p", "build-library": "babel ./src --out-dir ./dist --ignore test.js & postcss 'src/styles.scss' -d dist --ext css & postcss 'src/theme/*.scss' -d 'dist/theme' --ext css", "lint": "eslint 'src/**/*.js'", - "test": "jest" + "test": "jest", + "preversion": "yarn clear & yarn build" }, "keywords": [ "react", @@ -33,7 +34,8 @@ }, "dependencies": { "classnames": "^2.2.1", - "prop-types": "^15.5.10" + "prop-types": "^15.5.10", + "react-list": "^0.8.8" }, "peerDependencies": { "react": "^0.14 || ^15.0.0-rc || >=15.0" diff --git a/src/Calendar.js b/src/Calendar.js index 21f764206..087de2b5a 100644 --- a/src/Calendar.js +++ b/src/Calendar.js @@ -1,35 +1,46 @@ import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; -import DayCell, { rangeShape } from './DayCell.js'; -import { calcFocusDate, generateStyles } from './utils'; +import { rangeShape } from './DayCell.js'; +import Month from './Month.js'; +import { calcFocusDate, generateStyles, getMonthDisplayRange } from './utils'; import classnames from 'classnames'; -import { addMonths, startOfDay, endOfDay, addYears, setYear, setMonth } from 'date-fns'; -import defaultLocale from 'date-fns/locale/en-US'; -import coreStyles from './styles'; - +import ReactList from 'react-list'; import { + addMonths, format, eachDayOfInterval, startOfWeek, endOfWeek, isSameDay, + addYears, + setYear, + setMonth, + differenceInCalendarMonths, startOfMonth, endOfMonth, - isSunday, - isWithinInterval, - isBefore, - isAfter, + addDays, + isSameMonth, + differenceInDays, + min, + max, } from 'date-fns'; +import defaultLocale from 'date-fns/locale/en-US'; +import coreStyles from './styles'; class Calendar extends PureComponent { constructor(props, context) { super(props, context); this.changeShownDate = this.changeShownDate.bind(this); - this.renderDays = this.renderDays.bind(this); + this.focusToDate = this.focusToDate.bind(this); + this.updateShownDate = this.updateShownDate.bind(this); this.handleRangeFocusChange = this.handleRangeFocusChange.bind(this); this.renderDateDisplay = this.renderDateDisplay.bind(this); - this.onSelectionStart = this.onSelectionStart.bind(this); - this.onSelectionEnd = this.onSelectionEnd.bind(this); + this.onDragSelectionStart = this.onDragSelectionStart.bind(this); + this.onDragSelectionEnd = this.onDragSelectionEnd.bind(this); + this.onDragSelectionMove = this.onDragSelectionMove.bind(this); + this.dateOptions = { locale: props.locale }; + this.styles = generateStyles([coreStyles, props.classNames]); + this.listSizeCache = {}; this.state = { focusedDate: calcFocusDate(null, props), drag: { @@ -37,14 +48,57 @@ class Calendar extends PureComponent { range: { startDate: null, endDate: null }, disablePreview: false, }, + scrollArea: this.calcScrollArea(props), }; - this.styles = generateStyles([coreStyles, props.classNames]); } - updateShownDate(props) { - const newFocus = calcFocusDate(this.state.focusedDate, props || this.props); - this.setState({ - focusedDate: newFocus, - }); + calcScrollArea(props) { + const { direction, months, scroll } = props; + if (!scroll.enabled) return { enabled: false }; + + const longMonthHeight = scroll.longMonthHeight || scroll.monthHeight; + if (direction === 'vertical') { + return { + enabled: true, + monthHeight: scroll.monthHeight || 220, + longMonthHeight: longMonthHeight || 240, + calendarWidth: 'auto', + calendarHeight: (scroll.calendarHeight || longMonthHeight || 240) * months, + }; + } + return { + enabled: true, + monthWidth: scroll.monthWidth || 332, + calendarWidth: (scroll.calendarWidth || scroll.monthWidth || 332) * months, + monthHeight: longMonthHeight || 300, + calendarHeight: longMonthHeight || 300, + }; + } + focusToDate(date, props = this.props, preventUnnecessary = true) { + if (!props.scroll.enabled) { + this.setState({ focusedDate: date }); + return; + } + const targetMonthIndex = differenceInCalendarMonths(date, props.minDate, this.dateOptions); + const visibleMonths = this.list.getVisibleRange(); + if (preventUnnecessary && visibleMonths.includes(targetMonthIndex)) return; + this.list.scrollTo(targetMonthIndex); + this.setState({ focusedDate: date }); + } + updateShownDate(props = this.props) { + const newProps = props.scroll.enabled + ? { + ...props, + months: this.list.getVisibleRange().length, + } + : props; + const newFocus = calcFocusDate(this.state.focusedDate, newProps); + this.focusToDate(newFocus, newProps); + } + componentDidMount() { + if (this.props.scroll.enabled) { + // prevent react-list's initial render focus problem + setTimeout(this.updateShownDate, 1); + } } componentWillReceiveProps(nextProps) { const propMapper = { @@ -52,6 +106,12 @@ class Calendar extends PureComponent { date: 'date', }; const targetProp = propMapper[nextProps.displayMode]; + if (this.props.locale !== nextProps.locale) { + this.dateOptions = { locale: nextProps.locale }; + } + if (JSON.stringify(this.props.scroll) !== JSON.stringify(nextProps.scroll)) { + this.setState({ scrollArea: this.calcScrollArea(nextProps) }); + } if (nextProps[targetProp] !== this.props[targetProp]) { this.updateShownDate(nextProps); } @@ -63,18 +123,17 @@ class Calendar extends PureComponent { setMonth: () => setMonth(focusedDate, value), setYear: () => setYear(focusedDate, value), }; - this.setState({ - focusedDate: modeMapper[mode](), - }); + const newDate = min([max([modeMapper[mode](), this.props.minDate]), this.props.maxDate]); + console.log(focusedDate, newDate); + this.focusToDate(newDate, this.props, false); } handleRangeFocusChange(rangesIndex, rangeItemIndex) { this.props.onRangeFocusChange && this.props.onRangeFocusChange([rangesIndex, rangeItemIndex]); } - renderMonthAndYear(focusedDate) { - const { showMonthArrow, locale, minDate, maxDate } = this.props; - const upLimit = maxDate ? maxDate.getFullYear() : addYears(new Date(), 20).getFullYear(); - const downLimit = minDate ? minDate.getFullYear() : addYears(new Date(), -100).getFullYear(); - console.log(downLimit, upLimit); + renderMonthAndYear(focusedDate, props, changeShownDate) { + const { showMonthArrow, locale, minDate, maxDate } = props; + const upperYearLimit = maxDate.getFullYear(); + const lowerYearLimit = minDate.getFullYear(); const styles = this.styles; return (
@@ -82,7 +141,7 @@ class Calendar extends PureComponent { ) : null} @@ -90,7 +149,7 @@ class Calendar extends PureComponent { this.changeShownDate('setYear', e.target.value)}> - {new Array(upLimit - downLimit + 1).fill(upLimit).map((val, i) => { + onChange={e => changeShownDate('setYear', e.target.value)}> + {new Array(upperYearLimit - lowerYearLimit + 1).fill(upperYearLimit).map((val, i) => { const year = val - i; return (
); } - renderWeekdays(dateOptions) { + renderWeekdays() { const now = new Date(); - return eachDayOfInterval({ - start: startOfWeek(now, dateOptions), - end: endOfWeek(now, dateOptions), - }).map((day, i) => ( - - {format(day, 'ddd', dateOptions)} - - )); + return ( +
+ {eachDayOfInterval({ + start: startOfWeek(now, this.dateOptions), + end: endOfWeek(now, this.dateOptions), + }).map((day, i) => ( + + {format(day, 'ddd', this.dateOptions)} + + ))} +
+ ); } - renderDateDisplay(dateOptions) { + renderDateDisplay() { const { focusedRange, color, ranges } = this.props; const styles = this.styles; return ( @@ -154,7 +217,7 @@ class Calendar extends PureComponent { @@ -174,16 +237,16 @@ class Calendar extends PureComponent { ); } - onSelectionStart(date) { + onDragSelectionStart(date) { this.setState({ drag: { status: true, range: { startDate: date, endDate: date }, - disablePreview: false, + disablePreview: true, }, }); } - onSelectionEnd(date) { + onDragSelectionEnd(date) { const { updateRange, displayMode, onChange } = this.props; if (displayMode === 'date' || !this.state.drag.status) { onChange && onChange(date); @@ -201,84 +264,26 @@ class Calendar extends PureComponent { }); } } - renderDays(dateOptions, focusedDate) { - const now = new Date(); - const { specialDays, displayMode, focusedRange } = this.props; + onDragSelectionMove(date) { const { drag } = this.state; - const minDate = this.props.minDate && startOfDay(this.props.minDate); - const maxDate = this.props.maxDate && endOfDay(this.props.maxDate); - const startDateOfMonth = startOfMonth(focusedDate, dateOptions); - const endDateOfMonth = endOfMonth(focusedDate, dateOptions); - const startDateOfCalendar = startOfWeek(startDateOfMonth, dateOptions); - const endDateOfCalendar = endOfWeek(endDateOfMonth, dateOptions); - let ranges = this.props.ranges; - if (displayMode === 'dateRange' && drag.status) { - let { startDate, endDate } = drag.range; - if (isBefore(endDate, startDate)) { - [startDate, endDate] = [endDate, startDate]; - } - ranges = ranges.map((range, i) => { - if (i !== focusedRange[0]) return range; - return { - ...range, - startDate, - endDate, - }; - }); - } - const showPreview = this.props.showSelectionPreview && !drag.disablePreview; - return eachDayOfInterval({ start: startDateOfCalendar, end: endDateOfCalendar }).map( - (day, index) => { - const isStartOfMonth = isSameDay(day, startDateOfMonth); - const isEndOfMonth = isSameDay(day, endDateOfMonth); - const isSpecialDay = specialDays.some(specialDay => isSameDay(day, specialDay)); - const isOutsideMinMax = - (minDate && isBefore(day, minDate)) || (maxDate && isAfter(day, maxDate)); - - return ( - { - if (!drag.status) return; - this.setState({ - drag: { - status: drag.status, - range: { startDate: drag.range.startDate, endDate: date }, - disablePreview: true, - }, - }); - }} - dragRange={drag.range} - drag={drag.status} - /> - ); - } - ); + if (!drag.status) return; + this.setState({ + drag: { + status: drag.status, + range: { startDate: drag.range.startDate, endDate: date }, + disablePreview: true, + }, + }); } - formatDateDisplay(date, dateOptions, defaultText) { + formatDateDisplay(date, defaultText) { if (!date) return defaultText; - return format(date, this.props.dateDisplayFormat, dateOptions); + return format(date, this.props.dateDisplayFormat, this.dateOptions); } render() { - const { showDateDisplay, onPreviewChange } = this.props; - const dateOptions = { locale: this.props.locale }; + const { showDateDisplay, onPreviewChange, scroll, direction, maxDate, minDate } = this.props; + const { scrollArea, focusedDate } = this.state; + const isVertical = direction === 'vertical'; return (
{ this.setState({ drag: { status: false, range: {} } }); }}> - {showDateDisplay && this.renderDateDisplay(dateOptions)} - {this.renderMonthAndYear(this.state.focusedDate)} -
onPreviewChange && onPreviewChange()}> - {new Array(this.props.months).fill(null).map((_, i) => ( -
-
{this.renderWeekdays(dateOptions)}
-
- {this.renderDays(dateOptions, addMonths(this.state.focusedDate, i))} -
+ {showDateDisplay && this.renderDateDisplay()} + {this.renderMonthAndYear(focusedDate, this.props, this.changeShownDate)} + {scroll.enabled ? ( +
+ {isVertical && this.renderWeekdays(this.dateOptions)} +
onPreviewChange && onPreviewChange()} + style={{ + width: scrollArea.calendarWidth + 11, + height: scrollArea.calendarHeight + 11, + }} + onScroll={() => { + const visibleMonths = this.list.getVisibleRange(); + // prevent scroll jump with wrong visible value + if (visibleMonths[0] === undefined) return; + const visibleMonth = addMonths(minDate, visibleMonths[0] || 0); + const isFocusedToDifferent = !isSameMonth(visibleMonth, focusedDate); + if (isFocusedToDifferent) this.setState({ focusedDate: visibleMonth }); + }}> + (this.list = target)} + itemSizeEstimator={(index, cache) => { + this.listSizeCache = cache; + if (cache[index]) return cache[index]; + if (!isVertical) return scrollArea.monthWidth; + const monthStep = addMonths(minDate, index); + const { start, end } = getMonthDisplayRange(monthStep, this.dateOptions); + const isLongMonth = differenceInDays(end, start, this.dateOptions) + 1 > 7 * 5; + return isLongMonth ? scrollArea.longMonthHeight : scrollArea.monthHeight; + }} + axis={isVertical ? 'y' : 'x'} + itemRenderer={(index, key) => { + const monthStep = addMonths(minDate, index); + return ( + onPreviewChange && onPreviewChange()} + styles={this.styles} + style={ + isVertical + ? {} + : { height: scrollArea.monthHeight, width: scrollArea.monthWidth } + } + showMonthName + showWeekDays={!isVertical} + /> + ); + }} + />
- ))} -
+
+ ) : ( +
+ {new Array(this.props.months).fill(null).map((_, i) => { + const monthStep = addMonths(this.state.focusedDate, i); + return ( + onPreviewChange && onPreviewChange()} + styles={this.styles} + showWeekDays={!isVertical || i === 0} + showMonthName={!isVertical || i > 0} + /> + ); + })} +
+ )}
); } @@ -313,11 +400,18 @@ Calendar.defaultProps = { ranges: [], focusedRange: [0, 0], dateDisplayFormat: 'MMM D,YYYY', + monthDisplayFormat: 'MMM YYYY', showDateDisplay: true, showSelectionPreview: true, displayMode: 'date', months: 1, color: '#3d91ff', + scroll: { + enabled: false, + }, + direction: 'vertical', + maxDate: addYears(new Date(), 20), + minDate: addYears(new Date(), -100), }; Calendar.propTypes = { @@ -339,6 +433,7 @@ Calendar.propTypes = { }), previewColor: PropTypes.string, dateDisplayFormat: PropTypes.string, + monthDisplayFormat: PropTypes.string, focusedRange: PropTypes.arrayOf(PropTypes.number), months: PropTypes.number, className: PropTypes.string, @@ -347,6 +442,15 @@ Calendar.propTypes = { displayMode: PropTypes.oneOf(['dateRange', 'date']), color: PropTypes.string, updateRange: PropTypes.func, + scroll: PropTypes.shape({ + enabled: PropTypes.bool, + monthHeight: PropTypes.number, + longMonthHeight: PropTypes.number, + monthWidth: PropTypes.number, + calendarWidth: PropTypes.number, + calendarHeight: PropTypes.number, + }), + direction: PropTypes.oneOf(['vertical', 'horizontal']), }; export default Calendar; diff --git a/src/DayCell.js b/src/DayCell.js index af44c73ed..88672a355 100644 --- a/src/DayCell.js +++ b/src/DayCell.js @@ -34,9 +34,18 @@ class DayCell extends Component { } handleMouseEvent(event) { const { day, disabled, onPreviewChange } = this.props; - if (disabled) return null; const stateChanges = {}; + if (disabled) { + onPreviewChange(); + return; + } + switch (event.type) { + case 'mouseenter': + this.props.onMouseEnter(day); + onPreviewChange(day); + stateChanges.hover = true; + break; case 'blur': case 'mouseleave': stateChanges.hover = false; @@ -50,13 +59,8 @@ class DayCell extends Component { stateChanges.active = false; this.props.onMouseUp(day); break; - case 'mouseenter': - this.props.onMouseEnter(day); - onPreviewChange && onPreviewChange(day); - stateChanges.hover = true; - break; case 'focus': - onPreviewChange && onPreviewChange(day); + onPreviewChange(day); break; } if (Object.keys(stateChanges).length) { @@ -163,7 +167,6 @@ class DayCell extends Component { onMouseDown={this.handleMouseEvent} onMouseUp={this.handleMouseEvent} onBlur={this.handleMouseEvent} - onCompositionStart={this.handleMouseEvent} onPauseCapture={this.handleMouseEvent} onKeyDown={this.handleKeyEvent} onKeyUp={this.handleKeyEvent} diff --git a/src/Month.js b/src/Month.js new file mode 100644 index 000000000..775202f3a --- /dev/null +++ b/src/Month.js @@ -0,0 +1,140 @@ +/* eslint-disable no-fallthrough */ +import React, { PureComponent } from 'react'; +import PropTypes from 'prop-types'; +import DayCell, { rangeShape } from './DayCell.js'; +import { + format, + startOfDay, + endOfDay, + startOfWeek, + endOfWeek, + isBefore, + isSameDay, + isAfter, + isSunday, + isWithinInterval, + eachDayOfInterval, +} from 'date-fns'; +import { getMonthDisplayRange } from './utils'; + +function renderWeekdays(styles, dateOptions) { + const now = new Date(); + return ( +
+ {eachDayOfInterval({ + start: startOfWeek(now, dateOptions), + end: endOfWeek(now, dateOptions), + }).map((day, i) => ( + + {format(day, 'ddd', dateOptions)} + + ))} +
+ ); +} + +class Month extends PureComponent { + render() { + const now = new Date(); + const { specialDays, displayMode, focusedRange, drag, styles } = this.props; + const minDate = this.props.minDate && startOfDay(this.props.minDate); + const maxDate = this.props.maxDate && endOfDay(this.props.maxDate); + const monthDisplay = getMonthDisplayRange(this.props.month, this.props.dateOptions); + let ranges = this.props.ranges; + if (displayMode === 'dateRange' && drag.status) { + let { startDate, endDate } = drag.range; + if (isBefore(endDate, startDate)) { + [startDate, endDate] = [endDate, startDate]; + } + ranges = ranges.map((range, i) => { + if (i !== focusedRange[0]) return range; + return { + ...range, + startDate, + endDate, + }; + }); + } + const showPreview = this.props.showSelectionPreview && !drag.disablePreview; + return ( +
+ {this.props.showMonthName ? ( +
+ {format(this.props.month, this.props.monthDisplayFormat)} +
+ ) : null} + {this.props.showWeekDays && renderWeekdays(styles, this.props.dateOptions)} +
+ {eachDayOfInterval({ start: monthDisplay.start, end: monthDisplay.end }).map( + (day, index) => { + const isStartOfMonth = isSameDay(day, monthDisplay.startDateOfMonth); + const isEndOfMonth = isSameDay(day, monthDisplay.endDateOfMonth); + const isSpecialDay = specialDays.some(specialDay => isSameDay(day, specialDay)); + const isOutsideMinMax = + (minDate && isBefore(day, minDate)) || (maxDate && isAfter(day, maxDate)); + return ( + + ); + } + )} +
+
+ ); + } +} + +Month.defaultProps = {}; + +Month.propTypes = { + style: PropTypes.object, + styles: PropTypes.object, + month: PropTypes.object, + drag: PropTypes.object, + dateOptions: PropTypes.object, + preview: PropTypes.shape({ + startDate: PropTypes.object, + endDate: PropTypes.object, + }), + showSelectionPreview: PropTypes.bool, + specialDays: PropTypes.array, + displayMode: PropTypes.oneOf(['dateRange', 'date']), + minDate: PropTypes.object, + maxDate: PropTypes.object, + ranges: PropTypes.arrayOf(rangeShape), + focusedRange: PropTypes.arrayOf(PropTypes.number), + onDragSelectionStart: PropTypes.func, + onDragSelectionEnd: PropTypes.func, + onDragSelectionMove: PropTypes.func, + onMouseLeave: PropTypes.func, + monthDisplayFormat: PropTypes.string, + showWeekDays: PropTypes.bool, + showMonthName: PropTypes.bool, +}; + +export default Month; diff --git a/src/calendar.scss b/src/calendar.scss index 620f53a71..ba8704a9b 100644 --- a/src/calendar.scss +++ b/src/calendar.scss @@ -60,6 +60,15 @@ display: flex; } +.rdrMonthsVertical{ + flex-direction: column; +} + +.rdrMonthsHorizontal > div > div > div{ + display: flex; + flex-direction: row; +} + .rdrMonth{ width: 27.667em; } @@ -80,3 +89,9 @@ } .rdrDateDisplayWrapper{} + +.rdrMonthName{} + +.rdrInfiniteMonths{ + overflow: auto; +} \ No newline at end of file diff --git a/src/dayCell.scss b/src/dayCell.scss index 2ebc58cf3..228671054 100644 --- a/src/dayCell.scss +++ b/src/dayCell.scss @@ -3,6 +3,7 @@ width: calc(100% / 7); position: relative; font: inherit; + cursor: pointer; } .rdrDayNumber { @@ -13,17 +14,8 @@ } } -.rdrDayPassive { - cursor: default; - pointer-events: none; - .rdrInRange, .rdrStartEdge, .rdrEndEdge, .rdrSelected, .rdrDayStartPreview, .rdrDayInPreview, .rdrDayEndPreview{ - display: none; - } -} - .rdrDayDisabled { - cursor: default; - pointer-events: none; + cursor: not-allowed; } diff --git a/src/defaultRanges.js b/src/defaultRanges.js index 848f08ca7..dbd6deaf3 100644 --- a/src/defaultRanges.js +++ b/src/defaultRanges.js @@ -101,6 +101,7 @@ export const defaultInputRanges = [ }, getCurrentValue(range) { if (!isSameDay(range.endDate, defineds.endOfToday)) return '-'; + if (!range.startDate) return '∞'; return differenceInCalendarDays(defineds.endOfToday, range.startDate); }, }, @@ -115,6 +116,7 @@ export const defaultInputRanges = [ }, getCurrentValue(range) { if (!isSameDay(range.startDate, defineds.startOfToday)) return '-'; + if (!range.endDate) return '∞'; return differenceInCalendarDays(range.endDate, defineds.startOfToday); }, }, diff --git a/src/styles.js b/src/styles.js index 95e96c5af..73d3480ce 100644 --- a/src/styles.js +++ b/src/styles.js @@ -44,4 +44,9 @@ export default { staticRangeLabel: 'rdrStaticRangeLabel', dayHovered: 'rdrDayHovered', dayActive: 'rdrDayActive', + staticRangeSelected: 'rdrStaticRangeSelected', + monthName: 'rdrMonthName', + infiniteMonths: 'rdrInfiniteMonths', + monthsVertical: 'rdrMonthsVertical', + monthsHorizontal: 'rdrMonthsHorizontal', }; diff --git a/src/theme/default.scss b/src/theme/default.scss index e7a3f9d12..60b11f3e4 100644 --- a/src/theme/default.scss +++ b/src/theme/default.scss @@ -106,10 +106,15 @@ } } + .rdrMonth{ padding: 0 0.833em 1.666em 0.833em; } +.rdrMonths.rdrMonthsVertical .rdrMonth:first-child .rdrMonthName{ + display: none; +} + .rdrWeekDay { font-weight: 400; line-height: 2.667em; @@ -123,7 +128,6 @@ padding: 0; line-height: 3.000em; height: 3.000em; - cursor: pointer; text-align: center; color: #1d2429; &:focus { @@ -132,8 +136,6 @@ } .rdrDayNumber { - // height: 32px; - // line-height: 32px; outline: 0; font-weight: 300; position: absolute; @@ -163,28 +165,24 @@ } } +.rdrDayToday { + .rdrInRange, .rdrStartEdge, .rdrEndEdge, .rdrSelected{ + & ~ .rdrDayNumber span:after{ + background: #fff; + } + } +} + .rdrDay:not(.rdrDayPassive){ .rdrInRange, .rdrStartEdge, .rdrEndEdge, .rdrSelected{ & ~ .rdrDayNumber{ span{ color: rgba(255, 255, 255, 0.85); - &:after{ - background: rgba(255, 255, 255, 0.85); - } } } } } -.rdrDayPassive { - opacity: 0.5; -} - -.rdrDayDisabled .rdrDayNumber{ - opacity: 0.5; -} - - .rdrSelected, .rdrInRange, .rdrStartEdge, .rdrEndEdge{ background: currentColor; position: absolute; @@ -194,16 +192,23 @@ bottom: 5px; } +.rdrSelected{ + left: 2px; + right: 2px; +} + .rdrInRange{} .rdrStartEdge{ border-top-left-radius: 1.042em; border-bottom-left-radius: 1.042em; + left: 2px; } .rdrEndEdge{ border-top-right-radius: 1.042em; border-bottom-right-radius: 1.042em; + right: 2px; } .rdrSelected{ @@ -214,6 +219,7 @@ .rdrInRange, .rdrEndEdge{ border-top-left-radius: 1.042em; border-bottom-left-radius: 1.042em; + left: 2px; } } @@ -221,6 +227,7 @@ .rdrInRange, .rdrStartEdge{ border-top-right-radius: 1.042em; border-bottom-right-radius: 1.042em; + right: 2px; } } @@ -229,7 +236,7 @@ border-top-left-radius: 1.333em; border-bottom-left-radius: 1.333em; border-left-width: 1px; - left: -2px; + left: 0px; } } @@ -238,7 +245,7 @@ border-top-right-radius: 1.333em; border-bottom-right-radius: 1.333em; border-right-width: 1px; - right: -2px; + right: 0px; } } @@ -262,7 +269,7 @@ border-bottom-width: 1px; border-top-left-radius: 1.333em; border-bottom-left-radius: 1.333em; - left: -2px; + left: 0px; } .rdrDayInPreview{ @@ -276,7 +283,8 @@ border-bottom-width: 1px; border-top-right-radius: 1.333em; border-bottom-right-radius: 1.333em; - right: -2px; + right: 2px; + right: 0px; } .rdrDefinedRangesWrapper{ @@ -284,6 +292,10 @@ width: 226px; border-right: solid 1px #eff2f7; background: #fff; + .rdrStaticRangeSelected{ + color: #3d91ff; + font-weight: 600; + } } .rdrStaticRange{ @@ -341,7 +353,34 @@ position: absolute; top: -2px; bottom: -2px; - left: -2px; - right: -2px; + left: 0px; + right: 0px; background: transparent; -} \ No newline at end of file +} + +.rdrDayPassive{ + pointer-events: none; + .rdrDayNumber span{ + color: #d5dce0; + } + .rdrInRange, .rdrStartEdge, .rdrEndEdge, .rdrSelected, .rdrDayStartPreview, .rdrDayInPreview, .rdrDayEndPreview{ + display: none; + } +} + +.rdrDayDisabled { + background-color: rgb(248, 248, 248); + .rdrDayNumber span{ + color: #aeb9bf; + } + .rdrInRange, .rdrStartEdge, .rdrEndEdge, .rdrSelected, .rdrDayStartPreview, .rdrDayInPreview, .rdrDayEndPreview{ + filter: grayscale(100%) opacity(60%); + } +} + +.rdrMonthName{ + text-align: left; + font-weight: 600; + color: #849095; + padding: 0.833em; +} diff --git a/src/utils.js b/src/utils.js index 7ce8131e8..6a8bffd20 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,5 +1,12 @@ import classnames from 'classnames'; -import { addMonths, areIntervalsOverlapping, startOfMonth, endOfMonth } from 'date-fns'; +import { + addMonths, + areIntervalsOverlapping, + startOfMonth, + endOfMonth, + startOfWeek, + endOfWeek, +} from 'date-fns'; export function calcFocusDate(currentFocusedDate, props) { const { shownDate, date, months, ranges, focusedRange, displayMode } = props; @@ -19,17 +26,19 @@ export function calcFocusDate(currentFocusedDate, props) { } targetInterval.start = startOfMonth(targetInterval.start || new Date()); targetInterval.end = endOfMonth(targetInterval.end || targetInterval.start); - const targetDate = targetInterval.start || targetInterval.end || shownDate || new Date(); + // initial focus - if (!currentFocusedDate) - return shownDate || targetInterval.start || targetInterval.end || new Date(); + if (!currentFocusedDate) return shownDate || targetDate; + + // // just return targetDate for native scrolled calendars + // if (props.scroll.enabled) return targetDate; const currentFocusInterval = { start: startOfMonth(currentFocusedDate), end: endOfMonth(addMonths(currentFocusedDate, months - 1)), }; - // don't change focused if new selection in view area if (areIntervalsOverlapping(targetInterval, currentFocusInterval)) { + // don't change focused if new selection in view area return currentFocusedDate; } return targetDate; @@ -43,6 +52,19 @@ export function findNextRangeIndex(ranges, currentRangeIndex = -1) { return ranges.findIndex(range => range.autoFocus !== false && !range.disabled); } +export function getMonthDisplayRange(date, dateOptions) { + const startDateOfMonth = startOfMonth(date, dateOptions); + const endDateOfMonth = endOfMonth(date, dateOptions); + const startDateOfCalendar = startOfWeek(startDateOfMonth, dateOptions); + const endDateOfCalendar = endOfWeek(endDateOfMonth, dateOptions); + return { + start: startDateOfCalendar, + end: endDateOfCalendar, + startDateOfMonth, + endDateOfMonth, + }; +} + export function generateStyles(sources) { if (!sources.length) return {}; const generatedStyles = sources diff --git a/yarn.lock b/yarn.lock index b75ee2ac0..0f8d03c5f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5220,6 +5220,14 @@ promise@^7.1.1: dependencies: asap "~2.0.3" +prop-types@15: + version "15.6.1" + resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.1.tgz#36644453564255ddda391191fb3a125cbdf654ca" + dependencies: + fbjs "^0.8.16" + loose-envify "^1.3.1" + object-assign "^4.1.1" + prop-types@^15.5.10, prop-types@^15.5.4, prop-types@^15.6.0: version "15.6.0" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.0.tgz#ceaf083022fc46b4a35f69e13ef75aed0d639856" @@ -5382,6 +5390,12 @@ react-hot-loader@^3.1.3: redbox-react "^1.3.6" source-map "^0.6.1" +react-list@^0.8.8: + version "0.8.8" + resolved "https://registry.yarnpkg.com/react-list/-/react-list-0.8.8.tgz#de2cb768dd210fb3e4835b9277f158e9cd403078" + dependencies: + prop-types "15" + react-proxy@^3.0.0-alpha.0: version "3.0.0-alpha.1" resolved "https://registry.yarnpkg.com/react-proxy/-/react-proxy-3.0.0-alpha.1.tgz#4400426bcfa80caa6724c7755695315209fa4b07"