Skip to content

Commit 11c70f3

Browse files
feat(Tabs): Contained tabs on the grid (#13927)
* 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>
1 parent c765127 commit 11c70f3

7 files changed

Lines changed: 376 additions & 3 deletions

File tree

e2e/components/Tabs/Tabs-test.e2e.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,14 @@ test.describe('Tabs', () => {
7070
});
7171
});
7272

73+
test('contained fullWidth @vrt', async ({ page }) => {
74+
await snapshotStory(page, {
75+
component: 'Tabs',
76+
id: 'components-tabs--contained-full-width',
77+
theme,
78+
});
79+
});
80+
7381
test('contained with secondary labels @vrt', async ({ page }) => {
7482
await snapshotStory(page, {
7583
component: 'Tabs',

packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7605,6 +7605,9 @@ Map {
76057605
"contained": Object {
76067606
"type": "bool",
76077607
},
7608+
"fullWidth": Object {
7609+
"type": "bool",
7610+
},
76087611
"iconSize": Object {
76097612
"args": Array [
76107613
Array [

packages/react/src/components/Tabs/Tabs-test.js

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,16 @@ import { Tabs, Tab, TabPanel, TabPanels, TabList } from './Tabs';
33
import { act } from 'react-dom/test-utils';
44
import { render, screen } from '@testing-library/react';
55
import userEvent from '@testing-library/user-event';
6+
import * as hooks from '../../internal/useMatchMedia';
67

78
const prefix = 'cds';
89

910
describe('Tabs', () => {
11+
beforeEach(() => {
12+
jest.resetModules();
13+
jest.spyOn(hooks, 'useMatchMedia').mockImplementation(() => true);
14+
});
15+
1016
it('should update selected index based on the default provided', () => {
1117
render(
1218
<Tabs defaultSelectedIndex={1}>
@@ -53,6 +59,11 @@ describe('Tabs', () => {
5359
});
5460

5561
describe('Tab', () => {
62+
beforeEach(() => {
63+
jest.resetModules();
64+
jest.spyOn(hooks, 'useMatchMedia').mockImplementation(() => true);
65+
});
66+
5667
it('should set a className from props on outermost element in Tab', () => {
5768
render(
5869
<Tabs>
@@ -437,6 +448,11 @@ describe('Tab', () => {
437448
});
438449

439450
describe('TabPanel', () => {
451+
beforeEach(() => {
452+
jest.resetModules();
453+
jest.spyOn(hooks, 'useMatchMedia').mockImplementation(() => true);
454+
});
455+
440456
it('should have a className if provided by props', () => {
441457
render(
442458
<Tabs>
@@ -536,3 +552,93 @@ describe('TabPanel', () => {
536552
expect(screen.getByText('Tab Panel 1')).toHaveAttribute('tabIndex', '0');
537553
});
538554
});
555+
556+
describe('TabList', () => {
557+
it('should span fullWidth if lg and fullWidth prop is passed in', () => {
558+
jest.spyOn(hooks, 'useMatchMedia').mockImplementation(() => true);
559+
const { container } = render(
560+
<Tabs>
561+
<TabList aria-label="List of tabs" contained fullWidth>
562+
<Tab>Tab Label 1</Tab>
563+
<Tab>Tab Label 2</Tab>
564+
<Tab>Tab Label 3</Tab>
565+
</TabList>
566+
<TabPanels>
567+
<TabPanel className="custom-class">
568+
Tab Panel 1<button type="button">Submit</button>
569+
</TabPanel>
570+
<TabPanel>Tab Panel 2</TabPanel>
571+
<TabPanel>Tab Panel 3</TabPanel>
572+
</TabPanels>
573+
</Tabs>
574+
);
575+
576+
expect(container.firstChild).toHaveClass(`${prefix}--tabs--full-width`);
577+
});
578+
579+
it('should ignore fullWidth prop if screen smaller than lg', () => {
580+
jest.spyOn(hooks, 'useMatchMedia').mockImplementation(() => false);
581+
const { container } = render(
582+
<Tabs>
583+
<TabList aria-label="List of tabs" contained fullWidth>
584+
<Tab>Tab Label 1</Tab>
585+
<Tab>Tab Label 2</Tab>
586+
<Tab>Tab Label 3</Tab>
587+
</TabList>
588+
<TabPanels>
589+
<TabPanel className="custom-class">
590+
Tab Panel 1<button type="button">Submit</button>
591+
</TabPanel>
592+
<TabPanel>Tab Panel 2</TabPanel>
593+
<TabPanel>Tab Panel 3</TabPanel>
594+
</TabPanels>
595+
</Tabs>
596+
);
597+
598+
expect(container.firstChild).not.toHaveClass(`${prefix}--tabs--full-width`);
599+
});
600+
601+
it('should ignore fullWidth prop if tabs are not contained', () => {
602+
jest.spyOn(hooks, 'useMatchMedia').mockImplementation(() => true);
603+
const { container } = render(
604+
<Tabs>
605+
<TabList aria-label="List of tabs" fullWidth>
606+
<Tab>Tab Label 1</Tab>
607+
<Tab>Tab Label 2</Tab>
608+
<Tab>Tab Label 3</Tab>
609+
</TabList>
610+
<TabPanels>
611+
<TabPanel className="custom-class">
612+
Tab Panel 1<button type="button">Submit</button>
613+
</TabPanel>
614+
<TabPanel>Tab Panel 2</TabPanel>
615+
<TabPanel>Tab Panel 3</TabPanel>
616+
</TabPanels>
617+
</Tabs>
618+
);
619+
620+
expect(container.firstChild).not.toHaveClass(`${prefix}--tabs--full-width`);
621+
});
622+
623+
it('should not be fullWidth in default state', () => {
624+
jest.spyOn(hooks, 'useMatchMedia').mockImplementation(() => true);
625+
const { container } = render(
626+
<Tabs>
627+
<TabList aria-label="List of tabs" contained>
628+
<Tab>Tab Label 1</Tab>
629+
<Tab>Tab Label 2</Tab>
630+
<Tab>Tab Label 3</Tab>
631+
</TabList>
632+
<TabPanels>
633+
<TabPanel className="custom-class">
634+
Tab Panel 1<button type="button">Submit</button>
635+
</TabPanel>
636+
<TabPanel>Tab Panel 2</TabPanel>
637+
<TabPanel>Tab Panel 3</TabPanel>
638+
</TabPanels>
639+
</Tabs>
640+
);
641+
642+
expect(container.firstChild).not.toHaveClass(`${prefix}--tabs--full-width`);
643+
});
644+
});

packages/react/src/components/Tabs/Tabs.mdx

Lines changed: 159 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { Props, Preview, Story } from '@storybook/addon-docs';
1+
import { Props, Preview, Story, Canvas } from '@storybook/addon-docs';
22
import { Tabs, TabList, Tab, TabPanels, TabPanel } from './Tabs';
3+
import { Grid, Column } from '../Grid'
34

45
# Tabs
56

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

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

1417
- [Overview](#overview)
@@ -18,12 +21,15 @@ import { Tabs, TabList, Tab, TabPanels, TabPanel } from './Tabs';
1821
- [Dismissable Tabs](#dismissable-tabs)
1922
- [Component API](#component-api)
2023
- [Tab - render content on click](#tab---render-content-on-click)
24+
- [Tabs and the Grid - fullWidth prop](#tabs-and-the-grid---fullwidth-prop)
2125
- [V11](#v11)
2226
- [Tabs composition](#tabs-composition)
2327
- [Various updates](#various-updates)
2428
- [Max width](#max-width)
2529
- [Feedback](#feedback)
2630

31+
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
32+
2733
## Overview
2834

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

210+
### Tabs and the Grid - fullWidth prop
211+
212+
By default, a `Tab` component is only as wide as it's content. This posses difficulties when trying to align tabs to the grid.
213+
Alternatively, you may choose to use the `fullWidth` prop to allow `Tab` elements to grow as wide as their container allows.
214+
215+
Note that this feature is *only available* for `contained` tabs in large and extra large screen sizes.
216+
The prop is a no-op for smaller screens and will also be ignored for `TabList`s with more than 8 tabs.
217+
`fullWidth` paired up with a wrapping `Grid` component will allow for "grid-aware" tabs:
218+
219+
<Canvas>
220+
<Grid condensed>
221+
<Column lg={16} md={8} sm={4}>
222+
<Tabs>
223+
<TabList aria-label="List of tabs" contained fullWidth>
224+
<Tab>Tab Label 1</Tab>
225+
<Tab>Tab Label 2</Tab>
226+
<Tab disabled>Tab Label 3</Tab>
227+
</TabList>
228+
<TabPanels>
229+
<TabPanel>Tab Panel 1</TabPanel>
230+
<TabPanel>Tab Panel 2</TabPanel>
231+
<TabPanel>Tab Panel 3</TabPanel>
232+
</TabPanels>
233+
</Tabs>
234+
</Column>
235+
</Grid>
236+
</Canvas>
237+
238+
```jsx
239+
<Grid condensed>
240+
<Column lg={16} md={8} sm={4}>
241+
<Tabs>
242+
<TabList aria-label="List of tabs" contained fullWidth>
243+
<Tab>Tab Label 1</Tab>
244+
<Tab>Tab Label 2</Tab>
245+
<Tab disabled>Tab Label 3</Tab>
246+
</TabList>
247+
<TabPanels>
248+
<TabPanel>Tab Panel 1</TabPanel>
249+
<TabPanel>Tab Panel 2</TabPanel>
250+
<TabPanel>Tab Panel 3</TabPanel>
251+
</TabPanels>
252+
</Tabs>
253+
</Column>
254+
</Grid>
255+
```
256+
257+
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;
258+
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`.
259+
260+
For example, to have 5 tabs and each tab span exactly two columns:
261+
262+
<Canvas>
263+
<Grid condensed>
264+
<Column lg={10}>
265+
<Tabs>
266+
<TabList aria-label="List of tabs" contained fullWidth>
267+
<Tab>Tab Label 1</Tab>
268+
<Tab>Tab Label 2</Tab>
269+
<Tab disabled>Tab Label 3</Tab>
270+
<Tab>Tab Label 4</Tab>
271+
<Tab>Tab Label 5</Tab>
272+
</TabList>
273+
<TabPanels>
274+
<TabPanel>Tab Panel 1</TabPanel>
275+
<TabPanel>Tab Panel 2</TabPanel>
276+
<TabPanel>Tab Panel 3</TabPanel>
277+
<TabPanel>Tab Panel 4</TabPanel>
278+
<TabPanel>Tab Panel 5</TabPanel>
279+
</TabPanels>
280+
</Tabs>
281+
</Column>
282+
</Grid>
283+
</Canvas>
284+
285+
```jsx
286+
<Grid condensed>
287+
<Column lg={10}>
288+
<Tabs>
289+
<TabList aria-label="List of tabs" contained fullWidth>
290+
<Tab>Tab Label 1</Tab>
291+
<Tab>Tab Label 2</Tab>
292+
<Tab disabled>Tab Label 3</Tab>
293+
<Tab>Tab Label 4</Tab>
294+
<Tab>Tab Label 5</Tab>
295+
</TabList>
296+
<TabPanels>
297+
<TabPanel>Tab Panel 1</TabPanel>
298+
<TabPanel>Tab Panel 2</TabPanel>
299+
<TabPanel>Tab Panel 3</TabPanel>
300+
<TabPanel>Tab Panel 4</TabPanel>
301+
<TabPanel>Tab Panel 5</TabPanel>
302+
</TabPanels>
303+
</Tabs>
304+
</Column>
305+
</Grid>
306+
```
307+
308+
Or, to have 5 tabs and each tab span exactly three columns:
309+
310+
<Canvas>
311+
<Grid condensed>
312+
<Column lg={15}>
313+
<Tabs>
314+
<TabList aria-label="List of tabs" contained fullWidth>
315+
<Tab>Tab Label 1</Tab>
316+
<Tab>Tab Label 2</Tab>
317+
<Tab disabled>Tab Label 3</Tab>
318+
<Tab>Tab Label 4</Tab>
319+
<Tab>Tab Label 5</Tab>
320+
</TabList>
321+
<TabPanels>
322+
<TabPanel>Tab Panel 1</TabPanel>
323+
<TabPanel>Tab Panel 2</TabPanel>
324+
<TabPanel>Tab Panel 3</TabPanel>
325+
<TabPanel>Tab Panel 4</TabPanel>
326+
<TabPanel>Tab Panel 5</TabPanel>
327+
</TabPanels>
328+
</Tabs>
329+
</Column>
330+
</Grid>
331+
</Canvas>
332+
333+
```jsx
334+
<Grid condensed>
335+
<Column lg={15}>
336+
<Tabs>
337+
<TabList aria-label="List of tabs" contained fullWidth>
338+
<Tab>Tab Label 1</Tab>
339+
<Tab>Tab Label 2</Tab>
340+
<Tab disabled>Tab Label 3</Tab>
341+
<Tab>Tab Label 4</Tab>
342+
<Tab>Tab Label 5</Tab>
343+
</TabList>
344+
<TabPanels>
345+
<TabPanel>Tab Panel 1</TabPanel>
346+
<TabPanel>Tab Panel 2</TabPanel>
347+
<TabPanel>Tab Panel 3</TabPanel>
348+
<TabPanel>Tab Panel 4</TabPanel>
349+
<TabPanel>Tab Panel 5</TabPanel>
350+
</TabPanels>
351+
</Tabs>
352+
</Column>
353+
</Grid>
354+
```
355+
356+
357+
358+
359+
360+
361+
204362
## V11
205363

206364
### Tabs composition

0 commit comments

Comments
 (0)