Skip to content

Commit 13d3e99

Browse files
committed
🐛 Fix: Update TimePicker a11y
TAB and SHIFT+TAB now work as expected when cycling time inputs
1 parent 983759a commit 13d3e99

File tree

8 files changed

+199
-176
lines changed

8 files changed

+199
-176
lines changed

packages/web/src/components/TimePicker/index.ts

Lines changed: 0 additions & 3 deletions
This file was deleted.

packages/web/src/views/Forms/EventForm/DateTimeSection/DatePickers.tsx renamed to packages/web/src/views/Forms/EventForm/DateTimeSection/DatePickers/DatePickers.tsx

File renamed without changes.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { Flex } from "@web/components/Flex";
2+
import styled from "styled-components";
3+
4+
export const StyledDateFlex = styled(Flex)`
5+
width: 120px;
6+
`;

packages/web/src/views/Forms/EventForm/DateTimeSection/DateTimeSection.tsx

Lines changed: 14 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,10 @@ import customParseFormat from "dayjs/plugin/customParseFormat";
44
import { Categories_Event, Schema_Event } from "@core/types/event.types";
55
import { SelectOption } from "@web/common/types/component.types";
66
import { AlignItems } from "@web/components/Flex/styled";
7-
import { TimePicker } from "@web/components/TimePicker";
8-
import {
9-
getTimeOptionByValue,
10-
getTimeOptions,
11-
mapToBackend,
12-
shouldAdjustComplimentTime,
13-
} from "@web/common/utils/web.date.util";
14-
import { Option_Time } from "@web/common/types/util.types";
157

16-
import { DatePickers } from "./DatePickers";
17-
import { StyledDateTimeFlex, StyledTimeFlex } from "./styled";
8+
import { DatePickers } from "./DatePickers/DatePickers";
9+
import { StyledDateTimeFlex } from "./styled";
10+
import { TimePickers } from "./TimePicker/TimePickers";
1811

1912
dayjs.extend(customParseFormat);
2013

