Skip to content
29 changes: 27 additions & 2 deletions src/generic/datepicker-control/DatepickerControl.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,21 @@ const DatepickerControl = ({
[DATEPICKER_TYPES.date]: DATE_FORMAT,
[DATEPICKER_TYPES.time]: TIME_FORMAT,
};
const isTimePicker = type === DATEPICKER_TYPES.time;

let describedByIds;
if (isTimePicker) {
const ids = [`${controlName}-timehint`];
if (helpText) {
ids.push(`${controlName}-helptext`);
}
describedByIds = ids.filter(Boolean).join(' ') || undefined;
} else if (helpText) {
describedByIds = `${controlName}-helptext`;
}

return (
<Form.Group className="form-group-custom datepicker-custom">
<Form.Group controlId={controlName} className="form-group-custom datepicker-custom">
<Form.Label className="d-flex justify-content-between">
{label}
{showUTC && (
Expand All @@ -52,6 +64,7 @@ const DatepickerControl = ({
/>
)}
<DatePicker
id={controlName}
name={controlName}
selected={formattedDate}
disabled={readonly}
Expand All @@ -67,14 +80,26 @@ const DatepickerControl = ({
showTimeSelectOnly={type === DATEPICKER_TYPES.time}
placeholderText={inputFormat[type].toLocaleUpperCase()}
showPopperArrow={false}
aria-describedby={describedByIds}
onChange={(date) => {
if (isValidDate(date)) {
onChange(convertToStringFromDate(date));
}
}}
/>
</div>
{helpText && <Form.Control.Feedback>{helpText}</Form.Control.Feedback>}
{isTimePicker && (
<Form.Text id={`${controlName}-timehint`} className="sr-only">
{intl.formatMessage(messages.timepickerScreenreaderHint, {
timeFormat: inputFormat[type].toLocaleUpperCase(),
})}
</Form.Text>
)}
{helpText && (
<Form.Control.Feedback id={`${controlName}-helptext`}>
{helpText}
</Form.Control.Feedback>
)}
</Form.Group>
);
};
Expand Down
36 changes: 36 additions & 0 deletions src/generic/datepicker-control/DatepickerControl.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,40 @@ describe('<DatepickerControl />', () => {
convertToStringFromDate('06/16/2023'),
);
});

it('renders time picker with accessibility hint', () => {
const { getByText, getByPlaceholderText } = render(
<RootWrapper
{...props}
type={DATEPICKER_TYPES.time}
value="2025-01-01T10:00:00Z"
helpText=""
/>,
);
const input = getByPlaceholderText('HH:MM');

expect(
getByText('Enter time in HH:MM or twelve-hour format, for example 6:00 PM.'),
).toBeInTheDocument();
expect(input.getAttribute('aria-describedby')).toContain('fooControlName-timehint');
});

it('renders time picker with accessibility hint and help text', () => {
const { getByText, getByPlaceholderText } = render(
<RootWrapper
{...props}
type={DATEPICKER_TYPES.time}
value="2025-01-01T10:00:00Z"
helpText="This is help text"
/>,
);
const input = getByPlaceholderText('HH:MM');

expect(
getByText('Enter time in HH:MM or twelve-hour format, for example 6:00 PM.'),
).toBeInTheDocument();
expect(getByText('This is help text')).toBeInTheDocument();
expect(input.getAttribute('aria-describedby')).toContain('fooControlName-timehint');
expect(input.getAttribute('aria-describedby')).toContain('fooControlName-helptext');
});
});
8 changes: 8 additions & 0 deletions src/generic/datepicker-control/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ const messages = defineMessages({
id: 'course-authoring.schedule.schedule-section.datepicker.utc',
defaultMessage: 'UTC',
},
timepickerAriaLabel: {
id: 'course-authoring.schedule.schedule-section.timepicker.aria-label',
defaultMessage: 'Time input field. Enter a time or use the arrow keys to adjust.',
},
timepickerScreenreaderHint: {
id: 'course-authoring.schedule.schedule-section.timepicker.screenreader-hint',
defaultMessage: 'Enter time in {timeFormat} or twelve-hour format, for example 6:00 PM.',
},
});

export default messages;