Skip to content

Commit 8b9f156

Browse files
authored
fix: keyboard accessibility for time picker (#2613)
1 parent 9c70fd9 commit 8b9f156

3 files changed

Lines changed: 71 additions & 2 deletions

File tree

src/generic/datepicker-control/DatepickerControl.jsx

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,21 @@ const DatepickerControl = ({
3232
[DATEPICKER_TYPES.date]: DATE_FORMAT,
3333
[DATEPICKER_TYPES.time]: TIME_FORMAT,
3434
};
35+
const isTimePicker = type === DATEPICKER_TYPES.time;
36+
37+
let describedByIds;
38+
if (isTimePicker) {
39+
const ids = [`${controlName}-timehint`];
40+
if (helpText) {
41+
ids.push(`${controlName}-helptext`);
42+
}
43+
describedByIds = ids.filter(Boolean).join(' ') || undefined;
44+
} else if (helpText) {
45+
describedByIds = `${controlName}-helptext`;
46+
}
3547

3648
return (
37-
<Form.Group className="form-group-custom datepicker-custom">
49+
<Form.Group controlId={controlName} className="form-group-custom datepicker-custom">
3850
<Form.Label className="d-flex justify-content-between">
3951
{label}
4052
{showUTC && (
@@ -52,6 +64,7 @@ const DatepickerControl = ({
5264
/>
5365
)}
5466
<DatePicker
67+
id={controlName}
5568
name={controlName}
5669
selected={formattedDate}
5770
disabled={readonly}
@@ -67,14 +80,26 @@ const DatepickerControl = ({
6780
showTimeSelectOnly={type === DATEPICKER_TYPES.time}
6881
placeholderText={inputFormat[type].toLocaleUpperCase()}
6982
showPopperArrow={false}
83+
aria-describedby={describedByIds}
7084
onChange={(date) => {
7185
if (isValidDate(date)) {
7286
onChange(convertToStringFromDate(date));
7387
}
7488
}}
7589
/>
7690
</div>
77-
{helpText && <Form.Control.Feedback>{helpText}</Form.Control.Feedback>}
91+
{isTimePicker && (
92+
<Form.Text id={`${controlName}-timehint`} className="sr-only">
93+
{intl.formatMessage(messages.timepickerScreenreaderHint, {
94+
timeFormat: inputFormat[type].toLocaleUpperCase(),
95+
})}
96+
</Form.Text>
97+
)}
98+
{helpText && (
99+
<Form.Control.Feedback id={`${controlName}-helptext`}>
100+
{helpText}
101+
</Form.Control.Feedback>
102+
)}
78103
</Form.Group>
79104
);
80105
};

src/generic/datepicker-control/DatepickerControl.test.jsx

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,4 +48,40 @@ describe('<DatepickerControl />', () => {
4848
convertToStringFromDate('06/16/2023'),
4949
);
5050
});
51+
52+
it('renders time picker with accessibility hint', () => {
53+
const { getByText, getByPlaceholderText } = render(
54+
<RootWrapper
55+
{...props}
56+
type={DATEPICKER_TYPES.time}
57+
value="2025-01-01T10:00:00Z"
58+
helpText=""
59+
/>,
60+
);
61+
const input = getByPlaceholderText('HH:MM');
62+
63+
expect(
64+
getByText('Enter time in HH:MM or twelve-hour format, for example 6:00 PM.'),
65+
).toBeInTheDocument();
66+
expect(input.getAttribute('aria-describedby')).toContain('fooControlName-timehint');
67+
});
68+
69+
it('renders time picker with accessibility hint and help text', () => {
70+
const { getByText, getByPlaceholderText } = render(
71+
<RootWrapper
72+
{...props}
73+
type={DATEPICKER_TYPES.time}
74+
value="2025-01-01T10:00:00Z"
75+
helpText="This is help text"
76+
/>,
77+
);
78+
const input = getByPlaceholderText('HH:MM');
79+
80+
expect(
81+
getByText('Enter time in HH:MM or twelve-hour format, for example 6:00 PM.'),
82+
).toBeInTheDocument();
83+
expect(getByText('This is help text')).toBeInTheDocument();
84+
expect(input.getAttribute('aria-describedby')).toContain('fooControlName-timehint');
85+
expect(input.getAttribute('aria-describedby')).toContain('fooControlName-helptext');
86+
});
5187
});

src/generic/datepicker-control/messages.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@ const messages = defineMessages({
99
id: 'course-authoring.schedule.schedule-section.datepicker.utc',
1010
defaultMessage: 'UTC',
1111
},
12+
timepickerAriaLabel: {
13+
id: 'course-authoring.schedule.schedule-section.timepicker.aria-label',
14+
defaultMessage: 'Time input field. Enter a time or use the arrow keys to adjust.',
15+
},
16+
timepickerScreenreaderHint: {
17+
id: 'course-authoring.schedule.schedule-section.timepicker.screenreader-hint',
18+
defaultMessage: 'Enter time in {timeFormat} or twelve-hour format, for example 6:00 PM.',
19+
},
1220
});
1321

1422
export default messages;

0 commit comments

Comments
 (0)