Skip to content

Commit

Permalink
feat(Tabs): Contained tabs on the grid (#13927)
Browse files Browse the repository at this point in the history
* feat(Tabs): allow tabs to distribute width with new normalizeWidth prop

Co-authored-by: Guilherme Datilio Ribeiro <guilhermedatilio@gmail.com>

* feat(Tabs): add ellipsis overflow to fullWidth tabs and add test stories

Co-authored-by: Guilherme Datilio Ribeiro <guilhermedatilio@gmail.com>

* fix(Tabs): mock useMatchMedia for tests and add fullWidth tests

Co-authored-by: Guilherme Datilio Ribeiro <guilhermedatilio@gmail.com>

* fix: update snapshot

* fix: add more docs around tabs in the grid

Co-authored-by: Guilherme Datilio Ribeiro <guilhermedatilio@gmail.com>

* fix: format

* chore(Tabs): remove test story

---------

Co-authored-by: Guilherme Datilio Ribeiro <guilhermedatilio@gmail.com>
  • Loading branch information
francinelucca and guidari authored Jun 22, 2023
1 parent c765127 commit 11c70f3
Show file tree
Hide file tree
Showing 7 changed files with 376 additions and 3 deletions.
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 @@ -70,6 +70,14 @@ test.describe('Tabs', () => {
});
});

test('contained fullWidth @vrt', async ({ page }) => {
await snapshotStory(page, {
component: 'Tabs',
id: 'components-tabs--contained-full-width',
theme,
});
});

