Skip to content

Commit

Permalink
fix: fix #date-picker to use key navigation by default + NVDA enhance…
Browse files Browse the repository at this point in the history
…ments
  • Loading branch information
tujoworker committed Jun 24, 2020
1 parent 8917b58 commit 91f9292
Show file tree
Hide file tree
Showing 6 changed files with 143 additions and 115 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,11 @@ showTabs: true
| `status` | _(optional)_ text with a status message. The style defaults to an error message. |
| `status_state` | _(optional)_ defines the state of the status. Currently there are two statuses `[error, info]`. Defaults to `error`. |
| `disable_autofocus` | _(optional)_ once the date picker gets opened, there is a focus handling to ensure good accessibility. This can be disabled with this property. Defaults to `false`. |
| `enable_keyboard_nav` | _(optional)_ Enables easy keyboard navigation inside the calendar dates. **NB!** this feature suppresses Screen Reader navigation on NVDA since we then uses the arrow keys to navigate in the table of dates. Defaults to `false`. |
| `global_status_id` | _(optional)_ the `status_id` used for the target [GlobalStatus](/uilib/components/global-status). |
| [Space](/uilib/components/space/properties) | _(optional)_ spacing properties like `top` or `bottom` are supported. |

<!-- | `enable_keyboard_nav` | _(optional)_ Enables easy keyboard navigation inside the calendar dates. **NB!** this feature suppresses Screen Reader navigation on NVDA since we then uses the arrow keys to navigate in the table of dates. Defaults to `false`. | -->

## Shortcuts

