Skip to content

Commit

Permalink
feat(component): Add Date Picker component
Browse files Browse the repository at this point in the history
  • Loading branch information
MariaAga committed Jul 3, 2019
1 parent 8ef25aa commit f58ee52
Show file tree
Hide file tree
Showing 38 changed files with 3,025 additions and 2 deletions.
@@ -0,0 +1,3 @@
export const YEAR = 'YEAR';
export const MONTH = 'MONTH';
export const DAY = 'DAY';
@@ -0,0 +1,93 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { addMonths } from './helpers';
import MonthView from './MonthView';
import YearView from './YearView';
import DecadeView from './DecadeView';

class DateInput extends React.Component {
state = {
selectedDate: new Date(this.props.date),
date: new Date(this.props.date),
typeOfDateInput: this.props.typeOfDateInput
};
static getDerivedStateFromProps(nextProps, prevState) {
const nextDate = new Date(nextProps.date);
const prevDate = new Date(prevState.selectedDate);
const nextType = new Date(nextProps.date);
return Date.parse(nextDate) === Date.parse(prevDate)
? null
: {
selectedDate: nextDate,
date: nextDate,
typeOfDateInput: nextType
};
}
getPrevMonth = () => {
const { date } = this.state;
this.setState({ date: addMonths(date, -1) });
};
getNextMonth = () => {
const { date } = this.state;
this.setState({ date: addMonths(date, 1) });
};
setSelected = day => {
this.setState({
selectedDate: day,
date: day
});
this.props.setSelected(day);
};
toggleDateView = (type = null) => {
this.setState({
typeOfDateInput: type
});
};
getDateViewByType = type => {
const { date, locale, weekStartsOn, setSelected } = this.props;
const parsedDate = Date.parse(date) ? date : new Date();
switch (type) {
case 'D':
return <DecadeView date={parsedDate} setSelected={setSelected} toggleDateView={this.toggleDateView} />;
case 'Y':
return (
<YearView date={parsedDate} setSelected={setSelected} locale={locale} toggleDateView={this.toggleDateView} />
);
default:
return (
<MonthView
date={parsedDate}
setSelected={setSelected}
locale={locale}
weekStartsOn={weekStartsOn}
toggleDateView={this.toggleDateView}
/>
);
}
};
render() {
const { className } = this.props;
const { typeOfDateInput } = this.state;
return <div className={classNames('datepicker', className)}>{this.getDateViewByType(typeOfDateInput)}</div>;
}
}

DateInput.propTypes = {
date: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.string]),
setSelected: PropTypes.func,
locale: PropTypes.string,
weekStartsOn: PropTypes.number,
className: PropTypes.string,
typeOfDateInput: PropTypes.string
};

DateInput.defaultProps = {
setSelected: null,
date: new Date(),
locale: 'en-US',
weekStartsOn: 1,
className: '',
typeOfDateInput: 'M'
};
export default DateInput;
@@ -0,0 +1,46 @@
import React from 'react';
import { mount, shallow } from 'enzyme';
import DateInput from './DateInput';
import { isEqualDate } from './helpers';

test('DateInput is working properly', () => {
const component = shallow(<DateInput date="1/21/2019, 2:22:31 PM" />);

expect(component.render()).toMatchSnapshot();
});

test('DateInput changes selected on click', () => {
const setSelected = jest.fn();
const component = mount(<DateInput date="1/21/2019, 2:22:31 PM" setSelected={setSelected} />);
component
.find('.weekend')
.first()
.simulate('click');
expect(setSelected).toBeCalledWith(new Date('2019-01-04 14:22:31'));
});

test('DateInput toggles view to years', () => {
const component = mount(<DateInput date="1/21/2019, 2:22:31 PM" />);
component
.find('.picker-switch')
.first()
.simulate('click');
expect(component.render()).toMatchSnapshot();
});
test('DateInput toggles view to decades', () => {
const component = mount(<DateInput date="1/21/2019, 2:22:31 PM" />);
component
.find('.picker-switch')
.first()
.simulate('click');
component
.find('.picker-switch')
.first()
.simulate('click');
expect(component.render()).toMatchSnapshot();
});

test('DateInput is working properly with wrong date format', () => {
const component = shallow(<DateInput date="ABC" />);
expect(isEqualDate(component.get(0).props.children.props.date, new Date())).toBeTruthy();
});
@@ -0,0 +1,30 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';

const Day = ({ day, setSelected, classNamesArray }) => {
const date = day.getDate();
return (
<td
className={classNames('day', classNamesArray)}
data-day={date}
onClick={() => {
setSelected(day);
}}
>
{date}
</td>
);
};

Day.propTypes = {
day: PropTypes.instanceOf(Date).isRequired,
classNamesArray: PropTypes.object,
setSelected: PropTypes.func
};

Day.defaultProps = {
setSelected: null,
classNamesArray: []
};
export default Day;
@@ -0,0 +1,20 @@
import React from 'react';
import { shallow } from 'enzyme';
import Day from './Day';

