Skip to content
This repository was archived by the owner on Mar 8, 2023. It is now read-only.

Commit b7cc102

Browse files
authored
Merge branch 'master' into fix/number-input-0-value
2 parents 255884a + 3f33e9f commit b7cc102

File tree

2 files changed

+59
-28
lines changed

2 files changed

+59
-28
lines changed

docs/stories/components/TimePicker.stories.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ function TimePickerStory() {
2727
}}
2828
seconds={false}
2929
value={val}
30+
step={15}
3031
/>
3132
</div>
3233
<Pre>

packages/buffetjs-core/src/components/TimePicker/index.js

Lines changed: 58 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*
55
*/
66

7-
import React, { useEffect, useState, useRef } from 'react';
7+
import React, { useEffect, useState, useRef, useMemo } from 'react';
88
import { isInteger, toNumber } from 'lodash';
99
import PropTypes from 'prop-types';
1010

@@ -18,6 +18,11 @@ import Icon from '../Icon';
1818
import useEventListener from '../../hooks/useEventListener';
1919
import useShortcutEffect from '../../hooks/useShortcutEffect';
2020

21+
const MINUTES_IN_HOUR = 60;
22+
23+
// Returns string with two digits padded at start with 0
24+
const pad = num => `0${num}`.substr(-2);
25+
2126
// Convert time array to formatted time string
2227
export const timeFormatter = time => {
2328
const newTime = Array(3)
@@ -65,27 +70,43 @@ const short = hour => {
6570
return hour;
6671
};
6772

73+
// return array of minutes in hours with current step
74+
const getMinutesArr = step => {
75+
const length = MINUTES_IN_HOUR / step;
76+
77+
return Array.from({ length }, (_v, i) => step * i);
78+
};
79+
6880
// Generate options for TimeList display
69-
const getOptions = () => {
70-
const hours = Array.from({ length: 24 }, (_, k) => k);
81+
const getOptions = step => {
82+
const hours = Array.from({ length: 24 }, (_, i) => i);
83+
const minutes = getMinutesArr(step);
84+
7185
const options = hours.reduce((acc, cur) => {
72-
const hour = cur < 10 ? `0${cur}` : cur;
86+
const hour = pad(cur);
87+
88+
const hourOptions = minutes.map(minute => {
89+
const label = `${hour}:${pad(minute)}`;
7390

74-
return acc.concat([
75-
{ value: `${hour}:00:00`, label: `${hour}:00` },
76-
{ value: `${hour}:30:00`, label: `${hour}:30` },
77-
]);
91+
return { value: `${label}:00`, label };
92+
});
93+
94+
return acc.concat(hourOptions);
7895
}, []);
7996

8097
return options;
8198
};
8299

83100
// Find the nearest time option to select a TimeList value
84-
const roundHour = time => {
101+
const roundHour = (time, step) => {
85102
const arr = splitArray(time);
86-
const nearMin = nearest([0, 30, 60], parseInt(arr[1], 10));
103+
const minutesArr = getMinutesArr(step);
104+
const nearMin = nearest(
105+
minutesArr.concat(MINUTES_IN_HOUR),
106+
parseInt(arr[1], 10)
107+
);
87108

88-
arr[1] = nearMin !== 30 ? '00' : '30';
109+
arr[1] = minutesArr.includes(arr[1]) ? '00' : pad(nearMin);
89110
arr[2] = nearMin === 60 ? `${parseInt(arr[2], 10) + 1}` : arr[2];
90111

91112
return format(arr.reverse()).join(':');
@@ -99,18 +120,23 @@ const nearest = (arr, val) =>
99120
) + val;
100121

101122
function TimePicker(props) {
102-
const { name, onChange, seconds, tabIndex, value } = props;
123+
const { name, onChange, seconds, tabIndex, value, step } = props;
103124
const [inputVal, setInputVal] = useState(seconds ? value : short(value));
104125
const [isOpen, setIsOpen] = useState(false);
126+
const options = useMemo(() => getOptions(step), [step]);
105127
const inputRef = useRef();
106128
const wrapperRef = useRef();
107129
const listRef = useRef();
108-
const listRefs = getOptions().reduce((acc, curr) => {
130+
const listRefs = options.reduce((acc, curr) => {
109131
acc[curr.value] = useRef();
110132

111133
return acc;
112134
}, {});
113-
const currentTimeSelected = roundHour(timeFormatter(inputVal));
135+
136+
const currentTimeSelected = useMemo(
137+
() => roundHour(timeFormatter(inputVal), step),
138+
[inputVal, step]
139+
);
114140

115141
// Effect to enable scrolling
116142
useEffect(() => {
@@ -131,14 +157,13 @@ function TimePicker(props) {
131157
// Custom hook to select a time using the keyboard's up arrow
132158
useShortcutEffect('arrowUp', () => {
133159
if (isOpen) {
134-
const currentTimeIndex = getOptions().findIndex(
160+
const currentIndex = options.findIndex(
135161
o => o.value === currentTimeSelected
136162
);
137-
const optionsLength = getOptions().length;
138-
const nextTime =
139-
currentTimeIndex === optionsLength - 1
140-
? getOptions()[optionsLength - 1]
141-
: getOptions()[currentTimeIndex + 1];
163+
if (!currentIndex) return;
164+
const nextIndex = currentIndex - 1;
165+
166+
const nextTime = options[nextIndex] || options[currentIndex];
142167

143168
updateTime(nextTime.value);
144169
}
@@ -147,13 +172,14 @@ function TimePicker(props) {
147172
// Custom hook to select a time using the keyboard's down arrow
148173
useShortcutEffect('arrowDown', () => {
149174
if (isOpen) {
150-
const currentTimeIndex = getOptions().findIndex(
175+
const currentIndex = options.findIndex(
151176
o => o.value === currentTimeSelected
152177
);
153-
const nextTime =
154-
currentTimeIndex === 0
155-
? getOptions()[0]
156-
: getOptions()[currentTimeIndex - 1];
178+
const lastIndex = options.length - 1;
179+
if (currentIndex >= lastIndex) return;
180+
const nextIndex = currentIndex + 1;
181+
182+
const nextTime = options[nextIndex] || options[lastIndex];
157183

158184
updateTime(nextTime.value);
159185
}
@@ -218,16 +244,16 @@ function TimePicker(props) {
218244
<Icon icon="time" />
219245
</IconWrapper>
220246
<TimeList className={isOpen && 'displayed'} ref={listRef}>
221-
{getOptions().map(option => (
247+
{options.map(option => (
222248
<li key={option.value} ref={listRefs[option.value]}>
223249
<input
224250
type="radio"
225251
onChange={handleClick}
226252
value={option.value}
227253
id={option.value}
228254
name="time"
229-
checked={option.value === roundHour(timeFormatter(inputVal))}
230-
tabIndex="-1"
255+
checked={option.value === currentTimeSelected}
256+
tabIndex="0"
231257
/>
232258
<label htmlFor={option.value}>{option.label}</label>
233259
</li>
@@ -243,13 +269,17 @@ TimePicker.defaultProps = {
243269
tabIndex: '0',
244270
seconds: false,
245271
value: '',
272+
step: 30,
246273
};
247274

248275
TimePicker.propTypes = {
249276
className: PropTypes.string,
250277
name: PropTypes.string.isRequired,
251278
onChange: PropTypes.func,
252279
seconds: PropTypes.bool,
280+
step: (props, propName) =>
281+
MINUTES_IN_HOUR % props[propName] > 0 &&
282+
new Error('step should be divisible by 60'),
253283
tabIndex: PropTypes.string,
254284
value: PropTypes.string,
255285
};

0 commit comments

Comments
 (0)