Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Range and Multi selection modes #508

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 55 additions & 12 deletions lib/src/DatePicker/Calendar.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import withUtils from '../_shared/WithUtils';
/* eslint-disable no-unused-expressions */
export class Calendar extends Component {
static propTypes = {
date: PropTypes.object.isRequired,
date: DomainPropTypes.dateRange.isRequired,
minDate: DomainPropTypes.date,
maxDate: DomainPropTypes.date,
classes: PropTypes.object.isRequired,
Expand All @@ -30,6 +30,8 @@ export class Calendar extends Component {
shouldDisableDate: PropTypes.func,
utils: PropTypes.object.isRequired,
allowKeyboardControl: PropTypes.bool,
multi: PropTypes.bool,
range: PropTypes.bool,
};

static defaultProps = {
Expand All @@ -42,18 +44,22 @@ export class Calendar extends Component {
renderDay: undefined,
allowKeyboardControl: false,
shouldDisableDate: () => false,
multi: false,
range: false,
};

state = {
slideDirection: 'left',
currentMonth: this.props.utils.getStartOfMonth(this.props.date),
currentMonth: this.props.utils.getStartOfMonth(
this.props.date.length > 0 ? this.props.date[this.props.date.length - 1] : this.props.utils.date()),
};

static getDerivedStateFromProps(nextProps, state) {
if (!nextProps.utils.isEqual(nextProps.date, state.lastDate)) {
return {
lastDate: nextProps.date,
currentMonth: nextProps.utils.getStartOfMonth(nextProps.date),
currentMonth: nextProps.utils.getStartOfMonth(
nextProps.date.length > 0 ? nextProps.date[nextProps.date.length - 1] : nextProps.utils.date()),
};
}

Expand All @@ -65,22 +71,45 @@ export class Calendar extends Component {
date, minDate, maxDate, utils, disableFuture, disablePast,
} = this.props;

if (this.shouldDisableDate(date)) {
date.forEach(day =>
this.shouldDisableDate(day) &&
this.onDateSelect(findClosestEnabledDate({
date,
day,
utils,
minDate,
maxDate,
disablePast,
disableFuture,
shouldDisableDate: this.shouldDisableDate,
}), false);
}
}), false)
);
}

onDateSelect = (day, isFinish = true) => {
const { date, utils } = this.props;
this.props.onChange(utils.mergeDateAndTime(day, date), isFinish);
const { date, utils, multi, range } = this.props;

let newDate = day
if (date.length > 0) {
newDate = utils.mergeDateAndTime(day, date[0]);
}

if (multi) {
let i = date.findIndex(o => utils.isEqual(o, day))
if (i === -1) {
newDate = date.concat(newDate)
} else {
newDate = [ ...date ]
newDate.splice(i, 1)
}
} else if (range && date.length === 1) {
newDate = utils.isAfter(newDate, date[0])
? [ date[0], newDate ]
: [ newDate, date[0] ];
} else {
newDate = [ newDate ]
}

this.props.onChange(newDate, isFinish);
};

