Skip to content

Commit

Permalink
feat(Autocomplete): Focus toggle button
Browse files Browse the repository at this point in the history
Required so that the user can select a new option just by using the keyboard
  • Loading branch information
thyhjwb6 committed Apr 27, 2022
1 parent 6d122c1 commit 381a3a1
Show file tree
Hide file tree
Showing 6 changed files with 86 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ export default {
label: '[data-testid="select-label"]',
option: '[data-testid="select-option"]',
outerWrapper: '[data-testid="select-outer-wrapper"]',
toggleButton: '[data-testid="select-arrow-button"]',
tooltip: '[data-testid="floating-box"]',
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ describe('Autocomplete', () => {
cy.get(selectors.select.input).should('have.value', 'Three')
})

it('focuses the toggle button', () => {
cy.get(selectors.select.toggleButton).should('have.focus')
})

describe('and the component is focused', () => {
beforeEach(() => {
cy.get(selectors.select.outerWrapper).click()
Expand Down Expand Up @@ -91,6 +95,26 @@ describe('Autocomplete', () => {
})
})

describe('and the user escapes out', () => {
beforeEach(() => {
cy.get(selectors.select.input).type('{esc}')
})

it('focuses the toggle button', () => {
cy.get(selectors.select.toggleButton).should('have.focus')
})

describe('and the down arrow is pressed', () => {
beforeEach(() => {
cy.get(selectors.select.toggleButton).type('{downArrow}')
})

it('renders four options', () => {
cy.get(selectors.select.option).should('have.length', 4)
})
})
})

describe('and the user clicks out', () => {
beforeEach(() => {
cy.get('body').click()
Expand Down Expand Up @@ -149,6 +173,23 @@ describe('Autocomplete', () => {
})
})

describe('and the down arrow is pressed', () => {
beforeEach(() => {
cy.get(selectors.select.input).type('{downArrow}')
})

it('highlights the first item', () => {
const options = cy.get(selectors.select.option)

options.should(($option) => {
expect($option.eq(0), 'option 1').to.have.css(
'background-color',
hexToRgb(ColorNeutral100)
)
})
})
})

describe('and `*` is typed', () => {
beforeEach(() => {
cy.get(selectors.select.input).type('*')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,10 @@ describe('Autocomplete', () => {
expect(onChangeSpy).toHaveBeenCalledWith('two')
})

it('focuses the toggle button', () => {
expect(wrapper.getByTestId('select-arrow-button')).toHaveFocus()
})

describe('and the clear button is clicked', () => {
beforeEach(() => {
userEvent.click(wrapper.getByTestId('select-clear-button'))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,12 @@ export const Autocomplete: React.FC<AutocompleteProps> = ({
}) => {
const { hasError, inputRef, items, onInputValueChange, onIsOpenChange } =
useAutocomplete(React.Children.toArray(children), isInvalid)
const { onToggleButtonKeyDownHandler } = useToggleButton(inputRef)
const {
buttonRef,
focusToggleButton,
onInputEscapeKeyHandler,
onToggleButtonKeyDownHandler,
} = useToggleButton(inputRef)
const id = useExternalId('autocomplete', externalId)

const {
Expand Down Expand Up @@ -61,10 +66,12 @@ export const Autocomplete: React.FC<AutocompleteProps> = ({
if (onChange) {
onChange(React.isValidElement(newItem) ? newItem.props.value : null)
}

focusToggleButton()
},
})

const { onInputBlurHandler, onInputKeyDownHandler } = useHighlightedIndex(
const { onInputBlurHandler, onInputTabKeyHandler } = useHighlightedIndex(
highlightedIndex,
inputValue,
isOpen,
Expand All @@ -87,7 +94,10 @@ export const Autocomplete: React.FC<AutocompleteProps> = ({
inputProps={getInputProps({
onBlur: onInputBlurHandler,
onFocus: onInputFocusHandler,
onKeyDown: onInputKeyDownHandler,
onKeyDown: (e: React.KeyboardEvent<HTMLInputElement>) => {
onInputTabKeyHandler(e)
onInputEscapeKeyHandler(e)
},
onMouseDown: onInputMouseDownHandler,
ref: inputRef,
})}
Expand All @@ -102,6 +112,7 @@ export const Autocomplete: React.FC<AutocompleteProps> = ({
onKeyDown: (e: React.KeyboardEvent<HTMLButtonElement>) => {
onToggleButtonKeyDownHandler(e)
},
ref: buttonRef,
})}
value={selectedItem ? itemToString(selectedItem) : ''}
{...rest}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { useCallback, useEffect } from 'react'

import { findIndexOfInputValue } from '../helpers'
import { SelectChildWithStringType } from '../../SelectBase'

Expand All @@ -11,7 +12,7 @@ export function useHighlightedIndex(
setInputValue: (value: string) => void
): {
onInputBlurHandler: () => void
onInputKeyDownHandler: (e: React.KeyboardEvent<HTMLInputElement>) => void
onInputTabKeyHandler: (e: React.KeyboardEvent<HTMLInputElement>) => void
} {
useEffect(() => {
if (inputValue && highlightedIndex < 0) {
Expand All @@ -32,7 +33,7 @@ export function useHighlightedIndex(
setHighlightedIndex,
])

const onInputKeyDownHandler = useCallback(
const onInputTabKeyHandler = useCallback(
(e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.code === 'Tab' && highlightedIndex !== -1) {
const item = React.Children.toArray(items)[highlightedIndex]
Expand All @@ -46,6 +47,6 @@ export function useHighlightedIndex(

return {
onInputBlurHandler,
onInputKeyDownHandler,
onInputTabKeyHandler,
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,28 @@
import React, { useCallback } from 'react'
import React, { useCallback, useRef } from 'react'

export function useToggleButton(inputRef: React.RefObject<HTMLInputElement>): {
buttonRef: React.RefObject<HTMLButtonElement>
focusToggleButton: () => void
onInputEscapeKeyHandler: (e: React.KeyboardEvent<HTMLInputElement>) => void
onToggleButtonKeyDownHandler: (
e: React.KeyboardEvent<HTMLButtonElement>
) => void
} {
const buttonRef = useRef<HTMLButtonElement>(null)

function focusToggleButton() {
buttonRef.current?.focus()
}

const onInputEscapeKeyHandler = useCallback(
(e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.code === 'Escape') {
buttonRef.current?.focus()
}
},
[]
)

const onToggleButtonKeyDownHandler = useCallback(
(e: React.KeyboardEvent<HTMLButtonElement>) => {
if (e.code === 'ArrowDown') {
Expand All @@ -15,6 +33,9 @@ export function useToggleButton(inputRef: React.RefObject<HTMLInputElement>): {
)

return {
buttonRef,
focusToggleButton,
onInputEscapeKeyHandler,
onToggleButtonKeyDownHandler,
}
}

0 comments on commit 381a3a1

Please sign in to comment.