Skip to content

Commit 00996e9

Browse files
MariaAgajeff-phillips-18
authored andcommitted
feat(component): Add Time Picker component (#1875)
1 parent 0dc9236 commit 00996e9

21 files changed

+1664
-0
lines changed
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
.bootstrap-datetimepicker-widget.date-time-picker-pf.top {
2+
margin-top: -10px;
3+
}
4+
5+
.bootstrap-datetimepicker-widget.date-time-picker-pf.bottom {
6+
margin-top: 10px ;
7+
}
8+
9+
.bootstrap-datetimepicker-widget.date-picker-pf,
10+
.bootstrap-datetimepicker-widget.date-time-picker-pf {
11+
max-width: none;
12+
13+
.popover-content {
14+
color: $color-pf-black-900;
15+
padding: 0;
16+
}
17+
18+
.clock-btn {
19+
border: 0;
20+
box-shadow: none;
21+
color: #363636;
22+
display: block;
23+
padding-bottom: 4px;
24+
padding-top: 4px;
25+
}
26+
27+
table td {
28+
.today-button {
29+
padding: 0 16px 0 0;
30+
border: 0;
31+
background-color: transparent;
32+
33+
span {
34+
height: 24px;
35+
line-height: 24px;
36+
color: @color-pf-blue-400;
37+
}
38+
}
39+
}
40+
41+
&.bootstrap-timepicker-widget {
42+
width: 16em;
43+
44+
.popover-content {
45+
padding-left: 0;
46+
}
47+
}
48+
49+
&.date-picker-pf {
50+
.popover-content {
51+
padding: 10px;
52+
}
53+
}
54+
55+
.datepicker tr td span {
56+
height: 53px;
57+
}
58+
59+
.timepicker {
60+
table td {
61+
height: initial;
62+
line-height: initial;
63+
}
64+
}
65+
66+
.timepicker-hours .table-condensed > tbody > tr > td,
67+
.timepicker-minutes .table-condensed > tbody > tr > td {
68+
padding: 7px 0;
69+
}
70+
}

packages/patternfly-3/patternfly-react/less/patternfly-react.less

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,4 @@
1818
@import 'expand-collapse';
1919
@import 'login-page';
2020
@import 'dual-list-selector';
21+
@import 'date-time-picker';
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
.bootstrap-datetimepicker-widget.date-time-picker-pf.top {
2+
margin-top: -10px;
3+
}
4+
5+
.bootstrap-datetimepicker-widget.date-time-picker-pf.bottom {
6+
margin-top: 10px;
7+
}
8+
9+
.bootstrap-datetimepicker-widget.date-picker-pf,
10+
.bootstrap-datetimepicker-widget.date-time-picker-pf {
11+
max-width: none;
12+
13+
.popover-content {
14+
color: $color-pf-black-900;
15+
padding: 0;
16+
}
17+
18+
.clock-btn {
19+
border: 0;
20+
box-shadow: none;
21+
color: #363636;
22+
display: block;
23+
padding-bottom: 4px;
24+
padding-top: 4px;
25+
}
26+
27+
table td {
28+
.today-button {
29+
padding: 0 16px 0 0;
30+
border: 0;
31+
background-color: transparent;
32+
33+
span {
34+
height: 24px;
35+
line-height: 24px;
36+
color: $color-pf-blue-400;
37+
}
38+
}
39+
}
40+
41+
&.bootstrap-timepicker-widget {
42+
width: 16em;
43+
44+
.popover-content {
45+
padding-left: 0;
46+
}
47+
}
48+
49+
&.date-picker-pf {
50+
.popover-content {
51+
padding: 10px;
52+
}
53+
}
54+
55+
.datepicker tr td span {
56+
height: 53px;
57+
}
58+
59+
.timepicker {
60+
table td {
61+
height: initial;
62+
line-height: initial;
63+
}
64+
}
65+
66+
.timepicker-hours .table-condensed > tbody > tr > td,
67+
.timepicker-minutes .table-condensed > tbody > tr > td {
68+
padding: 7px 0;
69+
}
70+
}

packages/patternfly-3/patternfly-react/sass/patternfly-react/_patternfly-react.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,4 @@
1818
@import 'login-page';
1919
@import 'dual-list-selector';
2020
@import 'breadcrumbswitcher-popover';
21+
@import 'date-time-picker';
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import React from 'react';
2+
import { inlineTemplate } from 'storybook/decorators/storyTemplates';
3+
import { storiesOf } from '@storybook/react';
4+
import { storybookPackageName, DOCUMENTATION_URL, STORYBOOK_CATEGORY } from 'storybook/constants/siteConstants';
5+
import { select, withKnobs } from '@storybook/addon-knobs';
6+
import { name } from '../../../package.json';
7+
import { TimePicker } from './index';
8+
9+
const stories = storiesOf(
10+
`${storybookPackageName(name)}/${STORYBOOK_CATEGORY.FORMS_AND_CONTROLS}/Date Time Picker`,
11+
module
12+
);
13+
stories.addDecorator(withKnobs);
14+
stories.addWithInfo('TimePicker', '', () => {
15+
const story = (
16+
<div style={{ paddingTop: 60, paddingBottom: 60 }}>
17+
<label>Time picker</label>
18+
<div className="row">
19+
<div className="col-md-3">
20+
<TimePicker placement={select('Placement Time', ['top', 'bottom'], 'top')} />
21+
</div>
22+
</div>
23+
</div>
24+
);
25+
return inlineTemplate({
26+
title: 'TimePicker',
27+
documentationLink: `${DOCUMENTATION_URL.FORMS_AND_CONTROLS}#date-time-picker`,
28+
reactBootstrapDocumentationLink: `${DOCUMENTATION_URL.REACT_BOOTSTRAP_COMPONENT}date-time-picker/`,
29+
description: '',
30+
story
31+
});
32+
});
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import { noop } from '../../../common/helpers';
4+
import { AM, PM, HOUR, MINUTE } from './TimeConstants';
5+
6+
class PickTimeClock extends React.Component {
7+
state = {
8+
ampm: this.props.time.getHours() >= 12 ? PM : AM
9+
};
10+
static getDerivedStateFromProps(nextProps, prevState) {
11+
return {
12+
ampm: nextProps.time.getHours() >= 12 ? PM : AM
13+
};
14+
}
15+
setTime = (type, amount) => {
16+
const { time, setSelected } = this.props;
17+
if (type === HOUR) {
18+
time.setHours(time.getHours() + amount);
19+
} else if (type === MINUTE) {
20+
time.setMinutes(time.getMinutes() + amount);
21+
}
22+
setSelected(time);
23+
};
24+
toggleAMPM = () => {
25+
const { time, setSelected } = this.props;
26+
if (this.state.ampm === AM) {
27+
time.setHours(time.getHours() + 12);
28+
this.setState({ ampm: PM });
29+
} else {
30+
time.setHours(time.getHours() - 12);
31+
this.setState({ ampm: AM });
32+
}
33+
setSelected(time);
34+
};
35+
render() {
36+
const { time, toggleTimeTable } = this.props;
37+
const minutes = time.getMinutes();
38+
const hours = time.getHours() % 12 || 12;
39+
40+
return (
41+
<div className="timepicker-picker">
42+
<table>
43+
<tbody>
44+
<tr>
45+
<td onClick={() => this.setTime(HOUR, 1)}>
46+
<a title="Increment Hour" className="btn clock-btn">
47+
<span className="glyphicon glyphicon-chevron-up" />
48+
</a>
49+
</td>
50+
<td className="separator" />
51+
<td onClick={() => this.setTime(MINUTE, 1)}>
52+
<a title="Increment Minute" className="btn clock-btn">
53+
<span className="glyphicon glyphicon-chevron-up" />
54+
</a>
55+
</td>
56+
<td className="separator" />
57+
</tr>
58+
<tr>
59+
<td onClick={() => toggleTimeTable(HOUR)}>
60+
<span className="timepicker-hour" title="Pick Hour">
61+
{`${hours}`.padStart(2, '0')}
62+
</span>
63+
</td>
64+
<td className="separator">:</td>
65+
<td onClick={() => toggleTimeTable(MINUTE)}>
66+
<span className="timepicker-minute" title="Pick Minute">
67+
{`${minutes}`.padStart(2, '0')}
68+
</span>
69+
</td>
70+
<td>
71+
<button className="btn btn-primary ampm-toggle" onClick={() => this.toggleAMPM()}>
72+
{this.state.ampm}
73+
</button>
74+
</td>
75+
</tr>
76+
<tr>
77+
<td>
78+
<a title="Decrement Hour" className="btn clock-btn" onClick={() => this.setTime(HOUR, -1)}>
79+
<span className="glyphicon glyphicon-chevron-down" />
80+
</a>
81+
</td>
82+
<td className="separator" />
83+
<td>
84+
<a title="Decrement Minute" className="btn clock-btn" onClick={() => this.setTime(MINUTE, -1)}>
85+
<span className="glyphicon glyphicon-chevron-down" />
86+
</a>
87+
</td>
88+
<td className="separator" />
89+
</tr>
90+
</tbody>
91+
</table>
92+
</div>
93+
);
94+
}
95+
}
96+
97+
PickTimeClock.propTypes = {
98+
time: PropTypes.instanceOf(Date).isRequired,
99+
setSelected: PropTypes.func,
100+
toggleTimeTable: PropTypes.func
101+
};
102+
PickTimeClock.defaultProps = {
103+
setSelected: noop,
104+
toggleTimeTable: noop
105+
};
106+
export default PickTimeClock;
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import React from 'react';
2+
import { mount, shallow } from 'enzyme';
3+
import PickTimeClock from './PickTimeClock';
4+
import { AM, PM, HOUR, MINUTE } from './TimeConstants';
5+
6+
test('PickTimeClock is working properly', () => {
7+
const time = new Date('2/21/2019, 2:22:31 PM');
8+
const component = shallow(<PickTimeClock time={time} />);
9+
10+
expect(component.render()).toMatchSnapshot();
11+
});
12+
13+
test('Edit minutes of PickTimeClock', () => {
14+
const time = new Date('2/21/2019, 2:22:31 PM');
15+
const setSelected = jest.fn();
16+
const component = mount(<PickTimeClock time={time} setSelected={setSelected} />);
17+
expect(component.render()).toMatchSnapshot();
18+
component.find('[title~="Minute"][title~="Increment"]').simulate('click');
19+
expect(setSelected).toBeCalledWith(new Date('2/21/2019, 2:23:31 PM'));
20+
component.find('[title~="Minute"][title~="Decrement"]').simulate('click');
21+
component.find('[title~="Minute"][title~="Decrement"]').simulate('click');
22+
expect(setSelected).toBeCalledWith(new Date('2/21/2019, 2:21:31 PM'));
23+
});
24+
25+
test('Edit hours of PickTimeClock', () => {
26+
const time = new Date('2/21/2019, 2:22:31 PM');
27+
const setSelected = jest.fn();
28+
const component = mount(<PickTimeClock time={time} setSelected={setSelected} />);
29+
expect(component.render()).toMatchSnapshot();
30+
component.find('[title~="Hour"][title~="Increment"]').simulate('click');
31+
expect(setSelected).toBeCalledWith(new Date('2/21/2019, 3:22:31 PM'));
32+
component.find('[title~="Hour"][title~="Decrement"]').simulate('click');
33+
component.find('[title~="Hour"][title~="Decrement"]').simulate('click');
34+
expect(setSelected).toBeCalledWith(new Date('2/21/2019, 1:22:31 PM'));
35+
});
36+
37+
test('Toggle hours of PickTimeClock', () => {
38+
const time = new Date('2/21/2019, 12:22:31 PM');
39+
const component = mount(<PickTimeClock time={time} />);
40+
expect(component.state().ampm).toEqual(PM);
41+
component.find('.ampm-toggle').simulate('click');
42+
expect(component.state().ampm).toEqual(AM);
43+
expect(component.render()).toMatchSnapshot();
44+
component.find('.ampm-toggle').simulate('click');
45+
expect(component.state().ampm).toEqual(PM);
46+
});
47+
48+
test('Toggle TimeTable hour from PickTimeClock', () => {
49+
const time = new Date('2/21/2019, 12:22:31 PM');
50+
const toggleTimeTable = jest.fn();
51+
const component = mount(<PickTimeClock time={time} toggleTimeTable={toggleTimeTable} />);
52+
53+
component.find('.timepicker-hour').simulate('click');
54+
expect(toggleTimeTable).toBeCalledWith(HOUR);
55+
});
56+
57+
test('Toggle TimeTable minute from PickTimeClock', () => {
58+
const time = new Date('2/21/2019, 12:22:31 PM');
59+
const toggleTimeTable = jest.fn();
60+
const component = mount(<PickTimeClock time={time} toggleTimeTable={toggleTimeTable} />);
61+
62+
component.find('.timepicker-minute').simulate('click');
63+
expect(toggleTimeTable).toBeCalledWith(MINUTE);
64+
});

0 commit comments

Comments
 (0)