Skip to content

Commit

Permalink
Fix tab panel initial tab selection (#47100)
Browse files Browse the repository at this point in the history
* Fix tab panel initial tab selection

* Add changelog entry

* Add a test case for a deferred initial tab
  • Loading branch information
talldan committed Jan 12, 2023
1 parent 420ba0a commit 4f0e10a
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 16 deletions.
1 change: 1 addition & 0 deletions packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

- `Placeholder`: set fixed right margin for label's icon ([46918](https://github.com/WordPress/gutenberg/pull/46918)).
- `TreeGrid`: Fix right-arrow keyboard navigation when a row contains more than two focusable elements ([46998](https://github.com/WordPress/gutenberg/pull/46998)).
- `TabPanel`: Fix initial tab selection when the tab declaration is lazily added to the `tabs` array ([47100](https://github.com/WordPress/gutenberg/pull/47100)).

## 23.1.0 (2023-01-02)

Expand Down
52 changes: 37 additions & 15 deletions packages/components/src/tab-panel/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,25 +103,47 @@ export function TabPanel( {
const selectedTab = tabs.find( ( { name } ) => name === selected );
const selectedId = `${ instanceId }-${ selectedTab?.name ?? 'none' }`;

// Handle selecting the initial tab.
useEffect( () => {
const firstEnabledTab = tabs.find( ( tab ) => ! tab.disabled );
// If there's a selected tab, don't override it.
if ( selectedTab ) {
return;
}

const initialTab = tabs.find( ( tab ) => tab.name === initialTabName );
if ( ! selectedTab?.name && firstEnabledTab ) {
handleTabSelection(
initialTab && ! initialTab.disabled
? initialTab.name
: firstEnabledTab.name
);
} else if ( selectedTab?.disabled && firstEnabledTab ) {

// Wait for the denoted initial tab to be declared before making a
// selection. This ensures that if a tab is declared lazily it can
// still receive initial selection.
if ( initialTabName && ! initialTab ) {
return;
}

if ( initialTab && ! initialTab.disabled ) {
// Select the initial tab if it's not disabled.
handleTabSelection( initialTab.name );
} else {
// Fallback to the first enabled tab when the initial is disabled.
const firstEnabledTab = tabs.find( ( tab ) => ! tab.disabled );
if ( firstEnabledTab ) handleTabSelection( firstEnabledTab.name );
}
}, [ tabs, selectedTab, initialTabName, handleTabSelection ] );

// Handle the currently selected tab becoming disabled.
useEffect( () => {
// This effect only runs when the selected tab is defined and becomes disabled.
if ( ! selectedTab?.disabled ) {
return;
}

const firstEnabledTab = tabs.find( ( tab ) => ! tab.disabled );

// If the currently selected tab becomes disabled, select the first enabled tab.
// (if there is one).
if ( firstEnabledTab ) {
handleTabSelection( firstEnabledTab.name );
}
}, [
tabs,
selectedTab?.name,
selectedTab?.disabled,
initialTabName,
handleTabSelection,
] );
}, [ tabs, selectedTab?.disabled, handleTabSelection ] );

return (
<div className={ className }>
Expand Down
35 changes: 34 additions & 1 deletion packages/components/src/tab-panel/test/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ describe( 'TabPanel', () => {
);
} );

it( 'should select `initialTabname` if defined', () => {
it( 'should select `initialTabName` if defined', () => {
const mockOnSelect = jest.fn();

render(
Expand All @@ -162,6 +162,39 @@ describe( 'TabPanel', () => {
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' );
} );

it( 'waits for the tab with the `initialTabName` to become present in the `tabs` array before selecting it', () => {
const mockOnSelect = jest.fn();

const { rerender } = render(
<TabPanel
tabs={ TABS }
initialTabName="delta"
children={ () => undefined }
onSelect={ mockOnSelect }
/>
);

// There should be no selected tab.
expect(
screen.queryByRole( 'tab', { selected: true } )
).not.toBeInTheDocument();

rerender(
<TabPanel
tabs={ [
{ name: 'delta', title: 'Delta', className: 'delta-class' },
...TABS,
] }
initialTabName="delta"
children={ () => undefined }
onSelect={ mockOnSelect }
/>
);

expect( getSelectedTab() ).toHaveTextContent( 'Delta' );
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'delta' );
} );

it( 'should disable the tab when `disabled` is true', async () => {
const user = setupUser();
const mockOnSelect = jest.fn();
Expand Down

1 comment on commit 4f0e10a

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Flaky tests detected in 4f0e10a.
Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

🔍 Workflow run URL: https://github.com/WordPress/gutenberg/actions/runs/3901623868
📝 Reported issues:

Please sign in to comment.