Skip to content

Commit

Permalink
feat(tabs): vertical variation with inner grid (#16738)
Browse files Browse the repository at this point in the history
* feat(vertical-tabs): new tab variant

* fix(tabs): update vertical docs

* fix(vertical-tabs): sm to use contained

* fix(tablistvertical): add scrollintoview

* fix(tabs): hover & disabled styles

* fix(tabs-vertical): hover border style

* feat(tabs): vertical variation with inner grid

* fix(tab-panel): have to account if there is no set height

* fix(tabs): add description to each set

* fix(tabs-v): show tooltip resize & remove right border when selected

* fix(fields-on-layer): create new style utility to use

* fix(tabs): create TabsVertical

* fix(tabs): remove no longer needed styles

* chore(test): add initial vertical test

* fix(tabs-vertical): update to keep index state

* fix(tabs): remove layer from story add into styles

* fix(tabs): remove layer from story add into styles

---------

Co-authored-by: Alison Joseph <[email protected]>
  • Loading branch information
ariellalgilmore and alisonjoseph authored Jul 1, 2024
1 parent e05f46e commit f406632
Show file tree
Hide file tree
Showing 9 changed files with 872 additions and 59 deletions.
58 changes: 58 additions & 0 deletions e2e/components/Tabs/Tabs-test.avt.e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,17 @@ test.describe('@avt Tabs', () => {
await expect(page).toHaveNoACViolations('Tabs-contained');
});

test('@avt-advanced-states tabs vertical', async ({ page }) => {
await visitStory(page, {
component: 'Tabs',
id: 'components-tabs--vertical',
globals: {
theme: 'white',
},
});
await expect(page).toHaveNoACViolations('Tabs-vertical');
});

test('@avt-advanced-states tabs contained with icons', async ({ page }) => {
await visitStory(page, {
component: 'Tabs',
Expand Down Expand Up @@ -150,6 +161,53 @@ test.describe('@avt Tabs', () => {
await expect(page.getByRole('tab', { name: 'Tab label 4' })).toBeFocused();
});

test.slow('@avt-keyboard-nav', async ({ page }) => {
await visitStory(page, {
component: 'Tabs',
id: 'components-tabs--default',
globals: {
theme: 'white',
},
});

// Focus on the first tab via keyboard navigation
await page.keyboard.press('Tab');
await expect(page.getByRole('tab', { name: 'Tab label 1' })).toBeVisible();

// Focus should be on content within the first Tab
await page.keyboard.press('Tab');
await expect(
page.getByRole('tabpanel', { name: 'Tab Panel 1' })
).toBeFocused();

// Moving back one tab stop we should back on Tab label 1
await page.keyboard.press('Shift+Tab');
await expect(page.getByRole('tab', { name: 'Tab label 1' })).toBeFocused();

// Nav through the default tab panel via keyboard navigation
await page.keyboard.press('ArrowDown');
await expect(page.getByRole('tab', { name: 'Tab label 2' })).toBeVisible();

// Move through the actions in the second tab
await page.keyboard.press('Tab');
await expect(page.getByRole('checkbox')).toBeVisible();

await page.keyboard.press('Tab');
await expect(page.getByRole('button', { name: 'Submit' })).toBeVisible();

await page.keyboard.press('Tab');
await expect(page.getByRole('textbox')).toBeVisible();

await page.keyboard.press('Shift+Tab');
await page.keyboard.press('Shift+Tab');
await page.keyboard.press('Shift+Tab');
await expect(page.getByRole('tab', { name: 'Tab label 2' })).toBeFocused();

// Continue to nav through the default tab panel via keyboard navigation
await page.keyboard.press('ArrowDown');
await expect(page.getByRole('tab', { name: 'Tab label 4' })).toBeFocused();
});

test.slow('@avt-keyboard-nav - dismissable state', async ({ page }) => {
await visitStory(page, {
component: 'Tabs',
Expand Down
8 changes: 8 additions & 0 deletions e2e/components/Tabs/Tabs-test.e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,14 @@ test.describe('Tabs', () => {
theme,
});
});

test('vertical @vrt', async ({ page }) => {
await snapshotStory(page, {
component: 'Tabs',
id: 'components-tabs--vertical',
theme,
});
});
});
});
});
57 changes: 56 additions & 1 deletion packages/react/src/components/Tabs/Tabs-test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import React from 'react';
import { Tabs, Tab, TabPanel, TabPanels, TabList } from './Tabs';
import {
Tabs,
TabsVertical,
Tab,
TabPanel,
TabPanels,
TabList,
TabListVertical,
} from './Tabs';
import { act } from 'react';

import { render, screen } from '@testing-library/react';
Expand Down Expand Up @@ -650,3 +658,50 @@ describe('TabList', () => {
expect(container.firstChild).not.toHaveClass(`${prefix}--tabs--full-width`);
});
});

