Skip to content

Commit

Permalink
feat: add attributes to input and wrapper component
Browse files Browse the repository at this point in the history
add aria-activedescendent attribute to input and aria-live=”assertive” to wrapper component
  • Loading branch information
httpsmenahassan committed Aug 25, 2023
1 parent 79f0cb4 commit f557bc6
Showing 1 changed file with 48 additions and 24 deletions.
72 changes: 48 additions & 24 deletions src/Form/FormAutosuggest.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import React, {
useEffect, useState,
} from 'react';
import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import { useIntl } from 'react-intl';
import { v4 as uuidv4 } from 'uuid';
import { KeyboardArrowUp, KeyboardArrowDown } from '../../icons';
import Icon from '../Icon';
import FormGroup from './FormGroup';
Expand Down Expand Up @@ -37,6 +36,11 @@ function FormAutosuggest({
errorMessage: '',
dropDownItems: [],
});
const [activeMenuItemId, setActiveMenuItemId] = useState(null);

const handleMenuItemFocus = (menuItemId) => {
setActiveMenuItemId(menuItemId);

Check warning on line 42 in src/Form/FormAutosuggest.jsx

View check run for this annotation

Codecov / codecov/patch

src/Form/FormAutosuggest.jsx#L42

Added line #L42 was not covered by tests
};

