Skip to content

Commit

Permalink
feat(select a11y): handle pageUp and pageDown keys
Browse files Browse the repository at this point in the history
  • Loading branch information
Mohammer5 committed Oct 21, 2024
1 parent a4b5d08 commit af8a222
Show file tree
Hide file tree
Showing 19 changed files with 275 additions and 132 deletions.
1 change: 1 addition & 0 deletions components/select/src/single-select-a11y/menu/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Menu } from './menu.js'
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { colors, spacers } from '@dhis2/ui-constants'
import { Input } from '@dhis2-ui/input'
import PropTypes from 'prop-types'
import React from 'react'
import i18n from '../locales/index.js'
import i18n from '../../locales/index.js'

export function MenuFilter({ value, onChange, dataTest, placeholder, label }) {
return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,32 @@
import PropTypes from 'prop-types'
import React, { useEffect, useRef } from 'react'
import { isOptionHidden } from './is-option-hidden.js'
import React, { forwardRef, useEffect } from 'react'
import { isOptionHidden } from '../is-option-hidden.js'
import { optionProp } from '../shared-prop-types.js'
import { Option } from './option.js'
import { optionsProp } from './shared-prop-types.js'

export function MenuOptionsList({
comboBoxId,
expanded,
focussedOptionIndex,
idPrefix,
labelledBy,
options,
selected,
dataTest,
disabled,
loading,
onChange,
onBlur,
onKeyDown,
}) {
const listBoxRef = useRef()

export const MenuOptionsList = forwardRef(function MenuOptionsList(
{
comboBoxId,
expanded,
focussedOptionIndex,
idPrefix,
labelledBy,
options,
selected,
dataTest,
disabled,
loading,
onChange,
onBlur,
onKeyDown,
},
ref
) {
// scrolls the highlighted option into view when:
// * the highlighted option changes
// * the menu opens
useEffect(() => {
const { current: listBox } = listBoxRef
const { current: listBox } = ref
const highlightedOption = expanded
? listBox.childNodes[focussedOptionIndex]
: null
Expand All @@ -41,11 +42,11 @@ export function MenuOptionsList({
highlightedOption.scrollIntoView()
}
}
}, [expanded, focussedOptionIndex])
}, [expanded, focussedOptionIndex, ref])

return (
<div
ref={listBoxRef}
ref={ref}
role="listbox"
id={`${idPrefix}-listbox`}
aria-labelledby={labelledBy}
Expand Down Expand Up @@ -84,14 +85,14 @@ export function MenuOptionsList({
)}
</div>
)
}
})

