From 823ffe1e58be30e02b57c3528bbd3c9dfd372ae8 Mon Sep 17 00:00:00 2001 From: Lena Morita Date: Wed, 24 Aug 2022 22:13:39 +0900 Subject: [PATCH 1/7] TabPanel: Convert to TypeScript --- packages/components/src/tab-panel/README.md | 3 +- .../src/tab-panel/{index.js => index.tsx} | 82 +++++++++++++++---- .../components/src/tab-panel/stories/index.js | 39 --------- .../src/tab-panel/stories/index.tsx | 37 +++++++++ packages/components/src/tab-panel/types.ts | 64 +++++++++++++++ packages/components/tsconfig.json | 1 - 6 files changed, 170 insertions(+), 56 deletions(-) rename packages/components/src/tab-panel/{index.js => index.tsx} (52%) delete mode 100644 packages/components/src/tab-panel/stories/index.js create mode 100644 packages/components/src/tab-panel/stories/index.tsx create mode 100644 packages/components/src/tab-panel/types.ts diff --git a/packages/components/src/tab-panel/README.md b/packages/components/src/tab-panel/README.md index 90ec6af37087a..a4a1c0c525370 100644 --- a/packages/components/src/tab-panel/README.md +++ b/packages/components/src/tab-panel/README.md @@ -136,7 +136,7 @@ The class to add to the active tab #### initialTabName -Optionally provide a tab name for a tab to be selected upon mounting of component. If this prop is not set, the first tab will be selected by default. +The name of the tab to be selected upon mounting of component. If this prop is not set, the first tab will be selected by default. - Type: `String` - Required: No @@ -145,7 +145,6 @@ Optionally provide a tab name for a tab to be selected upon mounting of componen #### children A function which renders the tabviews given the selected tab. The function is passed the active tab object as an argument as defined the tabs prop. -The element to which the tooltip should anchor. - Type: (`Object`) => `Element` - Required: Yes diff --git a/packages/components/src/tab-panel/index.js b/packages/components/src/tab-panel/index.tsx similarity index 52% rename from packages/components/src/tab-panel/index.js rename to packages/components/src/tab-panel/index.tsx index d6124798c19de..a9545ae5048ae 100644 --- a/packages/components/src/tab-panel/index.js +++ b/packages/components/src/tab-panel/index.tsx @@ -15,10 +15,19 @@ import { useInstanceId } from '@wordpress/compose'; */ import { NavigableMenu } from '../navigable-container'; import Button from '../button'; +import type { TabButtonProps, TabPanelProps } from './types'; +import { contextConnect, WordPressComponentProps } from '../ui/context'; +import type { ForwardedRef } from 'react'; const noop = () => {}; -const TabButton = ( { tabId, onClick, children, selected, ...rest } ) => ( +const TabButton = ( { + tabId, + onClick, + children, + selected, + ...rest +}: TabButtonProps ) => ( ); -export default function TabPanel( { - className, - children, - tabs, - initialTabName, - orientation = 'horizontal', - activeClass = 'is-active', - onSelect = noop, -} ) { +function UnconnectedTabPanel( + { + className, + children, + tabs, + initialTabName, + orientation = 'horizontal', + activeClass = 'is-active', + onSelect = noop, + }: WordPressComponentProps< TabPanelProps, 'div', false >, + forwardedRef: ForwardedRef< any > +) { const instanceId = useInstanceId( TabPanel, 'tab-panel' ); - const [ selected, setSelected ] = useState( null ); + const [ selected, setSelected ] = useState< string >(); - const handleClick = ( tabKey ) => { + const handleClick = ( tabKey: string ) => { setSelected( tabKey ); onSelect( tabKey ); }; - const onNavigate = ( childIndex, child ) => { + const onNavigate = ( _childIndex: number, child: HTMLButtonElement ) => { child.click(); }; const selectedTab = find( tabs, { name: selected } ); @@ -58,7 +70,8 @@ export default function TabPanel( { const newSelectedTab = find( tabs, { name: selected } ); if ( ! newSelectedTab ) { setSelected( - initialTabName || ( tabs.length > 0 ? tabs[ 0 ].name : null ) + initialTabName || + ( tabs.length > 0 ? tabs[ 0 ].name : undefined ) ); } }, [ tabs ] ); @@ -66,6 +79,7 @@ export default function TabPanel( { return (
); } + +/** + * TabPanel is an ARIA-compliant tabpanel. + * + * TabPanels organize content across different screens, data sets, and interactions. + * It has two sections: a list of tabs, and the view to show when tabs are chosen. + * + * ```jsx + * import { TabPanel } from '@wordpress/components'; + * + * const onSelect = ( tabName ) => { + * console.log( 'Selecting tab', tabName ); + * }; + * + * const MyTabPanel = () => ( + * + * { ( tab ) =>

{ tab.title }

} + *
+ * ); + * ``` + */ +export const TabPanel = contextConnect( UnconnectedTabPanel, 'TabPanel' ); + +export default TabPanel; diff --git a/packages/components/src/tab-panel/stories/index.js b/packages/components/src/tab-panel/stories/index.js deleted file mode 100644 index 44b285af4077f..0000000000000 --- a/packages/components/src/tab-panel/stories/index.js +++ /dev/null @@ -1,39 +0,0 @@ -/** - * External dependencies - */ -import { text } from '@storybook/addon-knobs'; - -/** - * Internal dependencies - */ -import TabPanel from '../'; - -export default { - title: 'Components/TabPanel', - component: TabPanel, - parameters: { - knobs: { disable: false }, - }, -}; - -export const _default = () => { - return ( - - { ( tab ) =>

Selected tab: { tab.title }

} -
- ); -}; diff --git a/packages/components/src/tab-panel/stories/index.tsx b/packages/components/src/tab-panel/stories/index.tsx new file mode 100644 index 0000000000000..0088f79d7b5d6 --- /dev/null +++ b/packages/components/src/tab-panel/stories/index.tsx @@ -0,0 +1,37 @@ +/** + * External dependencies + */ +import type { ComponentMeta, ComponentStory } from '@storybook/react'; + +/** + * Internal dependencies + */ +import TabPanel from '..'; + +const meta: ComponentMeta< typeof TabPanel > = { + title: 'Components/TabPanel', + component: TabPanel, + parameters: { + controls: { expanded: true }, + }, +}; +export default meta; + +const Template: ComponentStory< typeof TabPanel > = ( props ) => { + return ; +}; + +export const Default = Template.bind( {} ); +Default.args = { + children: ( tab ) =>

Selected tab: { tab.title }

, + tabs: [ + { + name: 'tab1', + title: 'Tab 1', + }, + { + name: 'tab2', + title: 'Tab 2', + }, + ], +}; diff --git a/packages/components/src/tab-panel/types.ts b/packages/components/src/tab-panel/types.ts new file mode 100644 index 0000000000000..2e8591eb817f2 --- /dev/null +++ b/packages/components/src/tab-panel/types.ts @@ -0,0 +1,64 @@ +/** + * External dependencies + */ +import type { ReactNode } from 'react'; + +type Tab = { + /** + * The key of the tab. + */ + name: string; + /** + * The label of the tab. + */ + title: string; + /** + * The class name to apply to the tab button. + */ + className?: string; +} & Record< any, any >; + +export type TabButtonProps = { + children: ReactNode; + className?: string; + onClick: ( event: MouseEvent ) => void; + selected: boolean; + tabId: string; +}; + +export type TabPanelProps = { + /** + * The class name to add to the active tab. + * + * @default 'is-active' + */ + activeClass?: string; + /** + * A function which renders the tabviews given the selected tab. + * The function is passed the active tab object as an argument as defined by the tabs prop. + */ + children: ( tab: Tab ) => ReactNode; + className?: string; + /** + * The name of the tab to be selected upon mounting of component. + * If this prop is not set, the first tab will be selected by default. + */ + initialTabName?: string; + /** + * The function called when a tab has been selected. + * It is passed the `tabName` as an argument. + * + * @default `noop` + */ + onSelect?: ( tabName: string ) => void; + /** + * The orientation of the tablist. + * + * @default `horizontal` + */ + orientation?: 'horizontal' | 'vertical'; + /** + * Array of tab objects. Each tab object should contain at least a `name` and a `title`. + */ + tabs: Tab[]; +}; diff --git a/packages/components/tsconfig.json b/packages/components/tsconfig.json index 61b3f00f12a22..d5036248dc437 100644 --- a/packages/components/tsconfig.json +++ b/packages/components/tsconfig.json @@ -73,7 +73,6 @@ "src/sandbox", "src/search-control", "src/snackbar", - "src/tab-panel", "src/toggle-control", "src/toolbar", "src/toolbar-button", From 14509126b5b1fdb130776e95f4ef28c422445137 Mon Sep 17 00:00:00 2001 From: Lena Morita Date: Sat, 20 Aug 2022 05:17:29 +0900 Subject: [PATCH 2/7] Convert tests --- .../components/src/tab-panel/test/index.js | 271 ++++++++---------- 1 file changed, 112 insertions(+), 159 deletions(-) diff --git a/packages/components/src/tab-panel/test/index.js b/packages/components/src/tab-panel/test/index.js index b3d9bd0477685..5565bf4d7c8fe 100644 --- a/packages/components/src/tab-panel/test/index.js +++ b/packages/components/src/tab-panel/test/index.js @@ -1,179 +1,132 @@ /** * External dependencies */ -import TestUtils from 'react-dom/test-utils'; +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; /** * Internal dependencies */ import TabPanel from '../'; -/** - * WordPress dependencies - */ -import { Component } from '@wordpress/element'; +const setupUser = () => + userEvent.setup( { + advanceTimers: jest.advanceTimersByTime, + } ); + +const TABS = [ + { + name: 'alpha', + title: 'Alpha', + className: 'alpha-class', + }, + { + name: 'beta', + title: 'Beta', + className: 'beta-class', + }, + { + name: 'gamma', + title: 'Gamma', + className: 'gamma-class', + }, +]; + +const getSelectedTab = () => screen.getByRole( 'tab', { selected: true } ); describe( 'TabPanel', () => { - const getElementByClass = ( wrapper, className ) => { - return TestUtils.findRenderedDOMComponentWithClass( - wrapper, - className + it( 'should render a tabpanel, and clicking should change tabs', async () => { + const user = setupUser(); + + render( + `${ tab.name } panel` } + /> ); - }; - const getElementsByClass = ( wrapper, className ) => { - return TestUtils.scryRenderedDOMComponentsWithClass( - wrapper, - className + expect( getSelectedTab() ).toHaveTextContent( 'Alpha' ); + expect( screen.getByRole( 'tabpanel' ) ).toHaveTextContent( + 'alpha panel' + ); + + await user.click( screen.getByRole( 'tab', { name: 'Beta' } ) ); + + expect( getSelectedTab() ).toHaveTextContent( 'Beta' ); + expect( screen.getByRole( 'tabpanel' ) ).toHaveTextContent( + 'beta panel' ); - }; - - const elementClick = ( element ) => { - TestUtils.Simulate.click( element ); - }; - - // This is needed because TestUtils does not accept a stateless component. - // anything run through a HOC ends up as a stateless component. - const getTestComponent = ( WrappedComponent, props ) => { - class TestComponent extends Component { - render() { - return ; - } - } - return ; - }; - - describe( 'basic rendering', () => { - it( 'should render a tabpanel, and clicking should change tabs', () => { - const props = { - className: 'test-panel', - activeClass: 'active-tab', - tabs: [ - { - name: 'alpha', - title: 'Alpha', - className: 'alpha', - }, - { - name: 'beta', - title: 'Beta', - className: 'beta', - }, - { - name: 'gamma', - title: 'Gamma', - className: 'gamma', - }, - ], - children: ( tab ) => { - return ( -

- { tab.name } -

- ); - }, - }; - - let wrapper; - TestUtils.act( () => { - wrapper = TestUtils.renderIntoDocument( - getTestComponent( TabPanel, props ) - ); - } ); - const alphaTab = getElementByClass( wrapper, 'alpha' ); - const betaTab = getElementByClass( wrapper, 'beta' ); - const gammaTab = getElementByClass( wrapper, 'gamma' ); - - const getAlphaViews = () => - getElementsByClass( wrapper, 'alpha-view' ); - const getBetaViews = () => - getElementsByClass( wrapper, 'beta-view' ); - const getGammaViews = () => - getElementsByClass( wrapper, 'gamma-view' ); - - const getActiveTab = () => - getElementByClass( wrapper, 'active-tab' ); - const getActiveView = () => - getElementByClass( - wrapper, - 'components-tab-panel__tab-content' - ).firstChild.textContent; - - expect( getActiveTab().innerHTML ).toBe( 'Alpha' ); - expect( getAlphaViews() ).toHaveLength( 1 ); - expect( getBetaViews() ).toHaveLength( 0 ); - expect( getGammaViews() ).toHaveLength( 0 ); - expect( getActiveView() ).toBe( 'alpha' ); - - elementClick( betaTab ); - - expect( getActiveTab().innerHTML ).toBe( 'Beta' ); - expect( getAlphaViews() ).toHaveLength( 0 ); - expect( getBetaViews() ).toHaveLength( 1 ); - expect( getGammaViews() ).toHaveLength( 0 ); - expect( getActiveView() ).toBe( 'beta' ); - - elementClick( betaTab ); - - expect( getActiveTab().innerHTML ).toBe( 'Beta' ); - expect( getAlphaViews() ).toHaveLength( 0 ); - expect( getBetaViews() ).toHaveLength( 1 ); - expect( getGammaViews() ).toHaveLength( 0 ); - expect( getActiveView() ).toBe( 'beta' ); - - elementClick( gammaTab ); - - expect( getActiveTab().innerHTML ).toBe( 'Gamma' ); - expect( getAlphaViews() ).toHaveLength( 0 ); - expect( getBetaViews() ).toHaveLength( 0 ); - expect( getGammaViews() ).toHaveLength( 1 ); - expect( getActiveView() ).toBe( 'gamma' ); - - elementClick( alphaTab ); - - expect( getActiveTab().innerHTML ).toBe( 'Alpha' ); - expect( getAlphaViews() ).toHaveLength( 1 ); - expect( getBetaViews() ).toHaveLength( 0 ); - expect( getGammaViews() ).toHaveLength( 0 ); - expect( getActiveView() ).toBe( 'alpha' ); - } ); + await user.click( screen.getByRole( 'tab', { name: 'Gamma' } ) ); + + expect( getSelectedTab() ).toHaveTextContent( 'Gamma' ); + expect( screen.getByRole( 'tabpanel' ) ).toHaveTextContent( + 'gamma panel' + ); + + await user.click( + screen.getByRole( 'tab', { + name: 'Alpha', + } ) + ); + + expect( getSelectedTab() ).toHaveTextContent( 'Alpha' ); + expect( screen.getByRole( 'tabpanel' ) ).toHaveTextContent( + 'alpha panel' + ); } ); it( 'should render with a tab initially selected by prop initialTabIndex', () => { - const props = { - className: 'test-panel', - activeClass: 'active-tab', - initialTabName: 'beta', - tabs: [ - { - name: 'alpha', - title: 'Alpha', - className: 'alpha', - }, - { - name: 'beta', - title: 'Beta', - className: 'beta', - }, - ], - children: ( tab ) => { - return ( -

- { tab.name } -

- ); - }, - }; - - let wrapper; - TestUtils.act( () => { - wrapper = TestUtils.renderIntoDocument( - getTestComponent( TabPanel, props ) - ); - } ); - - const getActiveTab = () => getElementByClass( wrapper, 'active-tab' ); - expect( getActiveTab().innerHTML ).toBe( 'Beta' ); + render( + {} } + /> + ); + const selectedTab = screen.getByRole( 'tab', { selected: true } ); + expect( selectedTab ).toHaveTextContent( 'Beta' ); + } ); + + it( 'should apply the `activeClass` to the selected tab', async () => { + const user = setupUser(); + const activeClass = 'my-active-tab'; + + render( + {} } + /> + ); + expect( getSelectedTab() ).toHaveClass( activeClass ); + screen + .getAllByRole( 'tab', { selected: false } ) + .forEach( ( unselectedTab ) => { + expect( unselectedTab ).not.toHaveClass( activeClass ); + } ); + + await user.click( screen.getByRole( 'tab', { name: 'Beta' } ) ); + + expect( getSelectedTab() ).toHaveClass( activeClass ); + screen + .getAllByRole( 'tab', { selected: false } ) + .forEach( ( unselectedTab ) => { + expect( unselectedTab ).not.toHaveClass( activeClass ); + } ); + } ); + + it( "should apply the tab's `className` to the tab button", () => { + render( {} } /> ); + + expect( screen.getByRole( 'tab', { name: 'Alpha' } ) ).toHaveClass( + 'alpha-class' + ); + expect( screen.getByRole( 'tab', { name: 'Beta' } ) ).toHaveClass( + 'beta-class' + ); + expect( screen.getByRole( 'tab', { name: 'Gamma' } ) ).toHaveClass( + 'gamma-class' + ); } ); } ); From a9fc52ef1cd489cfea79db0ed0069954e65ebc04 Mon Sep 17 00:00:00 2001 From: Lena Morita Date: Sat, 20 Aug 2022 05:20:05 +0900 Subject: [PATCH 3/7] Convert tests to TS --- .../components/src/tab-panel/test/{index.js => index.tsx} | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) rename packages/components/src/tab-panel/test/{index.js => index.tsx} (94%) diff --git a/packages/components/src/tab-panel/test/index.js b/packages/components/src/tab-panel/test/index.tsx similarity index 94% rename from packages/components/src/tab-panel/test/index.js rename to packages/components/src/tab-panel/test/index.tsx index 5565bf4d7c8fe..01f943c4fc1a2 100644 --- a/packages/components/src/tab-panel/test/index.js +++ b/packages/components/src/tab-panel/test/index.tsx @@ -7,7 +7,7 @@ import userEvent from '@testing-library/user-event'; /** * Internal dependencies */ -import TabPanel from '../'; +import TabPanel from '..'; const setupUser = () => userEvent.setup( { @@ -81,7 +81,7 @@ describe( 'TabPanel', () => { {} } + children={ () => undefined } /> ); const selectedTab = screen.getByRole( 'tab', { selected: true } ); @@ -96,7 +96,7 @@ describe( 'TabPanel', () => { {} } + children={ () => undefined } /> ); expect( getSelectedTab() ).toHaveClass( activeClass ); @@ -117,7 +117,7 @@ describe( 'TabPanel', () => { } ); it( "should apply the tab's `className` to the tab button", () => { - render( {} } /> ); + render( undefined } /> ); expect( screen.getByRole( 'tab', { name: 'Alpha' } ) ).toHaveClass( 'alpha-class' From edbf40da95e0c4d44d1209b2bddace43b044a046 Mon Sep 17 00:00:00 2001 From: Lena Morita Date: Sun, 21 Aug 2022 05:04:18 +0900 Subject: [PATCH 4/7] Simplify --- .../components/src/tab-panel/test/index.tsx | 42 +++++++------------ 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/packages/components/src/tab-panel/test/index.tsx b/packages/components/src/tab-panel/test/index.tsx index 01f943c4fc1a2..3b8f7aacc265c 100644 --- a/packages/components/src/tab-panel/test/index.tsx +++ b/packages/components/src/tab-panel/test/index.tsx @@ -37,43 +37,31 @@ const getSelectedTab = () => screen.getByRole( 'tab', { selected: true } ); describe( 'TabPanel', () => { it( 'should render a tabpanel, and clicking should change tabs', async () => { const user = setupUser(); + const panelRenderFunction = jest.fn(); - render( - `${ tab.name } panel` } - /> - ); + render( ); expect( getSelectedTab() ).toHaveTextContent( 'Alpha' ); - expect( screen.getByRole( 'tabpanel' ) ).toHaveTextContent( - 'alpha panel' - ); + expect( + screen.getByRole( 'tabpanel', { name: 'Alpha' } ) + ).toBeInTheDocument(); + expect( panelRenderFunction ).toHaveBeenLastCalledWith( TABS[ 0 ] ); await user.click( screen.getByRole( 'tab', { name: 'Beta' } ) ); expect( getSelectedTab() ).toHaveTextContent( 'Beta' ); - expect( screen.getByRole( 'tabpanel' ) ).toHaveTextContent( - 'beta panel' - ); - - await user.click( screen.getByRole( 'tab', { name: 'Gamma' } ) ); - - expect( getSelectedTab() ).toHaveTextContent( 'Gamma' ); - expect( screen.getByRole( 'tabpanel' ) ).toHaveTextContent( - 'gamma panel' - ); + expect( + screen.getByRole( 'tabpanel', { name: 'Beta' } ) + ).toBeInTheDocument(); + expect( panelRenderFunction ).toHaveBeenLastCalledWith( TABS[ 1 ] ); - await user.click( - screen.getByRole( 'tab', { - name: 'Alpha', - } ) - ); + await user.click( screen.getByRole( 'tab', { name: 'Alpha' } ) ); expect( getSelectedTab() ).toHaveTextContent( 'Alpha' ); - expect( screen.getByRole( 'tabpanel' ) ).toHaveTextContent( - 'alpha panel' - ); + expect( + screen.getByRole( 'tabpanel', { name: 'Alpha' } ) + ).toBeInTheDocument(); + expect( panelRenderFunction ).toHaveBeenLastCalledWith( TABS[ 0 ] ); } ); it( 'should render with a tab initially selected by prop initialTabIndex', () => { From 8a670ac8caf07def31b98ebdca5a7e915b0580a2 Mon Sep 17 00:00:00 2001 From: Lena Morita Date: Wed, 24 Aug 2022 22:23:22 +0900 Subject: [PATCH 5/7] Replace noop with conditional chaining --- packages/components/src/tab-panel/index.tsx | 6 ++---- packages/components/src/tab-panel/types.ts | 2 -- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/components/src/tab-panel/index.tsx b/packages/components/src/tab-panel/index.tsx index a9545ae5048ae..ec38f5dfcf4a0 100644 --- a/packages/components/src/tab-panel/index.tsx +++ b/packages/components/src/tab-panel/index.tsx @@ -19,8 +19,6 @@ import type { TabButtonProps, TabPanelProps } from './types'; import { contextConnect, WordPressComponentProps } from '../ui/context'; import type { ForwardedRef } from 'react'; -const noop = () => {}; - const TabButton = ( { tabId, onClick, @@ -48,7 +46,7 @@ function UnconnectedTabPanel( initialTabName, orientation = 'horizontal', activeClass = 'is-active', - onSelect = noop, + onSelect, }: WordPressComponentProps< TabPanelProps, 'div', false >, forwardedRef: ForwardedRef< any > ) { @@ -57,7 +55,7 @@ function UnconnectedTabPanel( const handleClick = ( tabKey: string ) => { setSelected( tabKey ); - onSelect( tabKey ); + onSelect?.( tabKey ); }; const onNavigate = ( _childIndex: number, child: HTMLButtonElement ) => { diff --git a/packages/components/src/tab-panel/types.ts b/packages/components/src/tab-panel/types.ts index 2e8591eb817f2..d06a0d0a18dcf 100644 --- a/packages/components/src/tab-panel/types.ts +++ b/packages/components/src/tab-panel/types.ts @@ -47,8 +47,6 @@ export type TabPanelProps = { /** * The function called when a tab has been selected. * It is passed the `tabName` as an argument. - * - * @default `noop` */ onSelect?: ( tabName: string ) => void; /** From f1710c7fb7601cdd46bac18819a5f84dd51dff5a Mon Sep 17 00:00:00 2001 From: Lena Morita Date: Thu, 1 Sep 2022 03:44:11 +0900 Subject: [PATCH 6/7] Export without contextConnect --- packages/components/src/tab-panel/index.tsx | 99 ++++++++++----------- 1 file changed, 46 insertions(+), 53 deletions(-) diff --git a/packages/components/src/tab-panel/index.tsx b/packages/components/src/tab-panel/index.tsx index ec38f5dfcf4a0..36450b0aba349 100644 --- a/packages/components/src/tab-panel/index.tsx +++ b/packages/components/src/tab-panel/index.tsx @@ -16,8 +16,7 @@ import { useInstanceId } from '@wordpress/compose'; import { NavigableMenu } from '../navigable-container'; import Button from '../button'; import type { TabButtonProps, TabPanelProps } from './types'; -import { contextConnect, WordPressComponentProps } from '../ui/context'; -import type { ForwardedRef } from 'react'; +import type { WordPressComponentProps } from '../ui/context'; const TabButton = ( { tabId, @@ -38,18 +37,51 @@ const TabButton = ( { ); -function UnconnectedTabPanel( - { - className, - children, - tabs, - initialTabName, - orientation = 'horizontal', - activeClass = 'is-active', - onSelect, - }: WordPressComponentProps< TabPanelProps, 'div', false >, - forwardedRef: ForwardedRef< any > -) { +/** + * TabPanel is an ARIA-compliant tabpanel. + * + * TabPanels organize content across different screens, data sets, and interactions. + * It has two sections: a list of tabs, and the view to show when tabs are chosen. + * + * ```jsx + * import { TabPanel } from '@wordpress/components'; + * + * const onSelect = ( tabName ) => { + * console.log( 'Selecting tab', tabName ); + * }; + * + * const MyTabPanel = () => ( + * + * { ( tab ) =>

{ tab.title }

} + *
+ * ); + * ``` + */ +export function TabPanel( { + className, + children, + tabs, + initialTabName, + orientation = 'horizontal', + activeClass = 'is-active', + onSelect, +}: WordPressComponentProps< TabPanelProps, 'div', false > ) { const instanceId = useInstanceId( TabPanel, 'tab-panel' ); const [ selected, setSelected ] = useState< string >(); @@ -77,7 +109,6 @@ function UnconnectedTabPanel( return (
{ - * console.log( 'Selecting tab', tabName ); - * }; - * - * const MyTabPanel = () => ( - * - * { ( tab ) =>

{ tab.title }

} - *
- * ); - * ``` - */ -export const TabPanel = contextConnect( UnconnectedTabPanel, 'TabPanel' ); - export default TabPanel; From 72b72cb65584fe0bf63a8f70eaae92990e4fb6e9 Mon Sep 17 00:00:00 2001 From: Lena Morita Date: Thu, 1 Sep 2022 20:07:42 +0900 Subject: [PATCH 7/7] Add description to `className` --- packages/components/src/tab-panel/types.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/components/src/tab-panel/types.ts b/packages/components/src/tab-panel/types.ts index d06a0d0a18dcf..2a89da6a215b7 100644 --- a/packages/components/src/tab-panel/types.ts +++ b/packages/components/src/tab-panel/types.ts @@ -38,6 +38,9 @@ export type TabPanelProps = { * The function is passed the active tab object as an argument as defined by the tabs prop. */ children: ( tab: Tab ) => ReactNode; + /** + * The class name to give to the outer container for the TabPanel. + */ className?: string; /** * The name of the tab to be selected upon mounting of component.