You may use [date-fns](https://date-fns.org) to make date calculations.
Expand Down
10 changes: 5 additions & 5 deletions packages/dnb-ui-lib/src/components/date-picker/DatePicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -672,7 +672,7 @@ export default class DatePicker extends React.PureComponent {
only_month,
hide_last_week,
disable_autofocus,
enable_keyboard_nav,
enable_keyboard_nav, // eslint-disable-line
hide_navigation_buttons,
show_input, // eslint-disable-line
range,
Expand Down Expand Up @@ -779,6 +779,10 @@ export default class DatePicker extends React.PureComponent {
)
}

if (locale?.code) {
mainParams.lang = locale.code
}

validateDOMAttributes(this.props, inputParams)
validateDOMAttributes(null, submitParams)
validateDOMAttributes(null, pickerParams)
Expand Down Expand Up @@ -877,10 +881,6 @@ export default class DatePicker extends React.PureComponent {
endMonth={endMonth}
startDate={startDate}
endDate={endDate}
enableKeyboardNav={
isTrue(enable_keyboard_nav)
// || userUsesKeyboard // NB: We could extend this in future to be more smart
}
/>
{(addon_element || shortcuts) && (
<DatePickerAddon
Expand Down
218 changes: 123 additions & 95 deletions packages/dnb-ui-lib/src/components/date-picker/DatePickerCalendar.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ const propTypes = {
onlyMonth: PropTypes.bool,
hideNextMonthWeek: PropTypes.bool,
noAutofocus: PropTypes.bool,
enableKeyboardNav: PropTypes.bool,

onHover: PropTypes.func,
onSelect: PropTypes.func,
Expand Down Expand Up @@ -76,7 +75,6 @@ const defaultProps = {
onlyMonth: false,
hideNextMonthWeek: false,
noAutofocus: false,
enableKeyboardNav: false,

// locale
locale: nbLocale,
Expand Down Expand Up @@ -114,19 +112,23 @@ export default class DatePickerCalendar extends React.PureComponent {

componentDidMount() {
if (!this.props.noAutofocus && this.props.nr === 0) {
// because we block screen reader nav by providing the arrow key feature
// we set the focus on the label instead
if (this.props.enableKeyboardNav && this._listRef.current) {
this._listRef.current.focus({ preventScroll: true })
} else if (this._labelRef.current) {
if (this._labelRef.current) {
this._labelRef.current.focus({ preventScroll: true })
}
}
}

onKeyDownHandler = (event) => {
if (String(event?.target?.nodeName).toLowerCase() === 'td') {
try {
this._listRef.current.focus({ preventScroll: true })
} catch (e) {
//
}
}

if (typeof this.props.onKeyDown === 'function') {
this.props.onKeyDown(event, this._listRef, this.props.nr)
return this.props.onKeyDown(event, this._listRef, this.props.nr)
}
}

Expand Down Expand Up @@ -182,24 +184,37 @@ export default class DatePickerCalendar extends React.PureComponent {
}
} = this.context

this.days = getCalendar(
let count = 0
const days = getCalendar(
month || new Date(),
dayOffset(firstDayOfWeek),
{ onlyMonth, hideNextMonthWeek }
).map((date) =>
makeDayObject(date, {
startDate,
endDate,
hoverDate,
minDate,
maxDate,
month
})
)
.map((date) =>
makeDayObject(date, {
startDate,
endDate,
hoverDate,
minDate,
maxDate,
month
})
)
.reduce((acc, cur, i) => {
acc[count] = acc[count] || []
acc[count].push(cur)
if (i % 7 === 6) {
count++
}
return acc
}, {})

const weekDays = Object.values(days)

return (
<div
className={classnames('dnb-date-picker__calendar', rtl && 'rtl')}
lang={locale?.code}
>
{!hideNav && (
<div className="dnb-date-picker__header">
Expand All @@ -212,7 +227,6 @@ export default class DatePickerCalendar extends React.PureComponent {
context={this.context}
prevBtn={prevBtn}
onPrev={onPrev}
onKeyDown={this.onKeyDownHandler}
/>
</div>
<label
Expand Down Expand Up @@ -240,15 +254,14 @@ export default class DatePickerCalendar extends React.PureComponent {
context={this.context}
nextBtn={nextBtn}
onNext={onNext}
onKeyDown={this.onKeyDownHandler}
/>
</div>
</div>
)}
<table
role="grid"
className="dnb-no-focus"
tabIndex="-1"
tabIndex="0"
aria-labelledby={`${id}--title`}
onKeyDown={this.onKeyDownHandler}
ref={this._listRef}
Expand All @@ -260,7 +273,11 @@ export default class DatePickerCalendar extends React.PureComponent {
<th
key={i}
role="columnheader"
scope="col"
className="dnb-date-picker__labels__day"
aria-label={format(day, 'EEEE', {
locale
})}
>
{format(day, dayOfWeekFormat, {
locale
Expand All @@ -271,81 +288,92 @@ export default class DatePickerCalendar extends React.PureComponent {
</thead>
)}
<tbody>
<tr role="row" className="dnb-date-picker__days">
{this.days.map((day, i) => {
const title = format(day.date, 'PPPP', {
locale
})
const isDisabled =
day.isLastMonth || day.isNextMonth || day.isDisabled
const isInactive = day.isLastMonth || day.isNextMonth

// cell params
const paramsCell = {}
if (isInactive) {
paramsCell['aria-hidden'] = true
} else {
paramsCell.tabIndex = '-1'
if (day.isStartDate) {
paramsCell.id = id + '--button-start'
} else if (day.isEndDate) {
paramsCell.id = id + '--button-end'
}
}

// button params
const paramsButton = {}
if (nr === 0 ? day.isStartDate : day.isEndDate) {
paramsButton['aria-current'] = 'date'
paramsCell['aria-selected'] = true // aria-selected is not allowed on buttons
}
return (
<td
key={'day' + i}
role="gridcell"
className={classnames(
'dnb-date-picker__day',
'dnb-no-focus',
this.buildClassNames(day)
)}
{...paramsCell}
>
<Button
size="medium"
variant="secondary"
text={format(day.date, 'd', { locale })}
bounding={true}
disabled={isDisabled}
tabIndex={isDisabled ? '0' : '-1'} // fix for NVDA
aria-hidden={isInactive ? true : null}
aria-disabled={isDisabled}
aria-label={title}
{...paramsButton}
onClick={({ event }) =>
!day.isLastMonth &&
!day.isNextMonth &&
!day.isDisabled &&
onSelectRange({
day,
range,
startDate,
endDate,
onSelect,
resetDate,
event
})
}
onMouseOver={() =>
onHoverDay({ day, hoverDate, onHover })
}
onFocus={() =>
onHoverDay({ day, hoverDate, onHover })
{weekDays.map((week, i) => {
return (
<tr
key={'week' + i}
role="row"
className="dnb-date-picker__days"
>
{week.map((day, i) => {
const title = format(day.date, 'PPPP', {
locale
})
const isDisabled =
day.isLastMonth || day.isNextMonth || day.isDisabled
const isInactive = day.isLastMonth || day.isNextMonth

// cell params
const paramsCell = {}
if (isInactive) {
paramsCell['aria-hidden'] = true
} else {
paramsCell.tabIndex = '-1'
if (day.isStartDate) {
paramsCell.id = id + '--button-start'
} else if (day.isEndDate) {
paramsCell.id = id + '--button-end'
}
/>
</td>
)
})}
</tr>
}

// cell + button params
const paramsButton = {}
if (nr === 0 ? day.isStartDate : day.isEndDate) {
paramsButton['aria-current'] = 'date'
paramsCell['aria-selected'] = true // aria-selected is not allowed on buttons
}

return (
<td
key={'day' + i}
role="gridcell"
className={classnames(
'dnb-date-picker__day',
'dnb-no-focus',
this.buildClassNames(day)
)}
onFocus={this.onKeyDownHandler}
aria-label={title}
{...paramsCell}
>
<Button
size="medium"
variant="secondary"
text={format(day.date, 'd', { locale })}
bounding={true}
disabled={isDisabled}
tabIndex={isDisabled ? '0' : '-1'} // fix for NVDA
aria-hidden={isInactive ? true : null}
aria-disabled={isDisabled}
aria-label={title}
{...paramsButton}
onClick={({ event }) =>
!day.isLastMonth &&
!day.isNextMonth &&
!day.isDisabled &&
onSelectRange({
day,
range,
startDate,
endDate,
onSelect,
resetDate,
event
})
}
onMouseOver={() =>
onHoverDay({ day, hoverDate, onHover })
}
onFocus={() =>
onHoverDay({ day, hoverDate, onHover })
}
/>
</td>
)
})}
</tr>
)
})}
</tbody>
</table>
</div>
Expand Down
15 changes: 10 additions & 5 deletions packages/dnb-ui-lib/src/components/date-picker/DatePickerFooter.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,17 +69,22 @@ export default class DatePickerFooter extends React.PureComponent {
}
return (
<div className="dnb-date-picker__footer">
<p className="dnb-sr-only" aria-live="assertive">
{selectedDateTitle}
</p>

{(onSubmit && (
<Button
text={submit_button_text}
aria-label={
selectedDateTitle
? `${submit_button_text}, ${selectedDateTitle}`
: submit_button_text
}
// aria-label={
// selectedDateTitle
// ? `${submit_button_text}, ${selectedDateTitle}`
// : submit_button_text
// }
onClick={this.onSubmitHandler}
/>
)) || <span />}

<span>
{(onReset && (
<Button
Expand Down
Loading

0 comments on commit 91f9292

Please sign in to comment.