diff --git a/packages/react/src/components/Tab/Tab-test.js b/packages/react/src/components/Tab/Tab-test.js
index fdd59ac083c4..b7d526f8b9c1 100644
--- a/packages/react/src/components/Tab/Tab-test.js
+++ b/packages/react/src/components/Tab/Tab-test.js
@@ -101,21 +101,18 @@ describe('Tab', () => {
describe('keydown', () => {
const onKeyDown = jest.fn();
- const handleTabAnchorFocus = jest.fn();
const handleTabKeyDown = jest.fn();
const wrapper = shallow();
- wrapper.setProps({ onKeyDown, handleTabAnchorFocus, handleTabKeyDown });
+ wrapper.setProps({ onKeyDown, handleTabKeyDown });
it('invokes onKeyDown when a function is passed to onKeyDown prop', () => {
wrapper.simulate('keyDown', { which: 38 });
expect(onKeyDown).toBeCalled();
- expect(handleTabAnchorFocus).not.toBeCalled();
});
it('invokes handleTabAnchorFocus when onKeyDown occurs for appropriate events', () => {
wrapper.simulate('keyDown', { which: 37 });
expect(onKeyDown).toBeCalled();
- expect(handleTabAnchorFocus).toBeCalled();
});
});
});
diff --git a/packages/react/src/components/Tab/Tab.js b/packages/react/src/components/Tab/Tab.js
index 5c35b034cfff..dde7ce38914a 100644
--- a/packages/react/src/components/Tab/Tab.js
+++ b/packages/react/src/components/Tab/Tab.js
@@ -26,12 +26,6 @@ export default class Tab extends React.Component {
*/
handleTabClick: PropTypes.func,
- /**
- * A handler that is invoked when a user presses left/right key.
- * Reserved for usage in Tabs
- */
- handleTabAnchorFocus: PropTypes.func,
-
/**
* A handler that is invoked on the key down event for the control.
* Reserved for usage in Tabs
@@ -108,23 +102,10 @@ export default class Tab extends React.Component {
onKeyDown: () => {},
};
- setTabFocus(evt) {
- const leftKey = 37;
- const rightKey = 39;
- if (evt.which === leftKey) {
- this.props.handleTabAnchorFocus(this.props.index - 1);
- } else if (evt.which === rightKey) {
- this.props.handleTabAnchorFocus(this.props.index + 1);
- } else {
- return;
- }
- }
-
render() {
const {
className,
handleTabClick,
- handleTabAnchorFocus, // eslint-disable-line
handleTabKeyDown,
disabled,
href,
@@ -172,7 +153,6 @@ export default class Tab extends React.Component {
if (disabled) {
return;
}
- this.setTabFocus(evt);
handleTabKeyDown(index, evt);
onKeyDown(evt);
}}
diff --git a/packages/react/src/components/Tabs/Tabs-story.js b/packages/react/src/components/Tabs/Tabs-story.js
index 1adea0976a6c..e49bdfbc7fbf 100644
--- a/packages/react/src/components/Tabs/Tabs-story.js
+++ b/packages/react/src/components/Tabs/Tabs-story.js
@@ -78,14 +78,17 @@ storiesOf('Tabs', module)
Content for second tab goes here.
+
+ Content for third tab goes here.
+
- Content for third tab goes here.
+ Content for fourth tab goes here.
}>
-
Content for fourth tab goes here.
+ Content for fifth tab goes here.
),
diff --git a/packages/react/src/components/Tabs/Tabs-test.js b/packages/react/src/components/Tabs/Tabs-test.js
index 55f5034c7032..365ae339c2a7 100644
--- a/packages/react/src/components/Tabs/Tabs-test.js
+++ b/packages/react/src/components/Tabs/Tabs-test.js
@@ -7,14 +7,25 @@
import React from 'react';
import { ChevronDownGlyph } from '@carbon/icons-react';
+import { settings } from 'carbon-components';
+import { shallow, mount } from 'enzyme';
import Tabs from '../Tabs';
import Tab from '../Tab';
import TabsSkeleton from '../Tabs/Tabs.Skeleton';
-import { shallow, mount } from 'enzyme';
-import { settings } from 'carbon-components';
const { prefix } = settings;
+window.matchMedia = jest.fn().mockImplementation(query => ({
+ matches: true,
+ media: query,
+ onchange: null,
+ addListener: jest.fn(), // deprecated
+ removeListener: jest.fn(), // deprecated
+ addEventListener: jest.fn(),
+ removeEventListener: jest.fn(),
+ dispatchEvent: jest.fn(),
+}));
+
describe('Tabs', () => {
describe('renders as expected', () => {
describe('navigation ()', () => {
@@ -241,6 +252,53 @@ describe('Tabs', () => {
expect(wrapper.state().selected).toEqual(1);
});
});
+
+ describe('ignore disabled child tab', () => {
+ let wrapper;
+ let firstTab;
+ let lastTab;
+ beforeEach(() => {
+ wrapper = mount(
+
+
+ content1
+
+
+ content2
+
+
+ content3
+
+
+ );
+ firstTab = wrapper.find('.firstTab').last();
+ lastTab = wrapper.find('.lastTab').last();
+ });
+ it('updates selected state when pressing arrow keys', () => {
+ firstTab.simulate('keydown', { which: rightKey });
+ expect(wrapper.state().selected).toEqual(2);
+ lastTab.simulate('keydown', { which: leftKey });
+ expect(wrapper.state().selected).toEqual(0);
+ });
+
+ it('loops focus and selected state from lastTab to firstTab', () => {
+ wrapper.setState({ selected: 2 });
+ lastTab.simulate('keydown', { which: rightKey });
+ expect(wrapper.state().selected).toEqual(0);
+ });
+
+ it('loops focus and selected state from firstTab to lastTab', () => {
+ firstTab.simulate('keydown', { which: leftKey });
+ expect(wrapper.state().selected).toEqual(2);
+ });
+
+ it('updates selected state when pressing space or enter key', () => {
+ firstTab.simulate('keydown', { which: spaceKey });
+ expect(wrapper.state().selected).toEqual(0);
+ lastTab.simulate('keydown', { which: enterKey });
+ expect(wrapper.state().selected).toEqual(2);
+ });
+ });
});
});
diff --git a/packages/react/src/components/Tabs/Tabs.js b/packages/react/src/components/Tabs/Tabs.js
index 00979a33b700..5b2372a8e5af 100644
--- a/packages/react/src/components/Tabs/Tabs.js
+++ b/packages/react/src/components/Tabs/Tabs.js
@@ -10,6 +10,7 @@ import React from 'react';
import classNames from 'classnames';
import { ChevronDownGlyph } from '@carbon/icons-react';
import { settings } from 'carbon-components';
+import { keys, match, matches } from '../../internal/keyboard';
const { prefix } = settings;
@@ -116,6 +117,12 @@ export default class Tabs extends React.Component {
return React.Children.map(this.props.children, tab => tab);
}
+ getEnabledTabs = () =>
+ React.Children.toArray(this.props.children).reduce(
+ (acc, tab, index) => (!tab.props.disabled ? acc.concat(index) : acc),
+ []
+ );
+
getTabAt = (index, useFresh) => {
return (
(!useFresh && this[`tab${index}`]) ||
@@ -139,35 +146,47 @@ export default class Tabs extends React.Component {
};
};
+ getDirection = evt => {
+ if (match(evt, keys.ArrowLeft)) {
+ return -1;
+ }
+ if (match(evt, keys.ArrowRight)) {
+ return 1;
+ }
+ return 0;
+ };
+
+ getNextIndex = (index, direction) => {
+ const enabledTabs = this.getEnabledTabs();
+ const nextIndex = Math.max(
+ enabledTabs.indexOf(index) + direction,
+ -1 /* For `tab` not found in `enabledTabs` */
+ );
+ const nextIndexLooped =
+ nextIndex >= 0 && nextIndex < enabledTabs.length
+ ? nextIndex
+ : nextIndex - Math.sign(nextIndex) * enabledTabs.length;
+ return enabledTabs[nextIndexLooped];
+ };
+
handleTabKeyDown = onSelectionChange => {
return (index, evt) => {
- const key = evt.key || evt.which;
-
- if (key === 'Enter' || key === 13 || key === ' ' || key === 32) {
+ if (matches(evt, [keys.Enter, keys.Space])) {
this.selectTabAt(index, onSelectionChange);
this.setState({
dropdownHidden: true,
});
}
- };
- };
-
- handleTabAnchorFocus = onSelectionChange => {
- return index => {
- const tabCount = React.Children.count(this.props.children) - 1;
- let tabIndex = index;
- if (index < 0) {
- tabIndex = tabCount;
- } else if (index > tabCount) {
- tabIndex = 0;
- }
-
- const tab = this.getTabAt(tabIndex);
- if (tab) {
- this.selectTabAt(tabIndex, onSelectionChange);
- if (tab.tabAnchor) {
- tab.tabAnchor.focus();
+ if (window.matchMedia('(min-width: 42rem)').matches) {
+ evt.preventDefault();
+ const nextIndex = this.getNextIndex(index, this.getDirection(evt));
+ const tab = this.getTabAt(nextIndex);
+ if (tab) {
+ this.selectTabAt(nextIndex, onSelectionChange);
+ if (tab.tabAnchor) {
+ tab.tabAnchor.focus();
+ }
}
}
};
@@ -222,7 +241,6 @@ export default class Tabs extends React.Component {
index,
selected: index === this.state.selected,
handleTabClick: this.handleTabClick(onSelectionChange),
- handleTabAnchorFocus: this.handleTabAnchorFocus(onSelectionChange),
tabIndex,
ref: e => {
this.setTabAt(index, e);