Skip to content
This repository has been archived by the owner on May 24, 2024. It is now read-only.

[Terra-List] Added prop to Disable Tab Key Navigation for List Item #3944

Merged
merged 9 commits into from
Oct 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/terra-core-docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Unreleased

* Updated
* Updated `terra-list` examples to include `isTabFocusDisabled` prop.
* Removed `terra-table` as a dependency as the docs have now moved to `terra-framework-docs`.

## 1.45.0 - (October 16, 2023)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ class SingleSelectList extends React.Component {
super(props);
this.createListItem = this.createListItem.bind(this);
this.handleItemSelection = this.handleItemSelection.bind(this);
this.handleOnClick = this.handleOnClick.bind(this);
this.setListNode = this.setListNode.bind(this);
this.state = { selectedKey: null };
}

Expand All @@ -22,29 +24,48 @@ class SingleSelectList extends React.Component {
}
}

createListItem(itemData) {
handleOnClick() {
this.firstListItem.focus();
}

setListNode(element) {
this.firstListItem = element;
}

createListItem(itemData, index) {
const listItemRef = (index === 0) ? this.setListNode : null;
return (
<Item
key={itemData.key}
isSelectable
isSelected={this.state.selectedKey === itemData.key}
metaData={{ key: itemData.key }}
onSelect={this.handleItemSelection}
refCallback={listItemRef}
>
<Placeholder title={itemData.title} className={cx('placeholder')} />
</Item>
);
}

createListItems(data) {
return data.map(childItem => this.createListItem(childItem));
return data.map((childItem, index) => this.createListItem(childItem, index));
}

render() {
return (
<List dividerStyle="standard" ariaSelectionStyle="single-select">
{this.createListItems(mockData)}
</List>
<div>
<p>
<b>Note</b>
{' '}
Tab key Navigation is disabled for below list items. Inorder to interact with list-item user should set programmatic focus to one of the list item depending on the required scenario.
{' '}
</p>
<button type="button" onClick={this.handleOnClick}>Select Item 0</button>
<List dividerStyle="standard" ariaSelectionStyle="single-select" isTabFocusDisabled>
{this.createListItems(mockData)}
</List>
</div>
);
}
}
Expand Down
3 changes: 3 additions & 0 deletions packages/terra-list/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## Unreleased

* Added
* Added `isTabFocusDisabled` prop to disable tab key navigation.

* Fixed
* Fixed _property is undefined_ error while navigating with a keyboard.

