Skip to content

Commit

Permalink
feat: add keyword navigability to transfer, happy path
Browse files Browse the repository at this point in the history
  • Loading branch information
Flaminia Cavallo authored and Flaminia Cavallo committed Jul 18, 2024
1 parent fc658b0 commit 67ee3a9
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 11 deletions.
35 changes: 24 additions & 11 deletions components/transfer/src/options-container.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { CircularLoader } from '@dhis2-ui/loader'
import { spacers } from '@dhis2/ui-constants'
import PropTypes from 'prop-types'
import React, { Fragment, useRef } from 'react'
import React, { useRef } from 'react'
import { EndIntersectionDetector } from './end-intersection-detector.js'
import { useOptionsNavigation } from './use-options-navigation.js'
import { useResizeCounter } from './use-resize-counter.js'

export const OptionsContainer = ({
Expand All @@ -19,7 +20,9 @@ export const OptionsContainer = ({
toggleHighlightedOption,
}) => {
const optionsRef = useRef(null)
const wrapperRef = useRef(null)
const { wrapperRef, focusedOptionIndex, onOptionFocusedHandler } =
useOptionsNavigation(options)

const resizeCounter = useResizeCounter(wrapperRef.current)

return (
Expand All @@ -29,29 +32,39 @@ export const OptionsContainer = ({
<CircularLoader />
</div>
)}

<div className="container" data-test={dataTest} ref={optionsRef}>
<div className="content-container" ref={wrapperRef}>
{!options.length && emptyComponent}
{options.map((option) => {
{options.map((option, index) => {
const highlighted = !!highlightedOptions.find(
(highlightedSourceOption) =>
highlightedSourceOption === option.value
)

const optionsClickHandlers = getOptionClickHandlers(
option,
selectionHandler,
toggleHighlightedOption
)

const isFocused =
focusedOptionIndex !== null
? index === focusedOptionIndex
: index === 0

return (
<Fragment key={option.value}>
<span
key={option.value}
tabIndex={isFocused ? 0 : -1}
onFocus={() => onOptionFocusedHandler(index)}
>
{renderOption({
...option,
...getOptionClickHandlers(
option,
selectionHandler,
toggleHighlightedOption
),
...optionsClickHandlers,
highlighted,
selected,
})}
</Fragment>
</span>
)
})}

Expand Down
99 changes: 99 additions & 0 deletions components/transfer/src/use-options-navigation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { useEffect, useRef, useState } from 'react'

export const useOptionsNavigation = (options) => {
const wrapperRef = useRef(null)

const [focusedOptionIndex, setFocusedOptionIndex] = useState(null)

const handleKeyDown = (event) => {
switch (event.key) {
case 'ArrowUp':
event.preventDefault()
const newUpFocusIndex =

Check failure on line 12 in components/transfer/src/use-options-navigation.js

View workflow job for this annotation

GitHub Actions / lint

Unexpected lexical declaration in case block
focusedOptionIndex > 0
? focusedOptionIndex - 1
: options.length - 1
setFocusedOptionIndex(newUpFocusIndex)
if (event.shiftKey) {
// TODO: this is very ugly!
const clickEvent = new MouseEvent('click', {
bubbles: true,
cancelable: true,
shiftKey: true,
})
wrapperRef.current.children[
newUpFocusIndex
].children[0].dispatchEvent(clickEvent)
}

break
case 'ArrowDown':
event.preventDefault()
const newDownFocusIndex =

Check failure on line 32 in components/transfer/src/use-options-navigation.js

View workflow job for this annotation

GitHub Actions / lint

Unexpected lexical declaration in case block
focusedOptionIndex >= options.length - 1
? 0
: focusedOptionIndex + 1
setFocusedOptionIndex(newDownFocusIndex)
if (event.shiftKey) {
// TODO: this is very ugly!
const clickEvent = new MouseEvent('click', {
bubbles: true,
cancelable: true,
shiftKey: true,
})
wrapperRef.current.children[
newDownFocusIndex
].children[0].dispatchEvent(clickEvent)
}
break
case ' ':
event.preventDefault()
if (wrapperRef && wrapperRef.current.children.length > 0) {
// TODO: this is ugly!
wrapperRef.current.children[
focusedOptionIndex
].children[0].click()
}
break
default:
break
}
}

const onOptionFocusedHandler = (optionIndex) => {
setFocusedOptionIndex(optionIndex)
}

useEffect(() => {
if (wrapperRef && wrapperRef.current.children.length > 0) {
if (
focusedOptionIndex !== null &&
wrapperRef.current.children[focusedOptionIndex]
) {
wrapperRef.current.children[focusedOptionIndex]?.focus()
}
}
}, [wrapperRef, focusedOptionIndex, options])

useEffect(() => {
if (options && focusedOptionIndex >= options.length) {
setFocusedOptionIndex(null)
}
}, [options])

useEffect(() => {
if (wrapperRef?.current) {
wrapperRef.current.addEventListener('keydown', handleKeyDown)

return () => {
wrapperRef.current.removeEventListener('keydown', handleKeyDown)
}
}
}, [handleKeyDown, wrapperRef])

return {
wrapperRef,
onOptionFocusedHandler,
focusedOptionIndex,
}
}

0 comments on commit 67ee3a9

Please sign in to comment.