Skip to content

Commit

Permalink
Merge pull request #260 from rdjpalmer/daypicker
Browse files Browse the repository at this point in the history
Update DayPicker(s) to support more efficient state management
  • Loading branch information
rdjpalmer authored May 2, 2017
2 parents dcb3a39 + 1be121d commit 2ad0e8f
Show file tree
Hide file tree
Showing 9 changed files with 154 additions and 124 deletions.
8 changes: 4 additions & 4 deletions components/Calendar/CalendarMonth/CalendarMonth.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,17 +63,17 @@ export default class CalendarMonth extends Component {
const startOfMonth = month.clone().startOf('month');
const endOfMonth = month.clone().endOf('month');
const calendarMonth = getCalendarMonth(
month,
getPreDayCount(month),
getPostDayCount(month),
startOfMonth,
getPreDayCount(startOfMonth),
getPostDayCount(startOfMonth),
);

return (
<table className={ classNames.root }>
<thead className={ classNames.head }>
<tr className={ classNames.row }>
{ head.map((offset) => {
const day = moment().clone().day(offset);
const day = startOfMonth.clone().weekday(offset);
return (
<td key={ `${month.format('MM')}-${day.format('dd')}` }>
<ColumnHeadingComponent
Expand Down
19 changes: 2 additions & 17 deletions components/Calendar/DayPicker/DayPicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,7 @@ export default class DayPicker extends Component {
static propTypes = {
month: momentPropTypes.momentObj,
onInteraction: PropTypes.func,
onHighlight: PropTypes.func,
onMonthChange: PropTypes.func,
selectedDates: PropTypes.arrayOf(momentPropTypes.momentObj),
highlightedDates: PropTypes.arrayOf(momentPropTypes.momentObj),
disabledDates: PropTypes.arrayOf(momentPropTypes.momentObj),
dayProps: PropTypes.object,
accessibilityNextLabel: PropTypes.string,
accessibilityPrevLabel: PropTypes.string,
Expand All @@ -47,9 +43,6 @@ export default class DayPicker extends Component {
onInteraction: noop,
onHighlight: noop,
onMonthChange: noop,
selectedDates: [],
highlightedDates: [],
disabledDates: [],
dayProps: {},
accessibilityNextLabel: 'Go to next month',
accessibilityPrevLabel: 'Go to previous month',
Expand All @@ -70,14 +63,10 @@ export default class DayPicker extends Component {
render() {
const {
month,
selectedDates,
highlightedDates,
disabledDates,
dayProps,
onInteraction,
accessibilityNextLabel,
accessibilityPrevLabel,
onInteraction,
onHighlight,
dayProps,
} = this.props;

return (
Expand Down Expand Up @@ -108,10 +97,6 @@ export default class DayPicker extends Component {
dayProps={ {
...dayProps,
onInteraction,
onHighlight,
selectedDates,
disabledDates,
highlightedDates,
} }
DayComponent={ DayPickerItem }
/>
Expand Down
53 changes: 35 additions & 18 deletions components/Calendar/DayPicker/DayPicker.story.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,24 @@ import React, { Component } from 'react';
import { storiesOf } from '@kadira/storybook';
import { withKnobs, number } from '@kadira/storybook-addon-knobs';
import moment from 'moment';
import DayPicker, { getDates } from './DayPicker';
import DayPicker from './DayPicker';
import { defaultDayState } from './DayPickerItem';

class StateManagedDayPicker extends Component {
state = {
date: '',
month: moment(),
};

getDayState = (day) => {
const { date } = this.state;
return Object.assign({}, defaultDayState, {
isSelected: day && date && day.isSame(date, 'day'),
isFirstSelected: day && date && day.isSame(date, 'day'),
isLastSelected: day && date && day.isSame(date, 'day'),
});
};

handleInteraction = (e, date) => {
this.setState({ date });
};
Expand All @@ -19,7 +29,7 @@ class StateManagedDayPicker extends Component {
};

render() {
const { date, month } = this.state;
const { month } = this.state;

return (
<div>
Expand All @@ -28,7 +38,9 @@ class StateManagedDayPicker extends Component {
month={ month }
onInteraction={ this.handleInteraction }
onMonthChange={ this.handleMonthChange }
selectedDates={ getDates(date) }
dayProps={ {
getDayState: this.getDayState,
} }
/>
</div>
);
Expand All @@ -43,25 +55,30 @@ const today = moment();
stories
.add('Single date selected', () => (
<DayPicker
selectedDates={ getDates(moment()) }
month={ moment({ month: number('month', today.month()) }) }
dayProps={ {
getDayState: d => Object.assign({}, defaultDayState, {
isSelected: d && d.isSame(moment(), 'day'),
isFirstSelected: d && d.isSame(moment(), 'day'),
isLastSelected: d && d.isSame(moment(), 'day'),
}),
} }
month={ moment().month(4) }
/>
))
.add('Multiple dates selected', () => (
<DayPicker
selectedDates={ getDates(moment(), moment().add(5, 'day')) }
month={ moment({ month: number('month', today.month()) }) }
/>
))
.add('Single date highlighted', () => (
<DayPicker
highlightedDates={ getDates(moment()) }
month={ moment({ month: number('month', today.month()) }) }
/>
))
.add('Multiple dates highlighted', () => (
<DayPicker
highlightedDates={ getDates(moment(), moment().add(5, 'day')) }
dayProps={ {
getDayState: (d) => {
const startDate = moment().add(-5, 'day');
const endDate = moment().add(5, 'day');

return Object.assign({}, defaultDayState, {
isSelected: d && d.isAfter(startDate, 'day') && d.isBefore(endDate, 'day'),
isFirstSelected: d && d.isSame(startDate, 'day'),
isLastSelected: d && d.isSame(endDate, 'day'),
});
},
} }
month={ moment({ month: number('month', today.month()) }) }
/>
))
Expand Down
2 changes: 1 addition & 1 deletion components/Calendar/DayPicker/DayPickerItem.css
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

.disabled {
pointer-events: none;
color: var(--color-grey);
color: var(--color-greyLight);
}

.firstSelected.lastSelected {
Expand Down
96 changes: 30 additions & 66 deletions components/Calendar/DayPicker/DayPickerItem.js
Original file line number Diff line number Diff line change
@@ -1,64 +1,45 @@
import React, { Component, PropTypes } from 'react';
import cx from 'classnames';
import momentPropTypes from 'react-moment-proptypes';
import difference from 'lodash/fp/difference';

import css from './DayPickerItem.css';
import noop from '../../../utils/noop';
import { ENTER } from '../../../constants/keycodes';
import CalendarItem from '../CalendarItem/CalendarItem';

const isDisabledState = (day, disabledDates) => day
&& disabledDates.length > 0
&& disabledDates.some(date => day.isSame(date, 'day'));
export const defaultDayState = {
isDisabled: false,
isSelected: false,
isFirstSelected: false,
isLastSelected: false,
isHighlighted: false,
isFirstHighlighted: false,
isLastHighlighted: false,
};

const defaultGetDateState = () => defaultDayState;

export default class DayPickerItem extends Component {
static propTypes = {
day: momentPropTypes.momentObj,
selectedDates: PropTypes.arrayOf(momentPropTypes.momentObj),
highlightedDates: PropTypes.arrayOf(momentPropTypes.momentObj),
disabledDates: PropTypes.arrayOf(momentPropTypes.momentObj),
getDayState: PropTypes.func,
onInteraction: PropTypes.func,
onHighlight: PropTypes.func,
};

static defaultProps = {
selectedDates: [],
highlightedDates: [],
disabledDates: [],
getDayState: defaultGetDateState,
onInteraction: noop,
onMouseOver: noop,
};

constructor(props) {
super(props);

this.state = {
disabled: isDisabledState(props.day, props.disabledDates),
};
}

componentWillReceiveProps(nextProps) {
const { day: currentDay, disabledDates: currentDisabled } = this.props;
const { day: nextDay, disabledDates: nextDisabled } = nextProps;

if (
(currentDay && nextDay && !currentDay.isSame(nextDay)) ||
difference(currentDisabled, nextDisabled).length > 0
) {
this.setState({
disabled: isDisabledState(nextDay, nextDisabled),
});
}
}

handleInteraction = (e) => {
const { type, keyCode } = e;
const { disabled } = this.state;
const { day, onInteraction } = this.props;
const { day, onInteraction, getDayState } = this.props;
const { isDisabled } = getDayState(day);
let handled = false;

if (!disabled && onInteraction) {
if (!isDisabled && onInteraction) {
e.persist();
e.preventDefault();

Expand All @@ -77,9 +58,10 @@ export default class DayPickerItem extends Component {
}

handleHighlight = (e) => {
const { disabled } = this.state;
const { day, onHighlight } = this.props;
if (!disabled && onHighlight) onHighlight(e, day);
const { day, onHighlight, getDayState } = this.props;
const { isDisabled } = getDayState(day);

if (!isDisabled && onHighlight) onHighlight(e, day);
}

handleMouseOver = this.handleHighlight;
Expand All @@ -88,43 +70,25 @@ export default class DayPickerItem extends Component {
render() {
const {
day,
selectedDates,
highlightedDates,
disabledDates: _disabledDates,
onHighlight: _onHighlight,
onInteraction: _onInteraction,
getDayState,
...rest,
} = this.props;

const { disabled } = this.state;
const state = getDayState(day);

let className = cx(
const className = cx(
css.root,
disabled ? css.disabled : null,
state.isDisabled ? css.disabled : null,
state.isHighlighted ? css.highlighted : null,
state.isSelected ? css.selected : null,
state.isFirstSelected ? css.firstSelected : null,
state.isLastSelected ? css.lastSelected : null,
state.isFirstHighlighted ? css.firstHighlighted : null,
state.isLastHighlighted ? css.lastHighlighted : null,
);

if (day) {
if (highlightedDates.length > 0) {
className = cx(
className,
highlightedDates.some(date => day.isSame(date, 'day')) ? css.highlighted : null,
day.isSame(highlightedDates[0], 'day') ? css.firstHighlighted : null,
day.isSame(highlightedDates[highlightedDates.length - 1], 'day')
? css.lastHighlighted
: null,
);
}

if (selectedDates.length > 0) {
className = cx(
className,
selectedDates.some(date => day.isSame(date, 'day')) ? css.selected : null,
day.isSame(selectedDates[0], 'day') ? css.firstSelected : null,
day.isSame(selectedDates[selectedDates.length - 1], 'day') ? css.lastSelected : null,
);
}
}

return (
<CalendarItem
{ ...rest }
Expand Down
8 changes: 6 additions & 2 deletions components/Calendar/DayPicker/DayPickerItem.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,9 @@ describe('DayPickerItem', () => {
ref={ (c) => { component = c; } }
day={ now }
onInteraction={ spy }
disabledDates={ [now] }
getDayState={ () => ({
isDisabled: true,
}) }
/>,
div
);
Expand Down Expand Up @@ -168,7 +170,9 @@ describe('DayPickerItem', () => {
ref={ (c) => { component = c; } }
day={ now }
onHighlight={ spy }
disabledDates={ [now] }
getDayState={ () => ({
isDisabled: true,
}) }
/>,
div
);
Expand Down
Loading

0 comments on commit 2ad0e8f

Please sign in to comment.