const handleItemClick = (e, onClick) => {
const clickedValue = e.currentTarget.getAttribute('data-value');
Expand All @@ -45,7 +49,7 @@ function FormAutosuggest({
onSelected(clickedValue);
}

setState(prevState => ({
setState((prevState) => ({
...prevState,
dropDownItems: [],
displayValue: clickedValue,
Expand All @@ -62,18 +66,23 @@ function FormAutosuggest({
let childrenOpt = React.Children.map(children, (child) => {
// eslint-disable-next-line no-shadow
const { children, onClick, ...rest } = child.props;
// Generate a unique ID for each menu item
const menuItemId = uuidv4();

return React.cloneElement(child, {
...rest,
children,
'data-value': children,
onClick: (e) => handleItemClick(e, onClick),
// set ID for the option
id: menuItemId,
// set onFocus behavior
onFocus: () => handleMenuItemFocus(menuItemId),

Check warning on line 80 in src/Form/FormAutosuggest.jsx

View check run for this annotation

Codecov / codecov/patch

src/Form/FormAutosuggest.jsx#L80

Added line #L80 was not covered by tests
});
});

if (strToFind.length > 0) {
childrenOpt = childrenOpt
.filter((opt) => (opt.props.children.toLowerCase().includes(strToFind.toLowerCase())));
childrenOpt = childrenOpt.filter((opt) => opt.props.children.toLowerCase().includes(strToFind.toLowerCase()));
}

return childrenOpt;
Expand All @@ -91,7 +100,7 @@ function FormAutosuggest({
newState.errorMessage = '';
}

setState(prevState => ({
setState((prevState) => ({
...prevState,
...newState,
}));
Expand All @@ -104,16 +113,18 @@ function FormAutosuggest({
iconAs={Icon}
size="sm"
variant="secondary"
alt={isMenuClosed
? intl.formatMessage(messages.iconButtonOpened)
: intl.formatMessage(messages.iconButtonClosed)}
alt={
isMenuClosed
? intl.formatMessage(messages.iconButtonOpened)
: intl.formatMessage(messages.iconButtonClosed)
}
onClick={(e) => handleExpand(e, isMenuClosed)}
/>
);

const handleClickOutside = (e) => {
if (parentRef.current && !parentRef.current.contains(e.target) && state.dropDownItems.length > 0) {
setState(prevState => ({
setState((prevState) => ({
...prevState,
dropDownItems: [],
errorMessage: !state.displayValue ? errorMessageText : '',
Expand All @@ -123,11 +134,11 @@ function FormAutosuggest({
}
};

const keyDownHandler = e => {
const keyDownHandler = (e) => {
if (e.key === 'Escape') {
e.preventDefault();

setState(prevState => ({
setState((prevState) => ({

Check warning on line 141 in src/Form/FormAutosuggest.jsx

View check run for this annotation

Codecov / codecov/patch

src/Form/FormAutosuggest.jsx#L141

Added line #L141 was not covered by tests
...prevState,
dropDownItems: [],
errorMessage: !state.displayValue ? errorMessageText : '',
Expand All @@ -149,7 +160,7 @@ function FormAutosuggest({

useEffect(() => {
if (value || value === '') {
setState(prevState => ({
setState((prevState) => ({
...prevState,
displayValue: value,
}));
Expand All @@ -159,14 +170,14 @@ function FormAutosuggest({
const setDisplayValue = (itemValue) => {
const optValue = [];

children.forEach(opt => {
children.forEach((opt) => {
optValue.push(opt.props.children);
});

const normalized = itemValue.toLowerCase();
const opt = optValue.find((o) => o.toLowerCase() === normalized);

setState(prevState => ({
setState((prevState) => ({
...prevState,
displayValue: opt || itemValue,
}));
Expand All @@ -176,7 +187,7 @@ function FormAutosuggest({
const dropDownItems = getItems(e.target.value);

if (dropDownItems.length > 1) {
setState(prevState => ({
setState((prevState) => ({
...prevState,
dropDownItems,
errorMessage: '',
Expand All @@ -189,19 +200,21 @@ function FormAutosuggest({
const handleOnChange = (e) => {
const findStr = e.target.value;

if (onChange) { onChange(findStr); }
if (onChange) {
onChange(findStr);

Check warning on line 204 in src/Form/FormAutosuggest.jsx

View check run for this annotation

Codecov / codecov/patch

src/Form/FormAutosuggest.jsx#L204

Added line #L204 was not covered by tests
}

if (findStr.length) {
const filteredItems = getItems(findStr);
setState(prevState => ({
setState((prevState) => ({
...prevState,
dropDownItems: filteredItems,
errorMessage: '',
}));

setIsMenuClosed(false);
} else {
setState(prevState => ({
setState((prevState) => ({
...prevState,
dropDownItems: [],
errorMessageText,
Expand All @@ -215,6 +228,9 @@ function FormAutosuggest({

return (
<div className="pgn__form-autosuggest__wrapper" ref={parentRef}>
<div aria-live="assertive" className="sr-only">
{`${state.dropDownItems.length} options found`}
</div>
<FormGroup isInvalid={!!state.errorMessage}>
<FormControl
aria-expanded={(state.dropDownItems.length > 0).toString()}
Expand All @@ -224,6 +240,7 @@ function FormAutosuggest({
autoComplete="off"
value={state.displayValue}
aria-invalid={state.errorMessage}
aria-activedescendant={activeMenuItemId}
onChange={handleOnChange}
onClick={handleClick}
trailingElement={iconToggle}
Expand All @@ -250,16 +267,23 @@ function FormAutosuggest({
>
{isLoading ? (
<div className="pgn__form-autosuggest__dropdown-loading">
<Spinner animation="border" variant="dark" screenReaderText={screenReaderText} />
<Spinner
animation="border"
variant="dark"
screenReaderText={screenReaderText}
/>
</div>
) : state.dropDownItems.length > 0 && state.dropDownItems}
) : (
state.dropDownItems.length > 0 && state.dropDownItems
)}
</ul>
</div>
);
}

FormAutosuggest.defaultProps = {
arrowKeyNavigationSelector: 'a:not(:disabled),li:not(:disabled, .btn-icon),input:not(:disabled)',
arrowKeyNavigationSelector:
'a:not(:disabled),li:not(:disabled, .btn-icon),input:not(:disabled)',
ignoredArrowKeysNames: ['ArrowRight', 'ArrowLeft'],
isLoading: false,
className: null,
Expand All @@ -280,7 +304,7 @@ FormAutosuggest.propTypes = {
/**
* Specifies the CSS selector string that indicates to which elements
* the user can navigate using the arrow keys
*/
*/
arrowKeyNavigationSelector: PropTypes.string,
/** Specifies ignored hook keys. */
ignoredArrowKeysNames: PropTypes.arrayOf(PropTypes.string),
Expand Down

0 comments on commit f557bc6

Please sign in to comment.