Skip to content

Commit 5f6b467

Browse files
MariaAgatlabaj
authored andcommitted
feat(component): Add Date Picker component (#1873)
1 parent fccc378 commit 5f6b467

37 files changed

+2772
-2
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const YEAR = 'YEAR';
2+
export const MONTH = 'MONTH';
3+
export const DAY = 'DAY';
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import classNames from 'classnames';
4+
import { addMonths } from './helpers';
5+
import MonthView from './MonthView';
6+
import YearView from './YearView';
7+
import DecadeView from './DecadeView';
8+
9+
class DateInput extends React.Component {
10+
state = {
11+
selectedDate: new Date(this.props.date),
12+
date: new Date(this.props.date),
13+
typeOfDateInput: this.props.typeOfDateInput
14+
};
15+
static getDerivedStateFromProps(nextProps, prevState) {
16+
const nextDate = new Date(nextProps.date);
17+
const prevDate = new Date(prevState.selectedDate);
18+
const nextType = new Date(nextProps.date);
19+
return Date.parse(nextDate) === Date.parse(prevDate)
20+
? null
21+
: {
22+
selectedDate: nextDate,
23+
date: nextDate,
24+
typeOfDateInput: nextType
25+
};
26+
}
27+
getPrevMonth = () => {
28+
const { date } = this.state;
29+
this.setState({ date: addMonths(date, -1) });
30+
};
31+
getNextMonth = () => {
32+
const { date } = this.state;
33+
this.setState({ date: addMonths(date, 1) });
34+
};
35+
setSelected = day => {
36+
this.setState({
37+
selectedDate: day,
38+
date: day
39+
});
40+
this.props.setSelected(day);
41+
};
42+
toggleDateView = (type = null) => {
43+
this.setState({
44+
typeOfDateInput: type
45+
});
46+
};
47+
getDateViewByType = type => {
48+
const { date, weekStartsOn, locale, setSelected } = this.props;
49+
const parsedDate = Date.parse(date) ? date : new Date();
50+
switch (type) {
51+
case 'D':
52+
return <DecadeView date={parsedDate} setSelected={setSelected} toggleDateView={this.toggleDateView} />;
53+
case 'Y':
54+
return (
55+
<YearView date={parsedDate} setSelected={setSelected} locale={locale} toggleDateView={this.toggleDateView} />
56+
);
57+
default:
58+
return (
59+
<MonthView
60+
date={parsedDate}
61+
setSelected={setSelected}
62+
locale={locale}
63+
weekStartsOn={weekStartsOn}
64+
toggleDateView={this.toggleDateView}
65+
/>
66+
);
67+
}
68+
};
69+
render() {
70+
const { className } = this.props;
71+
const { typeOfDateInput } = this.state;
72+
return <div className={classNames('datepicker', className)}>{this.getDateViewByType(typeOfDateInput)}</div>;
73+
}
74+
}
75+
76+
DateInput.propTypes = {
77+
date: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.string]),
78+
setSelected: PropTypes.func,
79+
locale: PropTypes.string,
80+
weekStartsOn: PropTypes.number,
81+
className: PropTypes.string,
82+
typeOfDateInput: PropTypes.string
83+
};
84+
85+
DateInput.defaultProps = {
86+
setSelected: null,
87+
date: new Date(),
88+
locale: 'en-US',
89+
weekStartsOn: 1,
90+
className: '',
91+
typeOfDateInput: 'M'
92+
};
93+
export default DateInput;
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import React from 'react';
2+
import { mount, shallow } from 'enzyme';
3+
import DateInput from './DateInput';
4+
import { isEqualDate } from './helpers';
5+
6+
test('DateInput is working properly', () => {
7+
const component = shallow(<DateInput date="1/21/2019, 2:22:31 PM" />);
8+
9+
expect(component.render()).toMatchSnapshot();
10+
});
11+
12+
test('DateInput changes selected on click', () => {
13+
const setSelected = jest.fn();
14+
const component = mount(<DateInput date="1/21/2019, 2:22:31 PM" setSelected={setSelected} />);
15+
component
16+
.find('.weekend')
17+
.first()
18+
.simulate('click');
19+
expect(setSelected).toBeCalledWith(new Date('2019-01-04 14:22:31'));
20+
});
21+
22+
test('DateInput toggles view to years', () => {
23+
const component = mount(<DateInput date="1/21/2019, 2:22:31 PM" />);
24+
component
25+
.find('.picker-switch')
26+
.first()
27+
.simulate('click');
28+
expect(component.render()).toMatchSnapshot();
29+
});
30+
test('DateInput toggles view to decades', () => {
31+
const component = mount(<DateInput date="1/21/2019, 2:22:31 PM" />);
32+
component
33+
.find('.picker-switch')
34+
.first()
35+
.simulate('click');
36+
component
37+
.find('.picker-switch')
38+
.first()
39+
.simulate('click');
40+
expect(component.render()).toMatchSnapshot();
41+
});
42+
43+
test('DateInput is working properly with wrong date format', () => {
44+
const component = shallow(<DateInput date="ABC" />);
45+
expect(isEqualDate(component.get(0).props.children.props.date, new Date())).toBeTruthy();
46+
});
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import classNames from 'classnames';
4+
5+
const Day = ({ day, setSelected, classNamesArray }) => {
6+
const date = day.getDate();
7+
return (
8+
<td
9+
className={classNames('day', classNamesArray)}
10+
data-day={date}
11+
onClick={() => {
12+
setSelected(day);
13+
}}
14+
>
15+
{date}
16+
</td>
17+
);
18+
};
19+
20+
Day.propTypes = {
21+
day: PropTypes.instanceOf(Date).isRequired,
22+
classNamesArray: PropTypes.object,
23+
setSelected: PropTypes.func
24+
};
25+
26+
Day.defaultProps = {
27+
setSelected: null,
28+
classNamesArray: []
29+
};
30+
export default Day;
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import React from 'react';
2+
import { shallow } from 'enzyme';
3+
import Day from './Day';
4+
5+
test('Day is working properly', () => {
6+
const day = new Date('2019-01-04 14:22:31');
7+
const currDate = new Date('2019-01-02 12:22:31');
8+
const selectedDate = new Date('2019-01-05 18:22:31');
9+
const component = shallow(
10+
<table>
11+
<tbody>
12+
<tr>
13+
<Day day={day} currDate={currDate} selectedDate={selectedDate} classNamesArray={{ weekend: true }} />
14+
</tr>
15+
</tbody>
16+
</table>
17+
);
18+
19+
expect(component.render()).toMatchSnapshot();
20+
});
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import times from 'lodash/times';
4+
import { addYears } from './helpers';
5+
import { noop } from '../../../common/helpers';
6+
import { DecadeViewHeader } from './DecadeViewHeader';
7+
import { DecadeViewTable } from './DecadeViewTable';
8+
9+
class DecadeView extends React.Component {
10+
state = {
11+
date: new Date(this.props.date),
12+
selectedDate: new Date(this.props.date)
13+
};
14+
getYearArray = () => {
15+
const { date } = this.state;
16+
date.setFullYear(Math.floor(date.getFullYear() / 10) * 10);
17+
return times(12, i => addYears(date, i).getFullYear());
18+
};
19+
getPrevDecade = () => {
20+
const { date } = this.state;
21+
this.setState({ date: addYears(date, -10) });
22+
};
23+
getNextDecade = () => {
24+
const { date } = this.state;
25+
this.setState({ date: addYears(date, 10) });
26+
};
27+
setSelectedYear = year => {
28+
const { setSelected, toggleDateView } = this.props;
29+
const { date } = this.state;
30+
date.setFullYear(year);
31+
setSelected(date);
32+
toggleDateView('Y');
33+
};
34+
35+
render() {
36+
const { date, selectedDate } = this.state;
37+
const currDecade = Math.floor(date.getFullYear() / 10) * 10;
38+
const selectedYear = selectedDate.getFullYear();
39+
const yearArray = this.getYearArray();
40+
return (
41+
<div className="datepicker-years">
42+
<table className="table-condensed">
43+
<DecadeViewHeader
44+
currDecade={currDecade}
45+
getNextDecade={this.getNextDecade}
46+
getPrevDecade={this.getPrevDecade}
47+
/>
48+
<DecadeViewTable selectedYear={selectedYear} yearArray={yearArray} setSelectedYear={this.setSelectedYear} />
49+
</table>
50+
</div>
51+
);
52+
}
53+
}
54+
55+
DecadeView.propTypes = {
56+
date: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.string]),
57+
setSelected: PropTypes.func,
58+
toggleDateView: PropTypes.func
59+
};
60+
61+
DecadeView.defaultProps = {
62+
setSelected: noop,
63+
toggleDateView: noop,
64+
date: new Date()
65+
};
66+
export default DecadeView;
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import React from 'react';
2+
import { mount, shallow } from 'enzyme';
3+
import DecadeView from './DecadeView';
4+
5+
test('DecadeView is working properly', () => {
6+
const component = shallow(<DecadeView />);
7+
8+
expect(component.render()).toMatchSnapshot();
9+
});
10+
11+
test('Edit year DecadeView', () => {
12+
const date = new Date('2/21/2019, 2:22:31 PM');
13+
const setSelected = jest.fn();
14+
const component = mount(<DecadeView date={date} setSelected={setSelected} />);
15+
expect(component.render()).toMatchSnapshot();
16+
component
17+
.find('.year')
18+
.first()
19+
.simulate('click');
20+
expect(setSelected).toBeCalledWith(new Date('2/21/2010, 2:22:31 PM'));
21+
});
22+
23+
test('Edit decade DecadeView', () => {
24+
const date = new Date('2/21/2019, 2:22:31 PM');
25+
const setSelected = jest.fn();
26+
const component = mount(<DecadeView date={date} setSelected={setSelected} />);
27+
expect(component.render()).toMatchSnapshot();
28+
component
29+
.find('.next')
30+
.first()
31+
.simulate('click');
32+
component
33+
.find('.year')
34+
.first()
35+
.simulate('click');
36+
expect(setSelected).toBeCalledWith(new Date('2/21/2020, 2:22:31 PM'));
37+
component
38+
.find('.prev')
39+
.first()
40+
.simulate('click');
41+
component
42+
.find('.year')
43+
.first()
44+
.simulate('click');
45+
expect(setSelected).toBeCalledWith(new Date('2/21/2010, 2:22:31 PM'));
46+
});
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import { noop } from '../../../common/helpers';
4+
5+
export const DecadeViewHeader = ({ currDecade, getPrevDecade, getNextDecade }) => (
6+
<thead>
7+
<tr>
8+
<th className="prev" onClick={getPrevDecade}>
9+
<span className="glyphicon glyphicon-chevron-left" />
10+
</th>
11+
<th className="picker-switch" data-action="pickerSwitch" colSpan="5">
12+
{`${currDecade}-${currDecade + 11}`}
13+
</th>
14+
<th className="next" onClick={getNextDecade}>
15+
<span className="glyphicon glyphicon-chevron-right" />
16+
</th>
17+
</tr>
18+
</thead>
19+
);
20+
21+
DecadeViewHeader.propTypes = {
22+
currDecade: PropTypes.number,
23+
getPrevDecade: PropTypes.func,
24+
getNextDecade: PropTypes.func
25+
};
26+
DecadeViewHeader.defaultProps = {
27+
currDecade: 20,
28+
getPrevDecade: noop,
29+
getNextDecade: noop
30+
};
31+
export default DecadeViewHeader;
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import { noop } from '../../../common/helpers';
4+
5+
export const DecadeViewTable = ({ yearArray, selectedYear, setSelectedYear }) => (
6+
<tbody>
7+
<tr>
8+
<td colSpan="7">
9+
{yearArray.map(year => (
10+
<span
11+
onClick={() => setSelectedYear(year)}
12+
className={`year ${year === selectedYear ? 'active' : ''}`}
13+
key={year}
14+
>
15+
{year}
16+
</span>
17+
))}
18+
</td>
19+
</tr>
20+
</tbody>
21+
);
22+
23+
DecadeViewTable.propTypes = {
24+
yearArray: PropTypes.array,
25+
selectedYear: PropTypes.number,
26+
setSelectedYear: PropTypes.func
27+
};
28+
DecadeViewTable.defaultProps = {
29+
yearArray: [],
30+
selectedYear: new Date().getFullYear(),
31+
setSelectedYear: noop
32+
};
33+
34+
export default DecadeViewTable;

0 commit comments

Comments
 (0)