Skip to content

Commit

Permalink
[docs] Create the TabsUnstyled docs (#32023)
Browse files Browse the repository at this point in the history
  • Loading branch information
michaldudak authored Mar 29, 2022
1 parent bceda9f commit 263a2d3
Show file tree
Hide file tree
Showing 31 changed files with 340 additions and 282 deletions.
98 changes: 98 additions & 0 deletions docs/data/base/components/tabs/KeyboardNavigation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import * as React from 'react';
import { styled } from '@mui/system';
import { buttonUnstyledClasses } from '@mui/base/ButtonUnstyled';
import TabsUnstyled from '@mui/base/TabsUnstyled';
import TabUnstyled, { tabUnstyledClasses } from '@mui/base/TabUnstyled';
import TabsListUnstyled from '@mui/base/TabsListUnstyled';

const blue = {
50: '#F0F7FF',
100: '#C2E0FF',
200: '#80BFFF',
300: '#66B2FF',
400: '#3399FF',
500: '#007FFF',
600: '#0072E5',
700: '#0059B2',
800: '#004C99',
900: '#003A75',
};

const Tab = styled(TabUnstyled)`
font-family: IBM Plex Sans, sans-serif;
color: white;
cursor: pointer;
font-size: 0.875rem;
font-weight: bold;
background-color: transparent;
width: 100%;
padding: 12px 16px;
margin: 6px 6px;
border: none;
border-radius: 5px;
display: flex;
justify-content: center;
&:hover {
background-color: ${blue[400]};
}
&:focus {
color: #fff;
border-radius: 3px;
outline: 2px solid ${blue[200]};
outline-offset: 2px;
}
&.${tabUnstyledClasses.selected} {
background-color: ${blue[50]};
color: ${blue[600]};
}
&.${buttonUnstyledClasses.disabled} {
opacity: 0.5;
cursor: not-allowed;
}
`;

const TabsList = styled(TabsListUnstyled)`
min-width: 320px;
background-color: ${blue[500]};
border-radius: 8px;
margin-bottom: 16px;
display: flex;
align-items: center;
justify-content: center;
align-content: space-between;
`;

export default function AccessibleTabs1() {
return (
<div>
<p>Selection following focus:</p>
<TabsUnstyled
defaultValue={0}
aria-label="Tabs where selection follows focus"
selectionFollowsFocus
>
<TabsList>
<Tab>One</Tab>
<Tab>Two</Tab>
<Tab>Three</Tab>
</TabsList>
</TabsUnstyled>

<p>Selection independent of focus (default behavior):</p>
<TabsUnstyled
defaultValue={0}
aria-label="Tabs where selection does not follow focus"
>
<TabsList>
<Tab>One</Tab>
<Tab>Two</Tab>
<Tab>Three</Tab>
</TabsList>
</TabsUnstyled>
</div>
);
}
98 changes: 98 additions & 0 deletions docs/data/base/components/tabs/KeyboardNavigation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import * as React from 'react';
import { styled } from '@mui/system';
import { buttonUnstyledClasses } from '@mui/base/ButtonUnstyled';
import TabsUnstyled from '@mui/base/TabsUnstyled';
import TabUnstyled, { tabUnstyledClasses } from '@mui/base/TabUnstyled';
import TabsListUnstyled from '@mui/base/TabsListUnstyled';

const blue = {
50: '#F0F7FF',
100: '#C2E0FF',
200: '#80BFFF',
300: '#66B2FF',
400: '#3399FF',
500: '#007FFF',
600: '#0072E5',
700: '#0059B2',
800: '#004C99',
900: '#003A75',
};

const Tab = styled(TabUnstyled)`
font-family: IBM Plex Sans, sans-serif;
color: white;
cursor: pointer;
font-size: 0.875rem;
font-weight: bold;
background-color: transparent;
width: 100%;
padding: 12px 16px;
margin: 6px 6px;
border: none;
border-radius: 5px;
display: flex;
justify-content: center;
&:hover {
background-color: ${blue[400]};
}
&:focus {
color: #fff;
border-radius: 3px;
outline: 2px solid ${blue[200]};
outline-offset: 2px;
}
&.${tabUnstyledClasses.selected} {
background-color: ${blue[50]};
color: ${blue[600]};
}
&.${buttonUnstyledClasses.disabled} {
opacity: 0.5;
cursor: not-allowed;
}
`;

const TabsList = styled(TabsListUnstyled)`
min-width: 320px;
background-color: ${blue[500]};
border-radius: 8px;
margin-bottom: 16px;
display: flex;
align-items: center;
justify-content: center;
align-content: space-between;
`;

export default function AccessibleTabs1() {
return (
<div>
<p>Selection following focus:</p>
<TabsUnstyled
defaultValue={0}
aria-label="Tabs where selection follows focus"
selectionFollowsFocus
>
<TabsList>
<Tab>One</Tab>
<Tab>Two</Tab>
<Tab>Three</Tab>
</TabsList>
</TabsUnstyled>

<p>Selection independent of focus (default behavior):</p>
<TabsUnstyled
defaultValue={0}
aria-label="Tabs where selection does not follow focus"
>
<TabsList>
<Tab>One</Tab>
<Tab>Two</Tab>
<Tab>Three</Tab>
</TabsList>
</TabsUnstyled>
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ export default function UnstyledTabsBasic() {
<TabUnstyled>Two</TabUnstyled>
<TabUnstyled>Three</TabUnstyled>
</TabsListUnstyled>
<TabPanelUnstyled value={0}>First content</TabPanelUnstyled>
<TabPanelUnstyled value={1}>Second content</TabPanelUnstyled>
<TabPanelUnstyled value={2}>Third content</TabPanelUnstyled>
<TabPanelUnstyled value={0}>First page</TabPanelUnstyled>
<TabPanelUnstyled value={1}>Second page</TabPanelUnstyled>
<TabPanelUnstyled value={2}>Third page</TabPanelUnstyled>
</TabsUnstyled>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ export default function UnstyledTabsBasic() {
<TabUnstyled>Two</TabUnstyled>
<TabUnstyled>Three</TabUnstyled>
</TabsListUnstyled>
<TabPanelUnstyled value={0}>First content</TabPanelUnstyled>
<TabPanelUnstyled value={1}>Second content</TabPanelUnstyled>
<TabPanelUnstyled value={2}>Third content</TabPanelUnstyled>
<TabPanelUnstyled value={0}>First page</TabPanelUnstyled>
<TabPanelUnstyled value={1}>Second page</TabPanelUnstyled>
<TabPanelUnstyled value={2}>Third page</TabPanelUnstyled>
</TabsUnstyled>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<TabUnstyled>Two</TabUnstyled>
<TabUnstyled>Three</TabUnstyled>
</TabsListUnstyled>
<TabPanelUnstyled value={0}>First content</TabPanelUnstyled>
<TabPanelUnstyled value={1}>Second content</TabPanelUnstyled>
<TabPanelUnstyled value={2}>Third content</TabPanelUnstyled>
<TabPanelUnstyled value={0}>First page</TabPanelUnstyled>
<TabPanelUnstyled value={1}>Second page</TabPanelUnstyled>
<TabPanelUnstyled value={2}>Third page</TabPanelUnstyled>
</TabsUnstyled>
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,9 @@ export default function UnstyledTabsCustomized() {
<Tab>Two</Tab>
<Tab>Three</Tab>
</TabsList>
<TabPanel value={0}>First content</TabPanel>
<TabPanel value={1}>Second content</TabPanel>
<TabPanel value={2}>Third content</TabPanel>
<TabPanel value={0}>First page</TabPanel>
<TabPanel value={1}>Second page</TabPanel>
<TabPanel value={2}>Third page</TabPanel>
</TabsUnstyled>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,9 @@ export default function UnstyledTabsCustomized() {
<Tab>Two</Tab>
<Tab>Three</Tab>
</TabsList>
<TabPanel value={0}>First content</TabPanel>
<TabPanel value={1}>Second content</TabPanel>
<TabPanel value={2}>Third content</TabPanel>
<TabPanel value={0}>First page</TabPanel>
<TabPanel value={1}>Second page</TabPanel>
<TabPanel value={2}>Third page</TabPanel>
</TabsUnstyled>
);
}
10 changes: 10 additions & 0 deletions docs/data/base/components/tabs/UnstyledTabsCustomized.tsx.preview
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<TabsUnstyled defaultValue={0}>
<TabsList>
<Tab>One</Tab>
<Tab>Two</Tab>
<Tab>Three</Tab>
</TabsList>
<TabPanel value={0}>First page</TabPanel>
<TabPanel value={1}>Second page</TabPanel>
<TabPanel value={2}>Third page</TabPanel>
</TabsUnstyled>
87 changes: 87 additions & 0 deletions docs/data/base/components/tabs/tabs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
---
product: base
title: React Tabs component
components: TabsUnstyled, TabUnstyled, TabPanelUnstyled, TabsListUnstyled
githubLabel: 'component: tabs'
waiAria: https://www.w3.org/TR/wai-aria-practices/#tabpanel
packageName: '@mui/base'
---

# Tabs

<p class="description">Tabs make it easy to explore and switch between different views.</p>

Tabs organize and allow navigation between groups of content that are related and at the same level of hierarchy.

{{"component": "modules/components/ComponentLinkHeader.js", "design": false}}

## Basic tabs

Tabs are implemented using a handful of components, each with a specific purpose:

- TabUnstyled - an actual tab. Clicking on it displays the associated content.
- TabsListUnstyled - container for tabs. Responsible for handling focus and keyboard navigation between tabs.
- TabPanelUnstyled - hosts the content associated with a tab.
- TabsUnstyled - top-level component wrapping TabsListUnstyled and TabPanelUnstyled and letting them communicate.

```js
import TabsUnstyled from '@mui/base/TabsUnstyled';
import TabsListUnstyled from '@mui/base/TabUnstyled';
import TabUnstyled from '@mui/base/TabUnstyled';
import TabPanelUnstyled from '@mui/base/TabPanelUnstyled';
```

You can associate a tab with tab panel using the `value` prop.
If a tab with a given `value` is selected, a tab panel with a matching `value` will be shown.

Note that setting a `value` on a `TabUnstyled` is not mandatory.
If you omit this prop, it will default to order of a tab within its parent TabsListUnstyled.
This order is 0-based (the first tab has its `value` set to `0`, the next one `1`, etc.).

{{"demo": "UnstyledTabsBasic.js"}}

## Customizing the root element

By default, the `TabUnstyled` renders a native `button` element.
You are free to override this by setting the `component` or `components.Root` prop.
If a non-interactive element (such as a span) is provided this way, the `TabUnstyled` will take care of adding accessibility attributes.

The `TabPanelUnstyled`, on the other hand, renders a native `div` element by default.
You are free to override this as well by setting the `component` or `components.Root` prop on the `TabPanelUnstyled`.

{{"demo": "UnstyledTabsCustomized.js"}}

## Third-party routing library

One frequent use case is to perform navigation on the client only, without an HTTP round-trip to the server.
The `Tab` component provides the `component` prop to handle this use case.
Here is a [more detailed guide](/guides/routing/#tabs).

## Accessibility

(WAI-ARIA: https://www.w3.org/TR/wai-aria-practices/#tabpanel)

The following steps are needed in order to provide necessary information for assistive technologies:

1. Label `TabsUnstyled` via `aria-label` or `aria-labelledby`.
2. `TabUnstyled`s need to be connected to their corresponding tab panels by setting the correct `id`, `aria-controls` and `aria-labelledby`.

An example for the current implementation can be found in the demos on this page.

### Keyboard navigation

The components implement keyboard navigation using the "manual activation" behavior.
If you want to switch to the "selection automatically follows focus" behavior you have pass `selectionFollowsFocus` to the `Tabs` component.
The WAI-ARIA authoring practices have a detailed guide on [how to decide when to make selection automatically follow focus](https://www.w3.org/TR/wai-aria-practices/#kbd_selection_follows_focus).

#### Demo

The following two demos only differ in their keyboard navigation behavior.
Focus a tab and navigate with arrow keys to notice the difference, e.g. <kbd class="key">Arrow Right</kbd>.

```jsx
/* Tabs where selection follows focus */
<TabsUnstyled selectionFollowsFocus />
```

{{"demo": "KeyboardNavigation.js"}}
5 changes: 4 additions & 1 deletion docs/data/base/pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ const pages = [
{
pathname: '/base/components/navigation',
subheader: 'navigation',
children: [{ pathname: '/base/react-menu', title: 'Menu' }],
children: [
{ pathname: '/base/react-menu', title: 'Menu' },
{ pathname: '/base/react-tabs', title: 'Tabs' },
],
},
{
pathname: '/base/components/utils',
Expand Down

This file was deleted.

Loading

0 comments on commit 263a2d3

Please sign in to comment.