MenuOptionsList.propTypes = {
comboBoxId: PropTypes.string.isRequired,
expanded: PropTypes.bool.isRequired,
focussedOptionIndex: PropTypes.number.isRequired,
idPrefix: PropTypes.string.isRequired,
options: optionsProp.isRequired,
options: PropTypes.arrayOf(optionProp).isRequired,
onChange: PropTypes.func.isRequired,
dataTest: PropTypes.string,
disabled: PropTypes.bool,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import { Popper } from '@dhis2-ui/popper'
import cx from 'classnames'
import PropTypes from 'prop-types'
import React, { useEffect, useState } from 'react'
import { optionProp } from '../shared-prop-types.js'
import { MenuFilter } from './menu-filter.js'
import { MenuLoading } from './menu-loading.js'
import { MenuOptionsList } from './menu-options-list.js'
import { optionsProp } from './shared-prop-types.js'

export function Menu({
comboBoxId,
Expand All @@ -24,6 +24,7 @@ export function Menu({
filterable,
hidden,
labelledBy,
listBoxRef,
loading,
loadingText,
maxHeight,
Expand Down Expand Up @@ -66,6 +67,7 @@ export function Menu({
{!options.length && <div className="empty-container">{empty}</div>}

<MenuOptionsList
ref={listBoxRef}
comboBoxId={comboBoxId}
dataTest={`${dataTestPrefix}-list`}
disabled={disabled}
Expand All @@ -91,6 +93,9 @@ export function Menu({
border: 1px solid ${colors.grey200};
border-radius: 3px;
box-shadow: ${elevations.e300};
/* We want the provided height to be exact */
box-sizing: content-box;
}
.hidden {
Expand Down Expand Up @@ -125,7 +130,10 @@ Menu.propTypes = {
comboBoxId: PropTypes.string.isRequired,
focussedOptionIndex: PropTypes.number.isRequired,
idPrefix: PropTypes.string.isRequired,
options: optionsProp.isRequired,
listBoxRef: PropTypes.shape({
current: PropTypes.instanceOf(HTMLElement),
}).isRequired,
options: PropTypes.arrayOf(optionProp).isRequired,
onChange: PropTypes.func.isRequired,
dataTest: PropTypes.string,
disabled: PropTypes.bool,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ function DefaultStyle({ label, disabled, highlighted }) {
color: ${colors.grey900};
padding-block: ${spacers.dp8};
padding-inline: ${spacers.dp12};
line-height: ${spacers.dp16};
}
span:hover {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { SelectedValue } from './selected-value.js'
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { colors, theme } from '@dhis2/ui-constants'
import { Tooltip } from '@dhis2-ui/tooltip'
import PropTypes from 'prop-types'
import React from 'react'
import i18n from '../locales/index.js'
import i18n from '../../locales/index.js'

export const ClearButton = ({ onClear, clearText, dataTest }) => (
<button
Expand Down
14 changes: 6 additions & 8 deletions components/select/src/single-select-a11y/shared-prop-types.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import PropTypes from 'prop-types'

export const optionsProp = PropTypes.arrayOf(
PropTypes.shape({
label: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
component: PropTypes.elementType,
disabled: PropTypes.bool,
})
)
export const optionProp = PropTypes.shape({
label: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
component: PropTypes.elementType,
disabled: PropTypes.bool,
})
16 changes: 9 additions & 7 deletions components/select/src/single-select-a11y/single-select-a11y.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import { sharedPropTypes } from '@dhis2/ui-constants'
import cx from 'classnames'
import PropTypes from 'prop-types'
import React, { useCallback, useRef, useState } from 'react'
import { Menu } from './menu.js'
import { SelectedValue } from './selected-value.js'
import { optionsProp } from './shared-prop-types.js'
import { useHandleKeyPress } from './use-handle-key-press.js'
import { Menu } from './menu/index.js'
import { SelectedValue } from './selected-value/index.js'
import { optionProp } from './shared-prop-types.js'
import { useHandleKeyPress } from './use-handle-key-press/index.js'

export function SingleSelectA11y({
options,
Expand All @@ -30,7 +30,7 @@ export function SingleSelectA11y({
labelledBy = '',
loading = false,
menuLoadingText = '',
menuMaxHeight = '280px',
menuMaxHeight = '288px',
noMatchText = '',
placeholder = '',
prefix = '',
Expand Down Expand Up @@ -68,6 +68,7 @@ export function SingleSelectA11y({

// Using `useState` here so components get notified when the value changes (from null -> div)
const comboBoxRef = useRef()
const listBoxRef = useRef()
const [focussedOptionIndex, setFocussedOptionIndex] = useState(0)
const [selectRef, setSelectRef] = useState()
const [expanded, setExpanded] = useState(false)
Expand Down Expand Up @@ -105,6 +106,7 @@ export function SingleSelectA11y({
options,
openMenu,
closeMenu,
listBoxRef,
focussedOptionIndex,
setFocussedOptionIndex,
selectFocussedOption,
Expand Down Expand Up @@ -141,7 +143,6 @@ export function SingleSelectA11y({
disabled={disabled}
error={error}
expanded={expanded}
handleKeyPress={handleKeyPress}
hasSelection={!!value}
idPrefix={idPrefix}
labelledBy={labelledBy}
Expand Down Expand Up @@ -172,6 +173,7 @@ export function SingleSelectA11y({
hidden={!expanded}
idPrefix={idPrefix}
labelledBy={labelledBy}
listBoxRef={listBoxRef}
loading={loading}
loadingText={menuLoadingText}
maxHeight={menuMaxHeight}
Expand All @@ -196,7 +198,7 @@ SingleSelectA11y.propTypes = {
idPrefix: PropTypes.string.isRequired,

/** An array of options **/
options: optionsProp.isRequired,
options: PropTypes.arrayOf(optionProp).isRequired,

/** As of now, this component does not support being uncontrolled */
value: PropTypes.string.isRequired,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -427,20 +427,35 @@ export const WithOptionsAndLoadingText = () => {
}

export const WithManyOptions = () => {
const [value, setValue] = useState('art_entry_point:_no_pmtct')
// const [value, setValue] = useState('art_entry_point:_no_pmtct')
const [value, setValue] = useState('10')
const selectOptions = Array.apply(null, Array(100)).map((x, i) => i)

return (
<SingleSelectA11y
idPrefix="a11y"
value={value}
valueLabel={
value
? options.find((option) => option.value === value).label
: ''
}
onChange={(nextValue) => setValue(nextValue)}
options={options}
/>
<>
<SingleSelectA11y
idPrefix="a11y"
value={value}
// valueLabel={
// value
// ? options.find((option) => option.value === value).label
// : ''
// }
onChange={(nextValue) => setValue(nextValue)}
options={selectOptions.map((i) => ({
value: i.toString(),
label: `Select option ${i + 1}`,
}))}
/>

<select onChange={(e) => console.log('> onChange', e)}>
{selectOptions.map((i) => (
<option key={i} value={i}>
Select option {i + 1}
</option>
))}
</select>
</>
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { useHandleKeyPress } from './use-handle-key-press.js'
Loading

0 comments on commit af8a222

Please sign in to comment.