test('contained with secondary labels @vrt', async ({ page }) => {
await snapshotStory(page, {
component: 'Tabs',
Expand Down
3 changes: 3 additions & 0 deletions packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -7605,6 +7605,9 @@ Map {
"contained": Object {
"type": "bool",
},
"fullWidth": Object {
"type": "bool",
},
"iconSize": Object {
"args": Array [
Array [
Expand Down
106 changes: 106 additions & 0 deletions packages/react/src/components/Tabs/Tabs-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,16 @@ import { Tabs, Tab, TabPanel, TabPanels, TabList } from './Tabs';
import { act } from 'react-dom/test-utils';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import * as hooks from '../../internal/useMatchMedia';

const prefix = 'cds';

describe('Tabs', () => {
beforeEach(() => {
jest.resetModules();
jest.spyOn(hooks, 'useMatchMedia').mockImplementation(() => true);
});

it('should update selected index based on the default provided', () => {
render(
<Tabs defaultSelectedIndex={1}>
Expand Down Expand Up @@ -53,6 +59,11 @@ describe('Tabs', () => {
});

describe('Tab', () => {
beforeEach(() => {
jest.resetModules();
jest.spyOn(hooks, 'useMatchMedia').mockImplementation(() => true);
});

it('should set a className from props on outermost element in Tab', () => {
render(
<Tabs>
Expand Down Expand Up @@ -437,6 +448,11 @@ describe('Tab', () => {
});

describe('TabPanel', () => {
beforeEach(() => {
jest.resetModules();
jest.spyOn(hooks, 'useMatchMedia').mockImplementation(() => true);
});

it('should have a className if provided by props', () => {
render(
<Tabs>
Expand Down Expand Up @@ -536,3 +552,93 @@ describe('TabPanel', () => {
expect(screen.getByText('Tab Panel 1')).toHaveAttribute('tabIndex', '0');
});
});

describe('TabList', () => {
it('should span fullWidth if lg and fullWidth prop is passed in', () => {
jest.spyOn(hooks, 'useMatchMedia').mockImplementation(() => true);
const { container } = render(
<Tabs>
<TabList aria-label="List of tabs" contained fullWidth>
<Tab>Tab Label 1</Tab>
<Tab>Tab Label 2</Tab>
<Tab>Tab Label 3</Tab>
</TabList>
<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>
</Tabs>
);

expect(container.firstChild).toHaveClass(`${prefix}--tabs--full-width`);
});

it('should ignore fullWidth prop if screen smaller than lg', () => {
jest.spyOn(hooks, 'useMatchMedia').mockImplementation(() => false);
const { container } = render(
<Tabs>
<TabList aria-label="List of tabs" contained fullWidth>
<Tab>Tab Label 1</Tab>
<Tab>Tab Label 2</Tab>
<Tab>Tab Label 3</Tab>
</TabList>
<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>
</Tabs>
);

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

it('should ignore fullWidth prop if tabs are not contained', () => {
jest.spyOn(hooks, 'useMatchMedia').mockImplementation(() => true);
const { container } = render(
<Tabs>
<TabList aria-label="List of tabs" fullWidth>
<Tab>Tab Label 1</Tab>
<Tab>Tab Label 2</Tab>
<Tab>Tab Label 3</Tab>
</TabList>
<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>
</Tabs>
);

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

it('should not be fullWidth in default state', () => {
jest.spyOn(hooks, 'useMatchMedia').mockImplementation(() => true);
const { container } = render(
<Tabs>
<TabList aria-label="List of tabs" contained>
<Tab>Tab Label 1</Tab>
<Tab>Tab Label 2</Tab>
<Tab>Tab Label 3</Tab>
</TabList>
<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>
</Tabs>
);

expect(container.firstChild).not.toHaveClass(`${prefix}--tabs--full-width`);
});
});
160 changes: 159 additions & 1 deletion packages/react/src/components/Tabs/Tabs.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Props, Preview, Story } from '@storybook/addon-docs';
import { Props, Preview, Story, Canvas } from '@storybook/addon-docs';
import { Tabs, TabList, Tab, TabPanels, TabPanel } from './Tabs';
import { Grid, Column } from '../Grid'

# Tabs

Expand All @@ -9,6 +10,8 @@ import { Tabs, TabList, Tab, TabPanels, TabPanel } from './Tabs';
&nbsp;|&nbsp;
[Accessibility](https://www.carbondesignsystem.com/components/tabs/accessibility)

<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Table of Contents

- [Overview](#overview)
Expand All @@ -18,12 +21,15 @@ import { Tabs, TabList, Tab, TabPanels, TabPanel } from './Tabs';
- [Dismissable Tabs](#dismissable-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)
- [V11](#v11)
- [Tabs composition](#tabs-composition)
- [Various updates](#various-updates)
- [Max width](#max-width)
- [Feedback](#feedback)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->

## Overview

Use tabs to allow users to navigate easily between views within the same
Expand Down Expand Up @@ -201,6 +207,158 @@ loaded when the Tab is clicked. In v11, to do this, you can this by setting
</Tabs>
```

### Tabs and the Grid - fullWidth prop

By default, a `Tab` component is only as wide as it's content. This posses difficulties when trying to align tabs to the grid.
Alternatively, you may choose to use the `fullWidth` prop to allow `Tab` elements to grow as wide as their container allows.

Note that this feature is *only available* for `contained` tabs in large and extra large screen sizes.
The prop is a no-op for smaller screens and will also be ignored for `TabList`s with more than 8 tabs.
`fullWidth` paired up with a wrapping `Grid` component will allow for "grid-aware" tabs:

<Canvas>
<Grid condensed>
<Column lg={16} md={8} sm={4}>
<Tabs>
<TabList aria-label="List of tabs" contained fullWidth>
<Tab>Tab Label 1</Tab>
<Tab>Tab Label 2</Tab>
<Tab disabled>Tab Label 3</Tab>
</TabList>
<TabPanels>
<TabPanel>Tab Panel 1</TabPanel>
<TabPanel>Tab Panel 2</TabPanel>
<TabPanel>Tab Panel 3</TabPanel>
</TabPanels>
</Tabs>
</Column>
</Grid>
</Canvas>

```jsx
<Grid condensed>
<Column lg={16} md={8} sm={4}>
<Tabs>
<TabList aria-label="List of tabs" contained fullWidth>
<Tab>Tab Label 1</Tab>
<Tab>Tab Label 2</Tab>
<Tab disabled>Tab Label 3</Tab>
</TabList>
<TabPanels>
<TabPanel>Tab Panel 1</TabPanel>
<TabPanel>Tab Panel 2</TabPanel>
<TabPanel>Tab Panel 3</TabPanel>
</TabPanels>
</Tabs>
</Column>
</Grid>
```

Using the `fullWidth` prop alone within a `Grid` makes it so that the `Tabs` container aligns to the `Grid`, but not the individual `Tab` items;
to have each individual `Tab` take up exactly one or many columns within the `Grid`, you must specify the number of columns as a multiple of the number of `Tab` items within the `TabList`.

For example, to have 5 tabs and each tab span exactly two columns:

<Canvas>
<Grid condensed>
<Column lg={10}>
<Tabs>
<TabList aria-label="List of tabs" contained fullWidth>
<Tab>Tab Label 1</Tab>
<Tab>Tab Label 2</Tab>
<Tab disabled>Tab Label 3</Tab>
<Tab>Tab Label 4</Tab>
<Tab>Tab Label 5</Tab>
</TabList>
<TabPanels>
<TabPanel>Tab Panel 1</TabPanel>
<TabPanel>Tab Panel 2</TabPanel>
<TabPanel>Tab Panel 3</TabPanel>
<TabPanel>Tab Panel 4</TabPanel>
<TabPanel>Tab Panel 5</TabPanel>
</TabPanels>
</Tabs>
</Column>
</Grid>
</Canvas>

```jsx
<Grid condensed>
<Column lg={10}>
<Tabs>
<TabList aria-label="List of tabs" contained fullWidth>
<Tab>Tab Label 1</Tab>
<Tab>Tab Label 2</Tab>
<Tab disabled>Tab Label 3</Tab>
<Tab>Tab Label 4</Tab>
<Tab>Tab Label 5</Tab>
</TabList>
<TabPanels>
<TabPanel>Tab Panel 1</TabPanel>
<TabPanel>Tab Panel 2</TabPanel>
<TabPanel>Tab Panel 3</TabPanel>
<TabPanel>Tab Panel 4</TabPanel>
<TabPanel>Tab Panel 5</TabPanel>
</TabPanels>
</Tabs>
</Column>
</Grid>
```

Or, to have 5 tabs and each tab span exactly three columns:

<Canvas>
<Grid condensed>
<Column lg={15}>
<Tabs>
<TabList aria-label="List of tabs" contained fullWidth>
<Tab>Tab Label 1</Tab>
<Tab>Tab Label 2</Tab>
<Tab disabled>Tab Label 3</Tab>
<Tab>Tab Label 4</Tab>
<Tab>Tab Label 5</Tab>
</TabList>
<TabPanels>
<TabPanel>Tab Panel 1</TabPanel>
<TabPanel>Tab Panel 2</TabPanel>
<TabPanel>Tab Panel 3</TabPanel>
<TabPanel>Tab Panel 4</TabPanel>
<TabPanel>Tab Panel 5</TabPanel>
</TabPanels>
</Tabs>
</Column>
</Grid>
</Canvas>

```jsx
<Grid condensed>
<Column lg={15}>
<Tabs>
<TabList aria-label="List of tabs" contained fullWidth>
<Tab>Tab Label 1</Tab>
<Tab>Tab Label 2</Tab>
<Tab disabled>Tab Label 3</Tab>
<Tab>Tab Label 4</Tab>
<Tab>Tab Label 5</Tab>
</TabList>
<TabPanels>
<TabPanel>Tab Panel 1</TabPanel>
<TabPanel>Tab Panel 2</TabPanel>
<TabPanel>Tab Panel 3</TabPanel>
<TabPanel>Tab Panel 4</TabPanel>
<TabPanel>Tab Panel 5</TabPanel>
</TabPanels>
</Tabs>
</Column>
</Grid>
```







## V11

### Tabs composition
Expand Down
Loading

0 comments on commit 11c70f3

Please sign in to comment.