Skip to content

Commit

Permalink
feat: Allow manual tab activation (#3271)
Browse files Browse the repository at this point in the history
Co-authored-by: Andrei Zhaleznichenka <[email protected]>
  • Loading branch information
2 people authored and georgylobko committed Feb 18, 2025
1 parent b84fa40 commit 4fccce3
Show file tree
Hide file tree
Showing 7 changed files with 302 additions and 64 deletions.
28 changes: 26 additions & 2 deletions pages/tabs/responsive-integ.page.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,26 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import React, { useState } from 'react';
import React, { useContext, useState } from 'react';

import { ButtonDropdown } from '~components';
import { ButtonDropdown, Select } from '~components';
import Tabs, { TabsProps } from '~components/tabs';

import AppContext, { AppContextType } from '../app/app-context';

import styles from './responsive.scss';

type TabsContext = React.Context<
AppContextType<{
keyboardActivationMode: TabsProps['keyboardActivationMode'];
}>
>;

export default function TabsDemoPage() {
const {
urlParams: { keyboardActivationMode = 'automatic' },
setUrlParams,
} = useContext(AppContext as TabsContext);

const defaultTabs: Array<TabsProps.Tab> = [
{
label: 'First tab',
Expand All @@ -18,6 +31,7 @@ export default function TabsDemoPage() {
{
label: 'Second tab',
id: 'second',
href: '/second',
content:
'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.',
},
Expand Down Expand Up @@ -135,13 +149,22 @@ export default function TabsDemoPage() {
return (
<div id="container" className={small ? styles.small : ''}>
<h1>Tabs</h1>
<Select
options={[{ value: 'automatic' }, { value: 'manual' }]}
selectedOption={{ value: keyboardActivationMode }}
onChange={e =>
setUrlParams({ keyboardActivationMode: e.detail.selectedOption.value as TabsProps['keyboardActivationMode'] })
}
inlineLabelText="Activation mode"
/>
<input type="text" id="before" aria-label="before" />
<form action="/">
<Tabs
ariaLabel="General Tabs"
tabs={tabs}
activeTabId={selectedTab}
onChange={event => setSelectedTab(event.detail.activeTabId)}
keyboardActivationMode={keyboardActivationMode}
i18nStrings={{ scrollLeftAriaLabel: 'Scroll left', scrollRightAriaLabel: 'Scroll right' }}
/>
</form>
Expand All @@ -156,6 +179,7 @@ export default function TabsDemoPage() {
id="dismiss-tabs"
ariaLabel="Dismissible Tabs"
tabs={tabsDismissibles}
keyboardActivationMode={keyboardActivationMode}
i18nStrings={{
scrollLeftAriaLabel: 'Scroll left (Dismissible Tabs)',
scrollRightAriaLabel: 'Scroll right (Dismissible Tabs)',
Expand Down
21 changes: 21 additions & 0 deletions src/__tests__/snapshot-tests/__snapshots__/documenter.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -16619,6 +16619,27 @@ use the \`id\` attribute, consider setting it on a parent element instead.",
"optional": true,
"type": "string",
},
{
"defaultValue": ""automatic"",
"description": "Determines how the active tab is switched when navigating using
the keyboard. The options are:
- 'automatic' (default): the active tab is switched using the arrow keys.
- 'manual': a tab must be explicitly activated using the enter/space key.
We recommend using 'automatic' in most situations to provide consistent
and quick switching between tabs. Use 'manual' only if there is a specific
need to introduce friction to the switching of tabs.",
"inlineType": {
"name": "",
"type": "union",
"values": [
"automatic",
"manual",
],
},
"name": "keyboardActivationMode",
"optional": true,
"type": "string",
},
{
"description": "Specifies the tabs to display. Each tab object has the following properties:
- \`id\` (string) - The tab identifier. This value needs to be passed to the Tabs component as \`activeTabId\` to select this tab.
Expand Down
103 changes: 100 additions & 3 deletions src/tabs/__integ__/tabs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,17 @@ class TabsPage extends BasePageObject {

const setupTest = (
testFn: (page: TabsPage) => Promise<void>,
{ smallViewport = false, pagePath = 'responsive-integ' }: { smallViewport?: boolean; pagePath?: string } = {}
{
smallViewport = false,
pagePath = 'responsive-integ',
keyboardActivationMode,
}: { smallViewport?: boolean; pagePath?: string; keyboardActivationMode?: 'manual' | 'automatic' } = {}
) => {
return useBrowser(async browser => {
const page = new TabsPage(browser);
await browser.url(`#/light/tabs/${pagePath}`);
await browser.url(
`#/light/tabs/${pagePath}${keyboardActivationMode ? `?keyboardActivationMode=${keyboardActivationMode}` : ''}`
);
await page.waitForVisible(wrapper.findTabContent().toSelector());
if (smallViewport) {
await page.setWindowSize({ width: 400, height: 1000 });
Expand Down Expand Up @@ -249,7 +255,7 @@ test(
test(
'tab selection does not cause vertical scroll',
setupTest(async page => {
await page.setWindowSize({ width: 600, height: 300 });
await page.setWindowSize({ width: 600, height: 320 });
const { top: initialTopScrollPosition } = await page.getWindowScroll();
await page.click(wrapper.findTabLinkByIndex(3).toSelector());
const { top: currentTopScrollPosition } = await page.getWindowScroll();
Expand Down Expand Up @@ -512,3 +518,94 @@ test(
await expect(page.isFocused(wrapper.findTabLinkByIndex(3).toSelector())).resolves.toBe(true);
})
);

describe('activation mode', () => {
test(
'automatic mode - activates tab on arrow navigation',
setupTest(
async page => {
await page.focusTabHeader();
await expect(page.findActiveTabIndex()).resolves.toBe(0);

await page.keys(['ArrowRight']);
await expect(page.findActiveTabIndex()).resolves.toBe(1);

await page.keys(['ArrowLeft']);
await expect(page.findActiveTabIndex()).resolves.toBe(0);
},
{ keyboardActivationMode: 'automatic' }
)
);

test(
'manual mode - does not activate tab on arrow navigation',
setupTest(
async page => {
await page.focusTabHeader();
const initialActiveTab = await page.findActiveTabIndex();

await page.keys(['ArrowRight']);
await expect(page.findActiveTabIndex()).resolves.toBe(initialActiveTab);

await page.keys(['ArrowLeft']);
await expect(page.findActiveTabIndex()).resolves.toBe(initialActiveTab);
},
{ keyboardActivationMode: 'manual' }
)
);

test.each(['Space', 'Enter'])('manual mode - activates tab on %s press', key =>
setupTest(
async page => {
await page.focusTabHeader();
await expect(page.findActiveTabIndex()).resolves.toBe(0);

await page.keys(['ArrowRight']);
await page.keys([key]);
await expect(page.findActiveTabIndex()).resolves.toBe(1);

await page.keys(['ArrowRight']);
await page.keys([key]);
await expect(page.findActiveTabIndex()).resolves.toBe(2);
},
{ keyboardActivationMode: 'manual' }
)()
);

test(
'manual mode - allows full keyboard navigation without activation',
setupTest(
async page => {
await page.focusTabHeader();
const initialActiveTab = await page.findActiveTabIndex();

await page.keys(['End']);
await expect(page.findActiveTabIndex()).resolves.toBe(initialActiveTab);

await page.keys(['Home']);
await expect(page.findActiveTabIndex()).resolves.toBe(initialActiveTab);

await page.navigateTabList(3);
await expect(page.findActiveTabIndex()).resolves.toBe(initialActiveTab);
},
{ keyboardActivationMode: 'manual' }
)
);

test(
'automatic mode - activates tab on Home/End keys',
setupTest(
async page => {
await page.focusTabHeader();

await page.keys(['End']);
await page.navigateTabList(-2);
await expect(page.findActiveTabIndex()).resolves.toBe(5);

await page.keys(['Home']);
await expect(page.findActiveTabIndex()).resolves.toBe(0);
},
{ keyboardActivationMode: 'automatic' }
)
);
});
Loading

0 comments on commit 4fccce3

Please sign in to comment.