@@ -44,7 +37,6 @@ export const DateTimeSection: FC<Props> = ({
4437
category,
4538
event,
4639
inputColor,
47-
nextElementRef,
4840
isEndDatePickerOpen,
4941
isStartDatePickerOpen,
5042
selectedEndDate,
@@ -59,93 +51,6 @@ export const DateTimeSection: FC<Props> = ({
5951
startTime,
6052
endTime,
6153
}) => {
62-
const timeOptions = getTimeOptions();
63-
64-
const endTimeRef = useRef(null);
65-
66-
const adjustComplimentTimeIfNeeded = (
67-
changed: "start" | "end",
68-
value: string
69-
): Option_Time => {
70-
const start = changed === "start" ? value : startTime.value;
71-
const end = changed === "end" ? value : endTime.value;
72-
73-
const { shouldAdjust, adjustment, compliment } = shouldAdjustComplimentTime(
74-
changed,
75-
{
76-
oldStart: startTime.value,
77-
start,
78-
oldEnd: endTime.value,
79-
end,
80-
}
81-
);
82-
83-
if (shouldAdjust) {
84-
if (changed === "start") {
85-
const _correctedEnd = compliment.add(adjustment, "minutes");
86-
const correctedEnd = getTimeOptionByValue(_correctedEnd);
87-
setEndTime(correctedEnd);
88-
return correctedEnd;
89-
}
90-
91-
if (changed === "end") {
92-
const _correctedStart = compliment.subtract(adjustment, "minutes");
93-
const correctedStart = getTimeOptionByValue(_correctedStart);
94-
setStartTime(correctedStart);
95-
return correctedStart;
96-
}
97-
}
98-
99-
const defaultOption = changed === "start" ? startTime : endTime;
100-
return defaultOption;
101-
};
102-
103-
const onSelectEndTime = (option: SelectOption<string>) => {
104-
setEndTime(option);
105-
const correctedStart = adjustComplimentTimeIfNeeded("end", option.value);
106-
107-
if (endTime.value && endTime.value !== option.value) {
108-
const { startDate, endDate } = mapToBackend({
109-
startDate: selectedStartDate,
110-
endDate: selectedEndDate,
111-
startTime: correctedStart ? correctedStart : startTime,
112-
endTime: option,
113-
isAllDay: false,
114-
});
115-
116-
const _event = {
117-
...event,
118-
startDate,
119-
endDate,
120-
};
121-
122-
setEvent(_event);
123-
}
124-
};
125-
126-
const onSelectStartTime = (option: SelectOption<string>) => {
127-
setStartTime(option);
128-
const correctedEnd = adjustComplimentTimeIfNeeded("start", option.value);
129-
130-
if (startTime.value && startTime.value !== option.value) {
131-
const { startDate, endDate } = mapToBackend({
132-
startDate: selectedStartDate,
133-
endDate: selectedEndDate,
134-
startTime: option,
135-
endTime: correctedEnd ? correctedEnd : endTime,
136-
isAllDay: false,
137-
});
138-
139-
const _event = {
140-
...event,
141-
startDate,
142-
endDate,
143-
};
144-
145-
setEvent(_event);
146-
}
147-
};
148-
14954
return (
15055
<StyledDateTimeFlex alignItems={AlignItems.CENTER} role="tablist">
15156
{category === Categories_Event.ALLDAY && (
@@ -164,28 +69,17 @@ export const DateTimeSection: FC<Props> = ({
16469
)}
16570

16671
{category === Categories_Event.TIMED && (
167-
<StyledTimeFlex alignItems={AlignItems.CENTER}>
168-
<TimePicker
169-
bgColor={bgColor}
170-
value={startTime}
171-
inputId="startTimePicker"
172-
nextElementRef={endTimeRef}
173-
onChange={onSelectStartTime}
174-
openMenuOnFocus
175-
options={timeOptions}
176-
/>
177-
-
178-
<TimePicker
179-
bgColor={bgColor}
180-
value={endTime}
181-
inputId="endTimePicker"
182-
nextElementRef={nextElementRef}
183-
onChange={onSelectEndTime}
184-
openMenuOnFocus
185-
options={timeOptions}
186-
ref={endTimeRef}
187-
/>
188-
</StyledTimeFlex>
72+
<TimePickers
73+
bgColor={bgColor}
74+
endTime={endTime}
75+
event={event}
76+
setStartTime={setStartTime}
77+
setEndTime={setEndTime}
78+
setEvent={setEvent}
79+
startTime={startTime}
80+
selectedEndDate={selectedEndDate}
81+
selectedStartDate={selectedStartDate}
82+
/>
18983
)}
19084
</StyledDateTimeFlex>
19185
);

packages/web/src/components/TimePicker/TimePicker.tsx renamed to packages/web/src/views/Forms/EventForm/DateTimeSection/TimePicker/TimePicker.tsx

Lines changed: 25 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,51 @@
1-
import React, { ForwardedRef, forwardRef, useRef, useState } from "react";
2-
import ReactSelect, { Props as RSProps, SelectInstance } from "react-select";
1+
import React from "react";
2+
import ReactSelect, { Props as RSProps } from "react-select";
33
import { Key } from "ts-key-enum";
44
import { SelectOption } from "@web/common/types/component.types";
55
import { Option_Time } from "@web/common/types/util.types";
66

77
import { StyledTimePicker, StyledDivider } from "./styled";
88

9-
export interface Props extends Omit<RSProps, "value"> {
9+
export interface Props extends Omit<RSProps, "onChange" | "value"> {
1010
bgColor: string;
11-
nextElementRef?: React.RefObject<HTMLTextAreaElement>;
11+
isMenuOpen: boolean;
1212
onChange: (option: SelectOption<string>) => void;
1313
options?: Option_Time[];
1414
selectClassName?: string;
15+
setIsMenuOpen: React.Dispatch<React.SetStateAction<boolean>>;
1516
value: SelectOption<string>;
1617
}
1718

18-
export const _TimePicker = (
19-
{
20-
bgColor,
21-
nextElementRef,
22-
onChange: _onChange,
23-
options,
24-
selectClassName,
25-
value,
26-
...props
27-
}: Props,
28-
ref: ForwardedRef<ReactSelect>
29-
// ref: Ref<SelectInstance>
30-
) => {
31-
const [isFocused, setIsFocused] = useState(false);
32-
const [isMenuOpened, setIsMenuOpen] = useState(false);
33-
19+
export const TimePicker = ({
20+
bgColor,
21+
isMenuOpen,
22+
onChange: _onChange,
23+
options,
24+
selectClassName,
25+
setIsMenuOpen,
26+
value,
27+
...props
28+
}: Props) => {
3429
const TIMEPICKER = "timepicker";
3530
let scrollTimer: number;
3631

3732
return (
38-
<StyledTimePicker bgColor={bgColor} isOpen={isMenuOpened}>
33+
<StyledTimePicker bgColor={bgColor}>
3934
<ReactSelect
4035
{...props}
41-
ref={ref}
4236
className={selectClassName}
4337
classNamePrefix={TIMEPICKER}
4438
value={value}
4539
maxMenuHeight={4 * 41}
46-
noOptionsMessage={() => "Nothin'. Typo?"}
47-
onBlur={() => {
48-
console.log("blurred");
49-
setIsFocused(false);
50-
}}
51-
onFocus={() => {
52-
console.log("focused");
53-
setIsFocused(true);
54-
}}
40+
blurInputOnSelect
41+
menuIsOpen={isMenuOpen}
42+
//@ts-expect-error uses custom onChange to manage focus in parent
5543
onChange={_onChange}
5644
onKeyDown={(e) => {
5745
const key = e.key;
5846

5947
if (key === Key.Enter || key === Key.Backspace) {
48+
console.log("stopping prop");
6049
e.stopPropagation();
6150
}
6251

@@ -68,6 +57,10 @@ export const _TimePicker = (
6857
setIsMenuOpen(false);
6958
e.stopPropagation();
7059
}
60+
61+
if (key === Key.Tab) {
62+
setIsMenuOpen(false);
63+
}
7164
}}
7265
onMenuOpen={() => {
7366
scrollTimer = window.setTimeout(() => {
@@ -78,29 +71,16 @@ export const _TimePicker = (
7871
defaultOpt.scrollIntoView();
7972
}
8073
}, 15);
81-
8274
setIsMenuOpen(true);
8375
}}
8476
onMenuClose={() => {
85-
console.log("closed menu");
86-
setIsFocused(false);
87-
setIsMenuOpen(false);
8877
clearTimeout(scrollTimer);
89-
90-
if (nextElementRef && nextElementRef.current) {
91-
console.log("focusing on next input...");
92-
nextElementRef.current.focus();
93-
} else {
94-
console.log("doing nothing with refs");
95-
}
9678
}}
9779
openMenuOnFocus={true}
9880
options={options}
9981
tabSelectsValue={false}
10082
/>
101-
{isFocused && <StyledDivider width="calc(100% - 16px)" />}
83+
{isMenuOpen && <StyledDivider width="calc(100% - 16px)" />}
10284
</StyledTimePicker>
10385
);
10486
};
105-
106-
export const TimePicker = forwardRef(_TimePicker);

0 commit comments

Comments
 (0)