diff --git a/packages/react/src/components/TabContent/TabContent-test.js b/packages/react/src/components/TabContent/TabContent-test.js
index 6f43b8f46361..0ac53779850b 100644
--- a/packages/react/src/components/TabContent/TabContent-test.js
+++ b/packages/react/src/components/TabContent/TabContent-test.js
@@ -7,29 +7,54 @@
import React from 'react';
import TabContent from '../TabContent';
-import { shallow } from 'enzyme';
+import { render, screen } from '@testing-library/react';
+import '@testing-library/jest-dom';
describe('TabContent', () => {
describe('renders as expected', () => {
- const wrapper = shallow(
-
- content
- content
-
- );
-
it('renders children as expected', () => {
- expect(wrapper.props().children.length).toEqual(2);
+ render(
+
+ content
+ content
+
+ );
+ expect(screen.getByRole('tabpanel').children.length).toEqual(2);
});
- it('sets selected if passed in via props', () => {
- wrapper.setProps({ selected: true });
- expect(wrapper.props().selected).toEqual(true);
+ it('sets selected and hidden props with opposite boolean values', () => {
+ const { rerender } = render(
+
+ content
+ content
+
+ );
+ expect(screen.queryByRole('tabpanel')).not.toBeInTheDocument();
+ rerender(
+
+ content
+ content
+
+ );
+ expect(screen.getByRole('tabpanel')).toBeVisible();
});
- it('sets selected and hidden props with opposite boolean values', () => {
- wrapper.setProps({ selected: true });
- expect(wrapper.props().hidden).toEqual(false);
+ it('includes the content container in the tabbable index when no tabble contents is provided', () => {
+ render(
+
+ content
+
+ );
+ expect(screen.getByRole('tabpanel')).toHaveAttribute('tabindex', '0');
+ });
+
+ it('does not include the content container in the tabbable index when tabble contents is provided', () => {
+ render(
+
+ content
+
+ );
+ expect(screen.getByRole('tabpanel')).not.toHaveAttribute('tabindex', '0');
});
});
});
diff --git a/packages/react/src/components/TabContent/TabContent.js b/packages/react/src/components/TabContent/TabContent.js
index abe8140d73d3..5269fe427ee9 100644
--- a/packages/react/src/components/TabContent/TabContent.js
+++ b/packages/react/src/components/TabContent/TabContent.js
@@ -6,24 +6,45 @@
*/
import PropTypes from 'prop-types';
-import React from 'react';
+import React, { useState, useRef } from 'react';
import classNames from 'classnames';
import { settings } from 'carbon-components';
+import { selectorTabbable } from '../../internal/keyboard/navigation';
+import useIsomorphicEffect from '../../internal/useIsomorphicEffect';
const { prefix } = settings;
+/**
+ * Determine if the node within the provided ref contains content that is tabbable.
+ */
+function useTabbableContent(ref) {
+ const [hasTabbableContent, setHasTabbableContent] = useState(false);
+
+ useIsomorphicEffect(() => {
+ if (ref.current) {
+ setHasTabbableContent(ref.current.querySelector(selectorTabbable));
+ }
+ });
+
+ return hasTabbableContent;
+}
+
const TabContent = (props) => {
const { className, selected, children, ...other } = props;
const tabContentClasses = classNames(`${prefix}--tab-content`, {
[className]: className,
});
+ const ref = useRef(null);
+ const hasTabbableContent = useTabbableContent(ref);
return (
+ hidden={!selected}
+ ref={ref}
+ tabIndex={hasTabbableContent ? undefined : 0}>
{children}
);
diff --git a/packages/react/src/components/Tabs/Tabs-story.js b/packages/react/src/components/Tabs/Tabs-story.js
index 72ce156249f5..da9e59f22a0a 100644
--- a/packages/react/src/components/Tabs/Tabs-story.js
+++ b/packages/react/src/components/Tabs/Tabs-story.js
@@ -18,6 +18,7 @@ import { settings } from 'carbon-components';
import classNames from 'classnames';
import './Tabs-story.scss';
import CodeSnippet from '../CodeSnippet';
+import Button from '../Button';
import Tabs from '../Tabs';
import Tab from '../Tab';
import TabsSkeleton from '../Tabs/Tabs.Skeleton';
@@ -128,6 +129,7 @@ export const _Default = () => (
Content for second tab goes here.
+
Content for third tab goes here.
diff --git a/packages/react/src/internal/useIsomorphicEffect.js b/packages/react/src/internal/useIsomorphicEffect.js
new file mode 100644
index 000000000000..93b9f7e5ec7a
--- /dev/null
+++ b/packages/react/src/internal/useIsomorphicEffect.js
@@ -0,0 +1,7 @@
+import { useEffect, useLayoutEffect } from 'react';
+
+// useLayoutEffect on the client, useEffect on the server
+const useIsomorphicEffect =
+ typeof window !== 'undefined' ? useLayoutEffect : useEffect;
+
+export default useIsomorphicEffect;