Skip to content

Commit

Permalink
Tabs: Fallback to first enabled tab if no active tab Id (#60681)
Browse files Browse the repository at this point in the history
If there is no selectedTabId, focus will be placed on the tab container which requires an arrow keypress to move focus to the first tab. This behavior isn't as user friendly as focus should be placed on a tab. To prevent focus on the wrapper, if no element is focused and there is no activeId, fallback to the first enabled tab.

Co-authored-by: Lena Morita <lena@jaguchi.com>
  • Loading branch information
jeryj and mirka authored Apr 12, 2024
1 parent e284270 commit e9bccc8
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 0 deletions.
4 changes: 4 additions & 0 deletions packages/components/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@

- `ProgressBar`: Fix CSS variable with invalid value ([#60576](https://github.com/WordPress/gutenberg/pull/60576)).

### Experimental

- `Tabs`: Fallback to first enabled tab if no active tab id ([#60681](https://github.com/WordPress/gutenberg/pull/60681)).

## 27.3.0 (2024-04-03)

### Bug Fix
Expand Down
8 changes: 8 additions & 0 deletions packages/components/src/tabs/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,14 @@ function Tabs( {
}
}, [ isControlled, selectedTab, selectedTabId, setSelectedId ] );

useEffect( () => {
// If there is no active tab, fallback to place focus on the first enabled tab
// so there is always an active element
if ( selectedTabId === null && ! activeId && firstEnabledTab?.id ) {
setActiveId( firstEnabledTab.id );
}
}, [ selectedTabId, activeId, firstEnabledTab?.id, setActiveId ] );

useEffect( () => {
if ( ! isControlled ) {
return;
Expand Down
38 changes: 38 additions & 0 deletions packages/components/src/tabs/test/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,44 @@ describe( 'Tabs', () => {
await press.Tab();
expect( alphaButton ).toHaveFocus();
} );

it( 'should focus on the first enabled tab when pressing the Tab key if no tab is selected', async () => {
const TABS_WITH_ALPHA_DISABLED = TABS.map( ( tabObj ) =>
tabObj.tabId === 'alpha'
? {
...tabObj,
tab: {
...tabObj.tab,
disabled: true,
},
}
: tabObj
);

render(
<ControlledTabs
tabs={ TABS_WITH_ALPHA_DISABLED }
selectedTabId={ null }
/>
);

await sleep();
await press.Tab();
expect(
await screen.findByRole( 'tab', { name: 'Beta' } )
).toHaveFocus();

await press.ArrowRight();
expect(
await screen.findByRole( 'tab', { name: 'Gamma' } )
).toHaveFocus();

await press.Tab();
await press.ShiftTab();
expect(
await screen.findByRole( 'tab', { name: 'Gamma' } )
).toHaveFocus();
} );
} );

describe( 'Tab Attributes', () => {
Expand Down

0 comments on commit e9bccc8

Please sign in to comment.