describe('TabListVertical', () => {
it('should render TabList if screen smaller than md', () => {
jest.spyOn(hooks, 'useMatchMedia').mockImplementation(() => false);
const { container } = render(
<TabsVertical>
<TabListVertical aria-label="List of tabs">
<Tab>Tab Label 1</Tab>
<Tab>Tab Label 2</Tab>
<Tab>Tab Label 3</Tab>
</TabListVertical>
<TabPanels>
<TabPanel className="custom-class">
Tab Panel 1<button type="button">Submit</button>
</TabPanel>
<TabPanel>Tab Panel 2</TabPanel>
<TabPanel>Tab Panel 3</TabPanel>
</TabPanels>
</TabsVertical>
);

expect(container.firstChild).not.toHaveClass(`${prefix}--tabs--vertical`);
});

it('should have set height', () => {
const { container } = render(
<TabsVertical height="100px">
<TabListVertical aria-label="List of tabs">
<Tab>Tab Label 1</Tab>
<Tab>Tab Label 2</Tab>
<Tab>Tab Label 3</Tab>
<Tab>Tab Label 4</Tab>
</TabListVertical>
<TabPanels>
<TabPanel className="custom-class">
Tab Panel 1<button type="button">Submit</button>
</TabPanel>
<TabPanel>Tab Panel 2</TabPanel>
<TabPanel>Tab Panel 3</TabPanel>
<TabPanel>Tab Panel 4</TabPanel>
</TabPanels>
</TabsVertical>
);

expect(container.firstChild).toHaveAttribute('style', 'height: 100px;');
});
});
9 changes: 8 additions & 1 deletion packages/react/src/components/Tabs/Tabs.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ArgsTable, Canvas, Story } from '@storybook/blocks';
import { Tabs, TabList, Tab, TabPanels, TabPanel } from './Tabs';
import { Tabs, TabList, TabListVertical, Tab, TabPanels, TabPanel } from './Tabs';
import * as TabsStories from './Tabs.stories';
import { Grid, Column } from '../Grid';

Expand All @@ -20,6 +20,7 @@ import { Grid, Column } from '../Grid';
- [Contained Tabs](#contained-tabs)
- [Icon Tabs](#icon-tabs)
- [Dismissable Tabs](#dismissable-tabs)
- [Vertical Tabs](#vertical-tabs)
- [Component API](#component-api)
- [Tab - render content on click](#tab---render-content-on-click)
- [Tabs and the Grid - fullWidth prop](#tabs-and-the-grid---fullwidth-prop)
Expand Down Expand Up @@ -183,6 +184,12 @@ And there you have it! Working dismissable tabs.
<Story of={TabsStories.Dismissable} />
</Canvas>

### Vertical tabs

<Canvas>
<Story of={TabsStories.Vertical} />
</Canvas>

## Component API

<ArgsTable />
Expand Down
83 changes: 82 additions & 1 deletion packages/react/src/components/Tabs/Tabs.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,22 @@
*/

import React, { useState } from 'react';
import { Tabs, TabList, Tab, TabPanels, TabPanel, IconTab } from './Tabs';
import {
Tabs,
TabsVertical,
TabList,
TabListVertical,
Tab,
TabPanels,
TabPanel,
IconTab,
} from './Tabs';
import TextInput from '../TextInput';
import Checkbox from '../Checkbox';
import Button from '../Button';
import RadioButtonGroup from '../RadioButtonGroup';
import RadioButton from '../RadioButton';
import { Stack } from '../Stack';
import { Grid, Column } from '../Grid';
import mdx from './Tabs.mdx';

Expand All @@ -30,7 +42,9 @@ export default {
title: 'Components/Tabs',
component: Tabs,
subcomponents: {
TabsVertical,
TabList,
TabListVertical,
Tab,
TabPanels,
TabPanel,
Expand Down Expand Up @@ -542,6 +556,73 @@ export const ContainedFullWidth = () => (
</Grid>
);

export const Vertical = ({ ...args }) => (
<TabsVertical {...args}>
<TabListVertical aria-label="List of tabs">
<Tab>Dashboard</Tab>
<Tab>
Extra long label that will go two lines then truncate when it goes
beyond the Tab length
</Tab>
<Tab>Activity</Tab>
<Tab>Analyze</Tab>
<Tab>Investigate </Tab>
<Tab>Learn</Tab>
<Tab disabled>Settings</Tab>
</TabListVertical>
<TabPanels>
<TabPanel>Tab Panel 1</TabPanel>
<TabPanel>
<form style={{ margin: '2em' }}>
<Stack gap={7}>
<TextInput id="one" labelText="First Name" />
<TextInput id="three" labelText="Middle Initial" />
<TextInput id="two" labelText="Last Name" />
<RadioButtonGroup
legendText="Radio button heading"
name="formgroup-default-radio-button-group"
defaultSelected="radio-1">
<RadioButton labelText="Option 1" value="radio-1" id="radio-1" />
<RadioButton labelText="Option 2" value="radio-2" id="radio-2" />
<RadioButton labelText="Option 3" value="radio-3" id="radio-3" />
</RadioButtonGroup>
<Checkbox labelText={`Checkbox one`} id="checkbox-label-1" />
<Checkbox labelText={`Checkbox two`} id="checkbox-label-2" />
<Button>Submit</Button>
</Stack>
</form>
</TabPanel>
<TabPanel>Tab Panel 3</TabPanel>
<TabPanel>Tab Panel 4</TabPanel>
<TabPanel>Tab Panel 5</TabPanel>
<TabPanel>Tab Panel 6</TabPanel>
<TabPanel>Tab Panel 7</TabPanel>
</TabPanels>
</TabsVertical>
);

Vertical.args = {
height: '',
};

Vertical.argTypes = {
height: {
control: {
type: 'text',
},
},
dismissable: {
table: {
disable: true,
},
},
onTabCloseRequest: {
table: {
disable: true,
},
},
};

export const Skeleton = () => {
return (
<div style={{ maxWidth: '100%' }}>
Expand Down
Loading

0 comments on commit f406632

Please sign in to comment.