Expand Down
24 changes: 19 additions & 5 deletions packages/terra-list/src/List.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ const propTypes = {
* Whether or not the list item is draggable. List Item is draggable only when it is selectable.
*/
isDraggable: PropTypes.bool,
/**
* ![IMPORTANT](https://badgen.net/badge/UX/Accessibility/blue)
* Whether or not the list item is focusable with Tab key. Ensure alternative way of focusing list item when set to true for best accessibility experience.
*/
isTabFocusDisabled: PropTypes.bool,
/**
* Function callback when the Item is dropped. Parameters:
* @param {Object} result result
Expand All @@ -93,11 +98,12 @@ const propTypes = {
};

const defaultProps = {
ariaSelectionStyle: 'none',
children: [],
dividerStyle: 'none',
isTabFocusDisabled: false,
paddingStyle: 'none',
role: 'none',
ariaSelectionStyle: 'none',
};

const List = ({
Expand All @@ -112,6 +118,7 @@ const List = ({
role,
ariaSelectionStyle,
isDraggable,
isTabFocusDisabled,
onDragEnd,
...customProps
}) => {
Expand Down Expand Up @@ -238,24 +245,30 @@ const List = ({

const cloneListItem = (ListItem, provider) => React.cloneElement(ListItem, {
isDraggable: ListItem?.props?.isSelectable,
isTabFocusDisabled,
refCallback: provider.innerRef,
...provider.draggableProps,
...provider.dragHandleProps,
});

const clone = (object) => React.Children.map(object, (listitem) => React.cloneElement(listitem, {
isTabFocusDisabled,
}));

const renderListDom = () => (
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/role-supports-aria-props
<ul
{...customProps}
{...attrSpread}
aria-describedby={ariaDescribedBy}
aria-description={ariaDescription} // eslint-disable-line jsx-a11y/aria-props
// eslint-disable-next-line jsx-a11y/aria-props
aria-description={ariaDescription}
aria-details={ariaDetails}
className={listClassNames}
ref={handleListRef}
onKeyDown={handleKeyDown}
>
{children}
{clone(children)}
</ul>
);

Expand All @@ -268,13 +281,14 @@ const List = ({
)}
>
{(provided) => (
// eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/role-supports-aria-props
/* eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions, jsx-a11y/role-supports-aria-props */
<ul
{...provided.droppableProps}
{...customProps}
{...attrSpread}
aria-describedby={ariaDescribedBy}
aria-description={ariaDescription} // eslint-disable-line jsx-a11y/aria-props
// eslint-disable-next-line jsx-a11y/aria-props
aria-description={ariaDescription}
aria-details={ariaDetails}
className={listClassNames}
ref={(refobj) => {
Expand Down
9 changes: 7 additions & 2 deletions packages/terra-list/src/ListItem.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable no-param-reassign */
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
Expand Down Expand Up @@ -99,7 +100,8 @@ const ListItem = ({
),
customProps.className,
);
const { isDraggable } = customProps;
const { isDraggable, isTabFocusDisabled } = customProps;

const attrSpread = {};

const onFocusResponse = intl.formatMessage({ id: 'Terra.list.focus' });
Expand All @@ -108,7 +110,7 @@ const ListItem = ({
if (isSelectable) {
attrSpread.onClick = ListUtils.wrappedOnClickForItem(onClick, onSelect, metaData);
attrSpread.onKeyDown = ListUtils.wrappedOnKeyDownForItem(onKeyDown, onSelect, metaData);
attrSpread.tabIndex = '0';
attrSpread.tabIndex = (isTabFocusDisabled) ? '-1' : '0';
attrSpread.role = 'option';
attrSpread['aria-selected'] = isSelected;
attrSpread['data-item-show-focus'] = 'true';
Expand All @@ -119,6 +121,9 @@ const ListItem = ({
attrSpread['aria-describedby'] = responseId;
}

delete customProps?.isTabFocusDisabled;
delete customProps?.isDraggable;

return (
<li {...customProps} {...attrSpread} className={listItemClassNames} ref={refCallback}>
{(isDraggable) && (
Expand Down
11 changes: 11 additions & 0 deletions packages/terra-list/tests/jest/ListItem.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,17 @@ it('should render with callback functions', () => {
expect(mockCallBack.mock.calls.length).toEqual(3);
});

it('should render with tabindex -1 when isTabFocusDisabled is set to true', () => {
const mockCallBack = jest.fn();

const shallowComponent = shallowWithIntl(
<ListItem title="test" isSelectable isTabFocusDisabled onSelect={mockCallBack} refCallback={jest.fn()} />,
).dive();

expect(shallowComponent.prop('tabIndex')).toContain('-1');
expect(shallowComponent).toMatchSnapshot();
});

it('correctly applies the theme context className', () => {
const wrapper = mountWithIntl(
<ThemeContextProvider theme={{ className: 'orion-fusion-theme' }}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ exports[`should render List with items 1`] = `
}
}
isDraggable={true}
isTabFocusDisabled={false}
paddingStyle="none"
role="none"
>
Expand Down Expand Up @@ -219,6 +220,7 @@ exports[`should render List with items 1`] = `
draggable={false}
isDraggable={true}
isSelectable={true}
isTabFocusDisabled={false}
key="1"
onDragStart={[Function]}
onTransitionEnd={null}
Expand Down Expand Up @@ -270,6 +272,7 @@ exports[`should render List with items 1`] = `
isDraggable={true}
isSelectable={true}
isSelected={false}
isTabFocusDisabled={false}
onDragStart={[Function]}
onTransitionEnd={null}
refCallback={[Function]}
Expand All @@ -292,7 +295,6 @@ exports[`should render List with items 1`] = `
data-rbd-draggable-context-id="0"
data-rbd-draggable-id="1"
draggable={false}
isDraggable={true}
onBlur={[Function]}
onDragStart={[Function]}
onMouseDown={[Function]}
Expand Down Expand Up @@ -467,6 +469,7 @@ exports[`should render List with items 1`] = `
draggable={false}
isDraggable={true}
isSelectable={true}
isTabFocusDisabled={false}
key="2"
onDragStart={[Function]}
onTransitionEnd={null}
Expand Down Expand Up @@ -518,6 +521,7 @@ exports[`should render List with items 1`] = `
isDraggable={true}
isSelectable={true}
isSelected={false}
isTabFocusDisabled={false}
onDragStart={[Function]}
onTransitionEnd={null}
refCallback={[Function]}
Expand All @@ -540,7 +544,6 @@ exports[`should render List with items 1`] = `
data-rbd-draggable-context-id="0"
data-rbd-draggable-id="2"
draggable={false}
isDraggable={true}
onBlur={[Function]}
onDragStart={[Function]}
onMouseDown={[Function]}
Expand Down Expand Up @@ -674,15 +677,18 @@ exports[`should render List without Draggable items 1`] = `
>
<InjectIntl(ListItem)
isSelectable={true}
key="123"
isTabFocusDisabled={false}
key=".$123"
/>
<InjectIntl(ListItem)
isSelectable={true}
key="124"
isTabFocusDisabled={false}
key=".$124"
/>
<InjectIntl(ListItem)
isSelectable={true}
key="125"
isTabFocusDisabled={false}
key=".$125"
/>
</ul>
`;
Expand Down Expand Up @@ -1005,7 +1011,6 @@ exports[`should render Section with items 1`] = `
data-rbd-draggable-context-id="1"
data-rbd-draggable-id="123"
draggable={false}
isDraggable={true}
onBlur={[Function]}
onDragStart={[Function]}
onMouseDown={[Function]}
Expand Down Expand Up @@ -1253,7 +1258,6 @@ exports[`should render Section with items 1`] = `
data-rbd-draggable-context-id="1"
data-rbd-draggable-id="124"
draggable={false}
isDraggable={true}
onBlur={[Function]}
onDragStart={[Function]}
onMouseDown={[Function]}
Expand Down Expand Up @@ -1699,7 +1703,6 @@ exports[`should render subsection with items 1`] = `
data-rbd-draggable-context-id="2"
data-rbd-draggable-id="13"
draggable={false}
isDraggable={true}
onBlur={[Function]}
onDragStart={[Function]}
onMouseDown={[Function]}
Expand Down Expand Up @@ -1947,7 +1950,6 @@ exports[`should render subsection with items 1`] = `
data-rbd-draggable-context-id="2"
data-rbd-draggable-id="14"
draggable={false}
isDraggable={true}
onBlur={[Function]}
onDragStart={[Function]}
onMouseDown={[Function]}
Expand Down Expand Up @@ -2195,7 +2197,6 @@ exports[`should render subsection with items 1`] = `
data-rbd-draggable-context-id="2"
data-rbd-draggable-id="15"
draggable={false}
isDraggable={true}
onBlur={[Function]}
onDragStart={[Function]}
onMouseDown={[Function]}
Expand Down
Loading
Loading