handleChangeMonth = (newMonth, slideDirection) => {
Expand Down Expand Up @@ -184,22 +213,35 @@ export class Calendar extends Component {
}

renderDays = (week) => {
const { date, renderDay, utils } = this.props;
const { date, renderDay, utils, range } = this.props;
const { hover } = this.state;

const selectedDate = utils.startOfDay(date);
const selectedDate = date.map(utils.startOfDay);
const currentMonthNumber = utils.getMonth(this.state.currentMonth);
const now = utils.date();

return week.map((day) => {
const disabled = this.shouldDisableDate(day);
const dayInCurrentMonth = utils.getMonth(day) === currentMonthNumber;

let additionalProps;

if (range) {
additionalProps = {
prelighted: date.length === 1 && utils.isBetween(day, date[0], hover),
highlighted: date.length > 1 && utils.isBetween(day, date[0], date[1]),
leftCap: utils.isEqual(day, Math.min(date[0], date[1] || hover)),
rightCap: utils.isEqual(day, Math.max(date[0], date[1] || hover)),
}
}

let dayComponent = (
<Day
current={utils.isSameDay(day, now)}
hidden={!dayInCurrentMonth}
disabled={disabled}
selected={utils.isSameDay(selectedDate, day)}
selected={selectedDate.some(o => utils.isSameDay(o, day))}
{...additionalProps}
>
{utils.getDayText(day)}
</Day>
Expand All @@ -216,6 +258,7 @@ export class Calendar extends Component {
dayInCurrentMonth={dayInCurrentMonth}
disabled={disabled}
onSelect={this.onDateSelect}
onMouseEnter={e => this.setState({ hover: day })}
>
{dayComponent}
</DayWrapper>
Expand Down
16 changes: 12 additions & 4 deletions lib/src/DatePicker/DatePicker.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import withUtils from '../_shared/WithUtils';

export class DatePicker extends PureComponent {
static propTypes = {
date: PropTypes.object.isRequired,
date: DomainPropTypes.dateRange.isRequired,
minDate: DomainPropTypes.date,
maxDate: DomainPropTypes.date,
onChange: PropTypes.func.isRequired,
Expand All @@ -25,6 +25,8 @@ export class DatePicker extends PureComponent {
utils: PropTypes.object.isRequired,
shouldDisableDate: PropTypes.func,
allowKeyboardControl: PropTypes.bool,
multi: PropTypes.bool,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems as though these types of selections are wrappers that replace the renderDay implementation. Might it make sense to implement a separate MultiDatePickerWrapper and RangeDatePickerWrapper?

Copy link
Author

@RedHatter RedHatter Jul 23, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went that direction with my first attempt. I got it somewhat working but there were issues and ended up hacky all around. Rolling the features into the core seamed to be a better solution. Honestly it required a lot less modification than I expected.

range: PropTypes.bool,
}

static defaultProps = {
Expand All @@ -40,14 +42,16 @@ export class DatePicker extends PureComponent {
rightArrowIcon: undefined,
renderDay: undefined,
shouldDisableDate: undefined,
multi: false,
range: false,
}

state = {
showYearSelection: this.props.openToYearSelection,
}

get date() {
return this.props.utils.startOfDay(this.props.date);
return this.props.date.map(this.props.utils.startOfDay);
}

get minDate() {
Expand Down Expand Up @@ -83,6 +87,8 @@ export class DatePicker extends PureComponent {
utils,
shouldDisableDate,
allowKeyboardControl,
multi,
range,
} = this.props;
const { showYearSelection } = this.state;

Expand All @@ -93,14 +99,14 @@ export class DatePicker extends PureComponent {
variant="subheading"
onClick={this.openYearSelection}
selected={showYearSelection}
label={utils.getYearText(this.date)}
label={utils.getYearText(this.date[this.date.length - 1])}
/>

<ToolbarButton
variant="display1"
onClick={this.openCalendar}
selected={!showYearSelection}
label={utils.getDatePickerHeaderText(this.date)}
label={utils.getDatePickerHeaderText(this.date[this.date.length - 1])}
/>
</PickerToolbar>

Expand Down Expand Up @@ -133,6 +139,8 @@ export class DatePicker extends PureComponent {
utils={utils}
shouldDisableDate={shouldDisableDate}
allowKeyboardControl={allowKeyboardControl}
multi={multi}
range={range}
/>
}
</Fragment>
Expand Down
19 changes: 17 additions & 2 deletions lib/src/DatePicker/DatePickerWrapper.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ export const DatePickerWrapper = (props) => {
rightArrowIcon,
shouldDisableDate,
value,
multi,
range,
formatSeperator,
...other
} = props;

Expand Down Expand Up @@ -57,6 +60,7 @@ export const DatePickerWrapper = (props) => {
ref={forwardedRef}
value={value}
isAccepted={isAccepted}
formatSeperator={formatSeperator}
{...other}
>
<DatePicker
Expand All @@ -73,6 +77,8 @@ export const DatePickerWrapper = (props) => {
renderDay={renderDay}
rightArrowIcon={rightArrowIcon}
shouldDisableDate={shouldDisableDate}
multi={multi}
range={range}
/>
</ModalWrapper>
)
Expand All @@ -83,7 +89,7 @@ export const DatePickerWrapper = (props) => {

DatePickerWrapper.propTypes = {
/** Datepicker value */
value: DomainPropTypes.date,
value: DomainPropTypes.dateRange,
/** Min selectable date */
minDate: DomainPropTypes.date,
/** Max selectable date */
Expand Down Expand Up @@ -117,10 +123,16 @@ DatePickerWrapper.propTypes = {
/** Enables keyboard listener for moving between days in calendar */
allowKeyboardControl: PropTypes.bool,
forwardedRef: PropTypes.func,
/** Enables selecting multiple dates **/
multi: PropTypes.bool,
/** Enables selecting a range of dates **/
range: PropTypes.bool,
/** String to join multiple values **/
formatSeperator: PropTypes.string,
};

DatePickerWrapper.defaultProps = {
value: new Date(),
value: [ new Date() ],
format: 'MMMM Do',
autoOk: false,
minDate: '1900-01-01',
Expand All @@ -137,6 +149,9 @@ DatePickerWrapper.defaultProps = {
labelFunc: undefined,
shouldDisableDate: undefined,
forwardedRef: undefined,
multi: false,
range: false,
formatSeperator: ', ',
};

export default React.forwardRef((props, ref) => (
Expand Down
37 changes: 34 additions & 3 deletions lib/src/DatePicker/Day.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import classnames from 'classnames';
import withStyles from '@material-ui/core/styles/withStyles';
import IconButton from '@material-ui/core/IconButton';
import { fade } from '@material-ui/core/styles/colorManipulator';

class Day extends PureComponent {
static propTypes = {
Expand All @@ -23,17 +24,18 @@ class Day extends PureComponent {

render() {
const {
children, classes, disabled, hidden, current, selected, ...other
children, classes, disabled, hidden, current, selected,
prelighted, highlighted, leftCap, rightCap, ...other
} = this.props;

const className = classnames(classes.day, {
[classes.hidden]: hidden,
[classes.current]: current,
[classes.selected]: selected,
[classes.selected]: selected || highlighted,
[classes.disabled]: disabled,
});

return (
const icon = (
<IconButton
className={className}
tabIndex={hidden || disabled ? -1 : 0}
Expand All @@ -42,6 +44,21 @@ class Day extends PureComponent {
<span> {children} </span>
</IconButton>
);

if (highlighted || prelighted) {
return (
<div className={classnames({
[classes.leftCap]: leftCap,
[classes.rightCap]: rightCap,
[classes.prelighted]: prelighted,
[classes.highlighted]: highlighted,
})}>
{icon}
</div>
);
} else {
return icon;
}
}
}

Expand Down Expand Up @@ -74,6 +91,20 @@ const styles = theme => ({
pointerEvents: 'none',
color: theme.palette.text.hint,
},
prelighted: {
backgroundColor: fade(theme.palette.action.active, theme.palette.action.hoverOpacity),
},
highlighted: {
backgroundColor: theme.palette.primary.main,
},
leftCap: {
borderTopLeftRadius: '50%',
borderBottomLeftRadius: '50%',
},
rightCap: {
borderTopRightRadius: '50%',
borderBottomRightRadius: '50%',
},
});

export default withStyles(styles, { name: 'MuiPickersDay' })(Day);
6 changes: 3 additions & 3 deletions lib/src/DatePicker/YearSelection.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Year from './Year';

export class YearSelection extends PureComponent {
static propTypes = {
date: PropTypes.shape({}).isRequired,
date: DomainPropTypes.dateRange.isRequired,
minDate: DomainPropTypes.date.isRequired,
maxDate: DomainPropTypes.date.isRequired,
classes: PropTypes.object.isRequired,
Expand All @@ -30,7 +30,7 @@ export class YearSelection extends PureComponent {
onYearSelect = (year) => {
const { date, onChange, utils } = this.props;

const newDate = utils.setYear(date, year);
const newDate = date.map(o => utils.setYear(o, year));
onChange(newDate);
}

Expand All @@ -55,7 +55,7 @@ export class YearSelection extends PureComponent {
const {
minDate, maxDate, date, classes, disablePast, disableFuture, utils,
} = this.props;
const currentYear = utils.getYear(date);
const currentYear = utils.getYear(date[date.length - 1]);

return (
<div className={classes.container}>
Expand Down
Loading