@planningcenter/tapestry-react 2.6.0 → 2.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,6 +11,8 @@ import InputField from '../Input/InputField';
11
11
  import NumberField from '../NumberField';
12
12
  import { isIOS, noop, padNumber } from '../utils';
13
13
  import { HOURS_TO_NOON, HOURS_IN_DAY, addHoursToTime, addMinutesToTime, addTimeToDate, getMeridiem, getTimeFromDate } from './utils';
14
+ var MIN_MINUTE = 0;
15
+ var MAX_MINUTE = 59;
14
16
 
15
17
  var _ref = /*#__PURE__*/React.createElement(Box, {
16
18
  as: "span",
@@ -82,9 +84,31 @@ var TimeField = /*#__PURE__*/function (_Component) {
82
84
  });
83
85
 
84
86
  _defineProperty(_assertThisInitialized(_this), "handleHoursKeyDown", function (event) {
87
+ var hour = Number(event.target.value);
88
+ var isTwelveHourClock = _this.props.twelveHourClock;
89
+ var isHourEleven = HOURS_TO_NOON - 1;
90
+ var maxHour = isTwelveHourClock ? HOURS_TO_NOON : HOURS_IN_DAY - 1;
91
+ var minHour = isTwelveHourClock ? 1 : 0;
92
+
85
93
  if (event.key === 'ArrowRight') {
86
94
  _this.inputBox.focus(1);
87
95
  }
96
+
97
+ if (event.key === 'ArrowUp' && isTwelveHourClock && hour === isHourEleven) {
98
+ _this.toggleMeridiem();
99
+ }
100
+
101
+ if (event.key === 'ArrowUp' && hour === maxHour) {
102
+ _this.updateHours(1);
103
+ }
104
+
105
+ if (event.key === 'ArrowDown' && isTwelveHourClock && hour === minHour) {
106
+ _this.toggleMeridiem();
107
+ }
108
+
109
+ if (event.key === 'ArrowDown' && hour === minHour) {
110
+ _this.updateHours(-1);
111
+ }
88
112
  });
89
113
 
90
114
  _defineProperty(_assertThisInitialized(_this), "handleMinutesChange", function (minutes) {
@@ -94,6 +118,8 @@ var TimeField = /*#__PURE__*/function (_Component) {
94
118
  });
95
119
 
96
120
  _defineProperty(_assertThisInitialized(_this), "handleMinutesKeyDown", function (event) {
121
+ var minute = Number(event.target.value);
122
+
97
123
  if (_this.props.ignoredKeys.includes(event.key)) {
98
124
  return;
99
125
  }
@@ -105,6 +131,14 @@ var TimeField = /*#__PURE__*/function (_Component) {
105
131
  if (event.key === 'ArrowRight') {
106
132
  _this.inputBox.focus(2);
107
133
  }
134
+
135
+ if (event.key === 'ArrowUp' && minute >= MAX_MINUTE) {
136
+ _this.updateMinutes(1);
137
+ }
138
+
139
+ if (event.key === 'ArrowDown' && minute <= MIN_MINUTE) {
140
+ _this.updateMinutes(-1);
141
+ }
108
142
  });
109
143
 
110
144
  _defineProperty(_assertThisInitialized(_this), "handleAMPMKeyDown", function (event) {
@@ -194,8 +228,8 @@ var TimeField = /*#__PURE__*/function (_Component) {
194
228
  fontVariantNumeric: "tabular-nums",
195
229
  moveFocusOnMax: true,
196
230
  value: minutes,
197
- min: 0,
198
- max: 59,
231
+ min: MIN_MINUTE,
232
+ max: MAX_MINUTE,
199
233
  pad: 2,
200
234
  grow: 0,
201
235
  width: "2ch",
@@ -182,7 +182,7 @@ it('does not change the meridiem on arrow key if arrow keys are ignored', functi
182
182
  userEvent.type(meridiemInput, '{arrowup}');
183
183
  expect(meridiemInput).toHaveValue('PM');
184
184
  });
185
- it('changes the value of hours on arrow up or arrow down', function () {
185
+ it('increment hour on arrow up', function () {
186
186
  var _setup15 = setup({
187
187
  hours: 1,
188
188
  minutes: 42
@@ -193,13 +193,156 @@ it('changes the value of hours on arrow up or arrow down', function () {
193
193
  userEvent.type(hourInput, '{arrowup}');
194
194
  expect(hourInput).toHaveValue("02");
195
195
  });
196
- it('does not change the value of hours on arrow up or arrow down if ignored', function () {
196
+ it('increment hour if arrow up on minutes exceeds max value', function () {
197
197
  var _setup16 = setup({
198
+ hours: 1,
199
+ minutes: 59
200
+ }),
201
+ hourInput = _setup16.hourInput,
202
+ minuteInput = _setup16.minuteInput;
203
+
204
+ expect(hourInput).toHaveValue("01");
205
+ userEvent.type(minuteInput, '{arrowup}');
206
+ expect(hourInput).toHaveValue("02");
207
+ });
208
+ it('decrement hour on arrow down', function () {
209
+ var _setup17 = setup({
210
+ hours: 2,
211
+ minutes: 42
212
+ }),
213
+ hourInput = _setup17.hourInput;
214
+
215
+ expect(hourInput).toHaveValue("02");
216
+ userEvent.type(hourInput, '{arrowdown}');
217
+ expect(hourInput).toHaveValue("01");
218
+ });
219
+ it('decrement hour if arrow down on minutes exceeds min value', function () {
220
+ var _setup18 = setup({
221
+ hours: 2,
222
+ minutes: 0
223
+ }),
224
+ hourInput = _setup18.hourInput,
225
+ minuteInput = _setup18.minuteInput;
226
+
227
+ expect(hourInput).toHaveValue("02");
228
+ userEvent.type(minuteInput, '{arrowdown}');
229
+ expect(hourInput).toHaveValue("01");
230
+ });
231
+ it('increment minute on arrow up', function () {
232
+ var _setup19 = setup({
233
+ hours: 1,
234
+ minutes: 42
235
+ }),
236
+ minuteInput = _setup19.minuteInput;
237
+
238
+ expect(minuteInput).toHaveValue("42");
239
+ userEvent.type(minuteInput, '{arrowup}');
240
+ expect(minuteInput).toHaveValue("43");
241
+ });
242
+ it('decrement minute on arrow down', function () {
243
+ var _setup20 = setup({
244
+ hours: 2,
245
+ minutes: 43
246
+ }),
247
+ minuteInput = _setup20.minuteInput;
248
+
249
+ expect(minuteInput).toHaveValue("43");
250
+ userEvent.type(minuteInput, '{arrowdown}');
251
+ expect(minuteInput).toHaveValue("42");
252
+ });
253
+ it('set minute to min value if arrow up on minutes exceeds max value', function () {
254
+ var _setup21 = setup({
255
+ hours: 1,
256
+ minutes: 59
257
+ }),
258
+ minuteInput = _setup21.minuteInput;
259
+
260
+ expect(minuteInput).toHaveValue("59");
261
+ userEvent.type(minuteInput, '{arrowup}');
262
+ expect(minuteInput).toHaveValue("00");
263
+ });
264
+ it('set minute to max value if arrow down on minutes exceeds min value', function () {
265
+ var _setup22 = setup({
266
+ hours: 1,
267
+ minutes: 0
268
+ }),
269
+ minuteInput = _setup22.minuteInput;
270
+
271
+ expect(minuteInput).toHaveValue("00");
272
+ userEvent.type(minuteInput, '{arrowdown}');
273
+ expect(minuteInput).toHaveValue("59");
274
+ });
275
+ it('toggle meridiem if arrow up on hour exceeds eleven', function () {
276
+ var _setup23 = setup({
277
+ hours: 11
278
+ }),
279
+ hourInput = _setup23.hourInput,
280
+ meridiemInput = _setup23.meridiemInput;
281
+
282
+ expect(meridiemInput).toHaveValue('AM');
283
+ userEvent.type(hourInput, '{arrowup}');
284
+ expect(meridiemInput).toHaveValue('PM');
285
+ });
286
+ it('toggle meridiem if arrow down on hour exceeds min value', function () {
287
+ var _setup24 = setup({
288
+ hours: 1
289
+ }),
290
+ hourInput = _setup24.hourInput,
291
+ meridiemInput = _setup24.meridiemInput;
292
+
293
+ expect(meridiemInput).toHaveValue('AM');
294
+ userEvent.type(hourInput, '{arrowdown}');
295
+ expect(meridiemInput).toHaveValue('PM');
296
+ });
297
+ it('set 12 hour clock to min value if arrow up on hour exceeds max value', function () {
298
+ var _setup25 = setup({
299
+ hours: 12
300
+ }),
301
+ hourInput = _setup25.hourInput;
302
+
303
+ expect(hourInput).toHaveValue("12");
304
+ userEvent.type(hourInput, '{arrowup}');
305
+ expect(hourInput).toHaveValue("01");
306
+ });
307
+ it('set 24 hour clock to min value if arrow up on hour exceeds max value', function () {
308
+ var _setup26 = setup({
309
+ hours: 23,
310
+ twelveHourClock: false
311
+ }),
312
+ hourInput = _setup26.hourInput;
313
+
314
+ expect(hourInput).toHaveValue("23");
315
+ userEvent.type(hourInput, '{arrowup}');
316
+ expect(hourInput).toHaveValue("00");
317
+ });
318
+ it('set 12 hour clock to max value if arrow down on hour exceeds min value', function () {
319
+ var _setup27 = setup({
320
+ hours: 1
321
+ }),
322
+ hourInput = _setup27.hourInput;
323
+
324
+ expect(hourInput).toHaveValue("01");
325
+ userEvent.type(hourInput, '{arrowdown}');
326
+ expect(hourInput).toHaveValue("12");
327
+ });
328
+ it('set 24 hour clock to max value if arrow down on hour exceeds min value', function () {
329
+ var _setup28 = setup({
330
+ hours: 0,
331
+ twelveHourClock: false
332
+ }),
333
+ hourInput = _setup28.hourInput;
334
+
335
+ expect(hourInput).toHaveValue("00");
336
+ userEvent.type(hourInput, '{arrowdown}');
337
+ expect(hourInput).toHaveValue("23");
338
+ });
339
+ it('does not change the value of hours on arrow up or arrow down if ignored', function () {
340
+ var _setup29 = setup({
198
341
  hours: 1,
199
342
  minutes: 42,
200
343
  ignoredKeys: ['ArrowUp', 'ArrowDown']
201
344
  }),
202
- hourInput = _setup16.hourInput;
345
+ hourInput = _setup29.hourInput;
203
346
 
204
347
  expect(hourInput).toHaveValue("01");
205
348
  userEvent.type(hourInput, '{arrowup}');
@@ -208,12 +351,12 @@ it('does not change the value of hours on arrow up or arrow down if ignored', fu
208
351
  expect(hourInput).toHaveValue("01");
209
352
  });
210
353
  it('does not change the value of minutes on arrow up or arrow down if ignored', function () {
211
- var _setup17 = setup({
354
+ var _setup30 = setup({
212
355
  hours: 1,
213
356
  minutes: 42,
214
357
  ignoredKeys: ['ArrowUp', 'ArrowDown']
215
358
  }),
216
- minuteInput = _setup17.minuteInput;
359
+ minuteInput = _setup30.minuteInput;
217
360
 
218
361
  expect(minuteInput).toHaveValue("42");
219
362
  userEvent.type(minuteInput, '{arrowup}');
@@ -223,15 +366,15 @@ it('does not change the value of minutes on arrow up or arrow down if ignored',
223
366
  }); // ios
224
367
 
225
368
  it('renders iOS as a single input', function () {
226
- var _setup18 = setup({
369
+ var _setup31 = setup({
227
370
  isIOS: true,
228
371
  hours: 13,
229
372
  minutes: 42
230
373
  }),
231
- container = _setup18.container,
232
- hourInput = _setup18.hourInput,
233
- minuteInput = _setup18.minuteInput,
234
- meridiemInput = _setup18.meridiemInput;
374
+ container = _setup31.container,
375
+ hourInput = _setup31.hourInput,
376
+ minuteInput = _setup31.minuteInput,
377
+ meridiemInput = _setup31.meridiemInput;
235
378
 
236
379
  var singleTimeInput = container.querySelector('[type=time]');
237
380
  expect(singleTimeInput).toHaveValue('13:42');
@@ -0,0 +1,160 @@
1
+ import _regeneratorRuntime from "@babel/runtime/regenerator";
2
+ import _asyncToGenerator from "@babel/runtime/helpers/esm/asyncToGenerator";
3
+ import React from 'react';
4
+ import { act, render, screen, waitFor } from '@testing-library/react';
5
+ import userEvent from '@testing-library/user-event';
6
+ import '@testing-library/jest-dom/extend-expect';
7
+ import { Button, Link, StackView, Text, Tooltip, hooks } from '..';
8
+
9
+ var _ref = /*#__PURE__*/React.createElement(Text, null, "Tooltip anchor");
10
+
11
+ var _ref3 = /*#__PURE__*/React.createElement(Text, null, "Tooltip anchor");
12
+
13
+ var _ref5 = /*#__PURE__*/React.createElement(Button, null, "Tooltip anchor");
14
+
15
+ var _ref7 = /*#__PURE__*/React.createElement(Text, null, "Tooltip anchor");
16
+
17
+ var _ref8 = /*#__PURE__*/React.createElement(Tooltip, {
18
+ title: "Test Text"
19
+ }, /*#__PURE__*/React.createElement(Text, null, "Tooltip anchor"));
20
+
21
+ describe('Tooltip', function () {
22
+ test('does not render string when closed', function () {
23
+ var string = 'Test Text';
24
+ render( /*#__PURE__*/React.createElement(Tooltip, {
25
+ title: string
26
+ }, _ref));
27
+ expect(screen.queryByText(string)).toBeNull();
28
+ });
29
+ test('renders string on hover', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
30
+ var string, tooltip;
31
+ return _regeneratorRuntime.wrap(function _callee$(_context) {
32
+ while (1) {
33
+ switch (_context.prev = _context.next) {
34
+ case 0:
35
+ string = 'Test Text';
36
+ render( /*#__PURE__*/React.createElement(Tooltip, {
37
+ title: string
38
+ }, _ref3));
39
+ userEvent.hover(screen.getByText('Tooltip anchor'));
40
+ _context.next = 5;
41
+ return screen.findByText(string);
42
+
43
+ case 5:
44
+ tooltip = _context.sent;
45
+ expect(tooltip).toBeInTheDocument();
46
+
47
+ case 7:
48
+ case "end":
49
+ return _context.stop();
50
+ }
51
+ }
52
+ }, _callee);
53
+ })));
54
+ test('renders string on focus', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee2() {
55
+ var string, tooltip;
56
+ return _regeneratorRuntime.wrap(function _callee2$(_context2) {
57
+ while (1) {
58
+ switch (_context2.prev = _context2.next) {
59
+ case 0:
60
+ string = 'Test Text';
61
+ render( /*#__PURE__*/React.createElement(Tooltip, {
62
+ title: string,
63
+ triggerOnFocus: true
64
+ }, _ref5));
65
+ userEvent.tab();
66
+ _context2.next = 5;
67
+ return screen.findByText(string);
68
+
69
+ case 5:
70
+ tooltip = _context2.sent;
71
+ expect(tooltip).toBeInTheDocument();
72
+
73
+ case 7:
74
+ case "end":
75
+ return _context2.stop();
76
+ }
77
+ }
78
+ }, _callee2);
79
+ })));
80
+ test('removes string after timeout', /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee3() {
81
+ var string, anchor, tooltip;
82
+ return _regeneratorRuntime.wrap(function _callee3$(_context3) {
83
+ while (1) {
84
+ switch (_context3.prev = _context3.next) {
85
+ case 0:
86
+ string = 'Test Text';
87
+ render( /*#__PURE__*/React.createElement(Tooltip, {
88
+ title: string
89
+ }, _ref7));
90
+ anchor = screen.getByText('Tooltip anchor');
91
+ expect(screen.queryByText(string)).toBeNull();
92
+ userEvent.hover(anchor);
93
+ _context3.next = 7;
94
+ return screen.findByText(string);
95
+
96
+ case 7:
97
+ tooltip = _context3.sent;
98
+ expect(tooltip).toBeInTheDocument();
99
+ userEvent.unhover(anchor);
100
+ _context3.next = 12;
101
+ return waitFor(function () {
102
+ expect(screen.queryByText(string)).not.toBeInTheDocument();
103
+ });
104
+
105
+ case 12:
106
+ case "end":
107
+ return _context3.stop();
108
+ }
109
+ }
110
+ }, _callee3);
111
+ })));
112
+ test('stays open when hovering a custom tooltip', function () {
113
+ var CustomTooltip = function CustomTooltip() {
114
+ var _hooks$useHover = hooks.useHover(),
115
+ hover = _hooks$useHover.hover,
116
+ hoverProps = _hooks$useHover.hoverProps;
117
+
118
+ return /*#__PURE__*/React.createElement(Tooltip, {
119
+ title: /*#__PURE__*/React.createElement(Link, {
120
+ to: "#"
121
+ }, /*#__PURE__*/React.createElement(StackView, null, /*#__PURE__*/React.createElement(Text, {
122
+ "data-hover": hover
123
+ }, string)))
124
+ }, /*#__PURE__*/React.createElement(Button, hoverProps, "Tooltip anchor"));
125
+ };
126
+
127
+ var string = 'Test Text';
128
+ render( /*#__PURE__*/React.createElement(CustomTooltip, null));
129
+ var anchor = screen.getByText('Tooltip anchor');
130
+ userEvent.hover(anchor);
131
+ act(function () {
132
+ jest.runAllTimers();
133
+ });
134
+ var tooltip = screen.getByText(string);
135
+ expect(tooltip).toBeInTheDocument();
136
+ userEvent.unhover(anchor);
137
+ userEvent.hover(tooltip);
138
+ act(function () {
139
+ jest.runAllTimers();
140
+ });
141
+ expect(tooltip).toBeInTheDocument();
142
+ userEvent.unhover(tooltip);
143
+ act(function () {
144
+ jest.runAllTimers();
145
+ });
146
+ expect(tooltip).not.toBeInTheDocument();
147
+ });
148
+ test('cleans up pageViewChange listeners', function () {
149
+ window.removeEventListener = jest.fn().mockImplementation(function (event, callback) {});
150
+ document.removeEventListener = jest.fn().mockImplementation(function (event, callback) {});
151
+
152
+ var _render = render(_ref8),
153
+ unmount = _render.unmount;
154
+
155
+ unmount();
156
+ expect(window.removeEventListener).toBeCalledWith('focus', expect.any(Function));
157
+ expect(window.removeEventListener).toBeCalledWith('blur', expect.any(Function));
158
+ expect(document.removeEventListener).toBeCalledWith('visibilitychange', expect.any(Function));
159
+ });
160
+ });
@@ -0,0 +1 @@
1
+ import '@testing-library/jest-dom/extend-expect';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@planningcenter/tapestry-react",
3
- "version": "2.6.0",
3
+ "version": "2.6.2",
4
4
  "description": "A collection of flexible React components to help you build resilient, accessible user interfaces quickly and effectively.",
5
5
  "author": "Front End Systems Engineering <frontend@pco.bz>",
6
6
  "main": "dist/cjs/index.js",
@@ -98,7 +98,7 @@ function Collapse(props: Props) {
98
98
  }
99
99
  }
100
100
  if (instantRender) {
101
- handleComplete()
101
+ requestAnimationFrame(() => handleComplete())
102
102
  }
103
103
  node.addEventListener('transitionend', handleTransitionEnd)
104
104
  return () => {
@@ -91,6 +91,7 @@ class Dropdown extends Component<Props> {
91
91
  super(props)
92
92
  this.state = {
93
93
  isPopoverOpen: props.defaultOpen || props.forceOpen,
94
+ preventBlur: false,
94
95
  }
95
96
  this.id = generateId('dropdown')
96
97
  }
@@ -112,6 +113,7 @@ class Dropdown extends Component<Props> {
112
113
  }
113
114
 
114
115
  togglePopover = (event) => {
116
+ this.setState({ preventBlur: false })
115
117
  if (this.state.isPopoverOpen) {
116
118
  this.closePopover(event)
117
119
  } else {
@@ -223,7 +225,13 @@ class Dropdown extends Component<Props> {
223
225
  title: arrowIconOnly ? 'arrow down' : restProps.title,
224
226
  tabIndex: 0,
225
227
  cursor: 'pointer',
226
- onBlur: requestBlur,
228
+ onBlur: (event) => {
229
+ if (this.state.preventBlur) {
230
+ this.setState({ preventBlur: false })
231
+ } else {
232
+ requestBlur(event)
233
+ }
234
+ },
227
235
  onClick: (event) => {
228
236
  this.togglePopover()
229
237
  if (!isPopoverOpen) {
@@ -277,6 +285,9 @@ class Dropdown extends Component<Props> {
277
285
  })
278
286
  }
279
287
  },
288
+ onMouseDown: () => {
289
+ this.setState({ preventBlur: true })
290
+ },
280
291
  ...restProps,
281
292
  }
282
293
  if (React.isValidElement(triggerElement)) {
@@ -119,6 +119,11 @@ type InputProps = {
119
119
  * Changes color of the input's placeholder text. Both [theme colors](/colors) and html values are supported.
120
120
  */
121
121
  placeholderColor?: string,
122
+
123
+ /**
124
+ * Sets the input's required attribute.
125
+ */
126
+ required?: boolean,
122
127
  }
123
128
 
124
129
  class Input extends Component<InputProps> {
@@ -162,6 +167,7 @@ class Input extends Component<InputProps> {
162
167
  highlightOnInteraction,
163
168
  type,
164
169
  value,
170
+ required,
165
171
  ...restProps
166
172
  } = this.props
167
173
  const ariaLabel = restProps['aria-label']
@@ -206,6 +212,7 @@ class Input extends Component<InputProps> {
206
212
  onBlur={onBlur}
207
213
  onKeyDown={onKeyDown}
208
214
  onKeyUp={onKeyUp}
215
+ required={required}
209
216
  />
210
217
  </InputBox>
211
218
  )
@@ -69,11 +69,13 @@ function Modal({
69
69
  >
70
70
  <Box
71
71
  backgroundColor="surface"
72
+ boxShadow="0 0 20px rgba(0, 0, 0, 0.15)"
72
73
  innerRef={modalRef}
73
74
  margin={4}
74
75
  maxWidth={60}
75
- padding={2}
76
- radius={3}
76
+ padding={4}
77
+ paddingBottom={3}
78
+ radius={8}
77
79
  width="100%"
78
80
  {...themeProps}
79
81
  >
@@ -18,6 +18,9 @@ import {
18
18
  getTimeFromDate,
19
19
  } from './utils'
20
20
 
21
+ const MIN_MINUTE = 0
22
+ const MAX_MINUTE = 59
23
+
21
24
  type Props = {
22
25
  /**
23
26
  * An array of keys to ignore when pushed.
@@ -119,9 +122,31 @@ class TimeField extends Component<Props> {
119
122
  }
120
123
 
121
124
  handleHoursKeyDown = (event) => {
125
+ const hour = Number(event.target.value)
126
+ const isTwelveHourClock = this.props.twelveHourClock
127
+ const isHourEleven = HOURS_TO_NOON - 1
128
+ const maxHour = isTwelveHourClock ? HOURS_TO_NOON : HOURS_IN_DAY - 1
129
+ const minHour = isTwelveHourClock ? 1 : 0
130
+
122
131
  if (event.key === 'ArrowRight') {
123
132
  this.inputBox.focus(1)
124
133
  }
134
+
135
+ if (event.key === 'ArrowUp' && isTwelveHourClock && hour === isHourEleven) {
136
+ this.toggleMeridiem()
137
+ }
138
+
139
+ if (event.key === 'ArrowUp' && hour === maxHour) {
140
+ this.updateHours(1)
141
+ }
142
+
143
+ if (event.key === 'ArrowDown' && isTwelveHourClock && hour === minHour) {
144
+ this.toggleMeridiem()
145
+ }
146
+
147
+ if (event.key === 'ArrowDown' && hour === minHour) {
148
+ this.updateHours(-1)
149
+ }
125
150
  }
126
151
 
127
152
  handleMinutesChange = (minutes) => {
@@ -129,6 +154,8 @@ class TimeField extends Component<Props> {
129
154
  }
130
155
 
131
156
  handleMinutesKeyDown = (event) => {
157
+ const minute = Number(event.target.value)
158
+
132
159
  if (this.props.ignoredKeys.includes(event.key)) {
133
160
  return
134
161
  }
@@ -138,6 +165,12 @@ class TimeField extends Component<Props> {
138
165
  if (event.key === 'ArrowRight') {
139
166
  this.inputBox.focus(2)
140
167
  }
168
+ if (event.key === 'ArrowUp' && minute >= MAX_MINUTE) {
169
+ this.updateMinutes(1)
170
+ }
171
+ if (event.key === 'ArrowDown' && minute <= MIN_MINUTE) {
172
+ this.updateMinutes(-1)
173
+ }
141
174
  }
142
175
 
143
176
  handleAMPMKeyDown = (event) => {
@@ -214,8 +247,8 @@ class TimeField extends Component<Props> {
214
247
  fontVariantNumeric="tabular-nums"
215
248
  moveFocusOnMax
216
249
  value={minutes}
217
- min={0}
218
- max={59}
250
+ min={MIN_MINUTE}
251
+ max={MAX_MINUTE}
219
252
  pad={2}
220
253
  grow={0}
221
254
  width="2ch"