test('Day is working properly', () => {
const day = new Date('2019-01-04 14:22:31');
const currDate = new Date('2019-01-02 12:22:31');
const selectedDate = new Date('2019-01-05 18:22:31');
const component = shallow(
<table>
<tbody>
<tr>
<Day day={day} currDate={currDate} selectedDate={selectedDate} classNamesArray={{ weekend: true }} />
</tr>
</tbody>
</table>
);

expect(component.render()).toMatchSnapshot();
});
@@ -0,0 +1,66 @@
import React from 'react';
import PropTypes from 'prop-types';
import times from 'lodash/times';
import { addYears } from './helpers';
import { noop } from '../../../common/helpers';
import { DecadeViewHeader } from './DecadeViewHeader';
import { DecadeViewTable } from './DecadeViewTable';

class DecadeView extends React.Component {
state = {
date: new Date(this.props.date),
selectedDate: new Date(this.props.date)
};
getYearArray = () => {
const { date } = this.state;
date.setFullYear(Math.floor(date.getFullYear() / 10) * 10);
return times(12, i => addYears(date, i).getFullYear());
};
getPrevDecade = () => {
const { date } = this.state;
this.setState({ date: addYears(date, -10) });
};
getNextDecade = () => {
const { date } = this.state;
this.setState({ date: addYears(date, 10) });
};
setSelectedYear = year => {
const { setSelected, toggleDateView } = this.props;
const { date } = this.state;
date.setFullYear(year);
setSelected(date);
toggleDateView('Y');
};

render() {
const { date, selectedDate } = this.state;
const currDecade = Math.floor(date.getFullYear() / 10) * 10;
const selectedYear = selectedDate.getFullYear();
const yearArray = this.getYearArray();
return (
<div className="datepicker-years">
<table className="table-condensed">
<DecadeViewHeader
currDecade={currDecade}
getNextDecade={this.getNextDecade}
getPrevDecade={this.getPrevDecade}
/>
<DecadeViewTable selectedYear={selectedYear} yearArray={yearArray} setSelectedYear={this.setSelectedYear} />
</table>
</div>
);
}
}

DecadeView.propTypes = {
date: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.string]),
setSelected: PropTypes.func,
toggleDateView: PropTypes.func
};

DecadeView.defaultProps = {
setSelected: noop,
toggleDateView: noop,
date: new Date()
};
export default DecadeView;
@@ -0,0 +1,46 @@
import React from 'react';
import { mount, shallow } from 'enzyme';
import DecadeView from './DecadeView';

test('DecadeView is working properly', () => {
const component = shallow(<DecadeView />);

expect(component.render()).toMatchSnapshot();
});

test('Edit year DecadeView', () => {
const date = new Date('2/21/2019, 2:22:31 PM');
const setSelected = jest.fn();
const component = mount(<DecadeView date={date} setSelected={setSelected} />);
expect(component.render()).toMatchSnapshot();
component
.find('.year')
.first()
.simulate('click');
expect(setSelected).toBeCalledWith(new Date('2/21/2010, 2:22:31 PM'));
});

test('Edit decade DecadeView', () => {
const date = new Date('2/21/2019, 2:22:31 PM');
const setSelected = jest.fn();
const component = mount(<DecadeView date={date} setSelected={setSelected} />);
expect(component.render()).toMatchSnapshot();
component
.find('.next')
.first()
.simulate('click');
component
.find('.year')
.first()
.simulate('click');
expect(setSelected).toBeCalledWith(new Date('2/21/2020, 2:22:31 PM'));
component
.find('.prev')
.first()
.simulate('click');
component
.find('.year')
.first()
.simulate('click');
expect(setSelected).toBeCalledWith(new Date('2/21/2010, 2:22:31 PM'));
});
@@ -0,0 +1,31 @@
import React from 'react';
import PropTypes from 'prop-types';
import { noop } from '../../../common/helpers';

export const DecadeViewHeader = ({ currDecade, getPrevDecade, getNextDecade }) => (
<thead>
<tr>
<th className="prev" onClick={getPrevDecade}>
<span className="glyphicon glyphicon-chevron-left" />
</th>
<th className="picker-switch" data-action="pickerSwitch" colSpan="5">
{`${currDecade}-${currDecade + 11}`}
</th>
<th className="next" onClick={getNextDecade}>
<span className="glyphicon glyphicon-chevron-right" />
</th>
</tr>
</thead>
);

DecadeViewHeader.propTypes = {
currDecade: PropTypes.number,
getPrevDecade: PropTypes.func,
getNextDecade: PropTypes.func
};
DecadeViewHeader.defaultProps = {
currDecade: 20,
getPrevDecade: noop,
getNextDecade: noop
};
export default DecadeViewHeader;
@@ -0,0 +1,34 @@
import React from 'react';
import PropTypes from 'prop-types';
import { noop } from '../../../common/helpers';

export const DecadeViewTable = ({ yearArray, selectedYear, setSelectedYear }) => (
<tbody>
<tr>
<td colSpan="7">
{yearArray.map(year => (
<span
onClick={() => setSelectedYear(year)}
className={`year ${year === selectedYear ? 'active' : ''}`}
key={year}
>
{year}
</span>
))}
</td>
</tr>
</tbody>
);

DecadeViewTable.propTypes = {
yearArray: PropTypes.array,
selectedYear: PropTypes.number,
setSelectedYear: PropTypes.func
};
DecadeViewTable.defaultProps = {
yearArray: [],
selectedYear: new Date().getFullYear(),
setSelectedYear: noop
};

export default DecadeViewTable;

0 comments on commit f58ee52

Please sign in to comment.