Skip to content

Commit

Permalink
[lab] Add new experimental Tabs API (mui#20806)
Browse files Browse the repository at this point in the history
  • Loading branch information
eps1lon authored May 6, 2020
1 parent 939205f commit 3426f17
Show file tree
Hide file tree
Showing 28 changed files with 775 additions and 1 deletion.
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ module.exports = {
// tests are not driven by assistive technology
// add `jsx-a11y` rules once you encounter them in tests
'jsx-a11y/click-events-have-key-events': 'off',
'jsx-a11y/control-has-associated-label': 'off',
'jsx-a11y/iframe-has-title': 'off',
'jsx-a11y/mouse-events-have-key-events': 'off',
'jsx-a11y/no-noninteractive-tabindex': 'off',
Expand Down
15 changes: 15 additions & 0 deletions docs/pages/api-docs/tab-context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';
import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs';
import { prepareMarkdown } from 'docs/src/modules/utils/parseMarkdown';

const pageFilename = 'api/tab-context';
const requireRaw = require.context('!raw-loader!./', false, /\/tab-context\.md$/);

export default function Page({ docs }) {
return <MarkdownDocs docs={docs} />;
}

Page.getInitialProps = () => {
const { demos, docs } = prepareMarkdown({ pageFilename, requireRaw });
return { demos, docs };
};
35 changes: 35 additions & 0 deletions docs/pages/api-docs/tab-context.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
filename: /packages/material-ui-lab/src/TabContext/TabContext.js
---

<!--- This documentation is automatically generated, do not try to edit it. -->

# TabContext API

<p class="description">The API documentation of the TabContext React component. Learn more about the props and the CSS customization points.</p>

## Import

```js
import TabContext from '@material-ui/lab/TabContext';
// or
import { TabContext } from '@material-ui/lab';
```

You can learn more about the difference by [reading this guide](/guides/minimizing-bundle-size/).





## Props

| Name | Type | Default | Description |
|:-----|:-----|:--------|:------------|
| <span class="prop-name">children</span> | <span class="prop-type">node</span> | | The content of the component. |
| <span class="prop-name required">value&nbsp;*</span> | <span class="prop-type">string</span> | | The value of the currently selected `Tab`. |

The component cannot hold a ref.

Any other props supplied will be provided to the root element (native element).

15 changes: 15 additions & 0 deletions docs/pages/api-docs/tab-list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';
import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs';
import { prepareMarkdown } from 'docs/src/modules/utils/parseMarkdown';

const pageFilename = 'api/tab-list';
const requireRaw = require.context('!raw-loader!./', false, /\/tab-list\.md$/);

export default function Page({ docs }) {
return <MarkdownDocs docs={docs} />;
}

Page.getInitialProps = () => {
const { demos, docs } = prepareMarkdown({ pageFilename, requireRaw });
return { demos, docs };
};
39 changes: 39 additions & 0 deletions docs/pages/api-docs/tab-list.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
filename: /packages/material-ui-lab/src/TabList/TabList.js
---

<!--- This documentation is automatically generated, do not try to edit it. -->

# TabList API

<p class="description">The API documentation of the TabList React component. Learn more about the props and the CSS customization points.</p>

## Import

```js
import TabList from '@material-ui/lab/TabList';
// or
import { TabList } from '@material-ui/lab';
```

You can learn more about the difference by [reading this guide](/guides/minimizing-bundle-size/).





## Props

| Name | Type | Default | Description |
|:-----|:-----|:--------|:------------|
| <span class="prop-name">children</span> | <span class="prop-type">Array&lt;element&gt;</span> | | |

The `ref` is forwarded to the root element.

Any other props supplied will be provided to the root element ([Tabs](/api/tabs/)).

## Inheritance

The props of the [Tabs](/api/tabs/) component are also available.
You can take advantage of this behavior to [target nested components](/guides/api/#spread).

15 changes: 15 additions & 0 deletions docs/pages/api-docs/tab-panel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';
import MarkdownDocs from 'docs/src/modules/components/MarkdownDocs';
import { prepareMarkdown } from 'docs/src/modules/utils/parseMarkdown';

const pageFilename = 'api/tab-panel';
const requireRaw = require.context('!raw-loader!./', false, /\/tab-panel\.md$/);

export default function Page({ docs }) {
return <MarkdownDocs docs={docs} />;
}

Page.getInitialProps = () => {
const { demos, docs } = prepareMarkdown({ pageFilename, requireRaw });
return { demos, docs };
};
52 changes: 52 additions & 0 deletions docs/pages/api-docs/tab-panel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
---
filename: /packages/material-ui-lab/src/TabPanel/TabPanel.js
---

<!--- This documentation is automatically generated, do not try to edit it. -->

# TabPanel API

<p class="description">The API documentation of the TabPanel React component. Learn more about the props and the CSS customization points.</p>

## Import

```js
import TabPanel from '@material-ui/lab/TabPanel';
// or
import { TabPanel } from '@material-ui/lab';
```

You can learn more about the difference by [reading this guide](/guides/minimizing-bundle-size/).



## Component name

The `MuiTabPanel` name can be used for providing [default props](/customization/globals/#default-props) or [style overrides](/customization/globals/#css) at the theme level.

## Props

| Name | Type | Default | Description |
|:-----|:-----|:--------|:------------|
| <span class="prop-name">children</span> | <span class="prop-type">node</span> | | The content of the component. |
| <span class="prop-name">classes</span> | <span class="prop-type">object</span> | | Override or extend the styles applied to the component. See [CSS API](#css) below for more details. |
| <span class="prop-name required">value&nbsp;*</span> | <span class="prop-type">string</span> | | The `value` of the corresponding `Tab`. Must use the index of the `Tab` when no `value` was passed to `Tab`. |

The `ref` is forwarded to the root element.

Any other props supplied will be provided to the root element (native element).

## CSS

| Rule name | Global class | Description |
|:-----|:-------------|:------------|
| <span class="prop-name">root</span> | <span class="prop-name">.MuiTabPanel-root</span> | Styles applied to the root element.

You can override the style of the component thanks to one of these customization points:

- With a rule name of the [`classes` object prop](/customization/components/#overriding-styles-with-classes).
- With a [global class name](/customization/components/#overriding-styles-with-global-class-names).
- With a theme and an [`overrides` property](/customization/globals/#css).

If that's not sufficient, you can check the [implementation of the component](https://github.com/mui-org/material-ui/blob/master/packages/material-ui-lab/src/TabPanel/TabPanel.js) for more detail.

40 changes: 40 additions & 0 deletions docs/src/pages/components/tabs/LabTabs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import AppBar from '@material-ui/core/AppBar';
import Tab from '@material-ui/core/Tab';
import TabContext from '@material-ui/lab/TabContext';
import TabList from '@material-ui/lab/TabList';
import TabPanel from '@material-ui/lab/TabPanel';

const useStyles = makeStyles((theme) => ({
root: {
flexGrow: 1,
backgroundColor: theme.palette.background.paper,
},
}));

export default function LabTabs() {
const classes = useStyles();
const [value, setValue] = React.useState('1');

const handleChange = (event, newValue) => {
setValue(newValue);
};

return (
<div className={classes.root}>
<TabContext value={value}>
<AppBar position="static">
<TabList onChange={handleChange} aria-label="simple tabs example">
<Tab label="Item One" value="1" />
<Tab label="Item Two" value="2" />
<Tab label="Item Three" value="3" />
</TabList>
</AppBar>
<TabPanel value="1">Item One</TabPanel>
<TabPanel value="2">Item Two</TabPanel>
<TabPanel value="3">Item Three</TabPanel>
</TabContext>
</div>
);
}
40 changes: 40 additions & 0 deletions docs/src/pages/components/tabs/LabTabs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from 'react';
import { makeStyles, Theme } from '@material-ui/core/styles';
import AppBar from '@material-ui/core/AppBar';
import Tab from '@material-ui/core/Tab';
import TabContext from '@material-ui/lab/TabContext';
import TabList from '@material-ui/lab/TabList';
import TabPanel from '@material-ui/lab/TabPanel';

const useStyles = makeStyles((theme: Theme) => ({
root: {
flexGrow: 1,
backgroundColor: theme.palette.background.paper,
},
}));

export default function LabTabs() {
const classes = useStyles();
const [value, setValue] = React.useState('1');

const handleChange = (event: React.ChangeEvent<{}>, newValue: string) => {
setValue(newValue);
};

return (
<div className={classes.root}>
<TabContext value={value}>
<AppBar position="static">
<TabList onChange={handleChange} aria-label="simple tabs example">
<Tab label="Item One" value="1" />
<Tab label="Item Two" value="2" />
<Tab label="Item Three" value="3" />
</TabList>
</AppBar>
<TabPanel value="1">Item One</TabPanel>
<TabPanel value="2">Item Two</TabPanel>
<TabPanel value="3">Item Three</TabPanel>
</TabContext>
</div>
);
}
9 changes: 8 additions & 1 deletion docs/src/pages/components/tabs/tabs.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ Left and right scroll buttons will be presented regardless of the viewport width

### Prevent Scroll Buttons

Left and right scroll buttons will never be presented. All scrolling must be initiated through user agent scrolling mechanisms (e.g. left/right swipe, shift-mousewheel, etc.)
Left and right scroll buttons will never be presented. All scrolling must be initiated through user agent scrolling mechanisms (e.g. left/right swipe, shift-mousewheel, etc.)

{{"demo": "pages/components/tabs/ScrollableTabsButtonPrevent.js", "bg": true}}

Expand Down Expand Up @@ -90,3 +90,10 @@ Tab labels may be either all icons or all text.
{{"demo": "pages/components/tabs/IconTabs.js", "bg": true}}

{{"demo": "pages/components/tabs/IconLabelTabs.js", "bg": true}}

## Experimental Tabs API

`@material-ui/lab` offers utility components that inject props to implement accessible tabs
following [WAI-ARIA authoring practices](https://www.w3.org/TR/wai-aria-practices/#tabpanel).

{{"demo": "pages/components/tabs/LabTabs.js", "bg": true}}
24 changes: 24 additions & 0 deletions packages/material-ui-lab/src/TabContext/TabContext.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import * as React from 'react';

export interface TabContextValue {
idPrefix: string;
value: string;
}

export interface TabContextProps {
children?: React.ReactNode;
/**
* The value of the currently selected `Tab`.
*/
value: string;
}
/**
*
* API:
*
* - [TabContext API](https://material-ui.com/api/tab-context/)
*/
export default function TabContext(props: TabContextProps): JSX.Element;
export function useTabContext(): TabContextValue | null;
export function getPanelId(context: TabContextValue, tabValue: string): string;
export function getTabId(context: TabContextValue, tabValue: string): string;
67 changes: 67 additions & 0 deletions packages/material-ui-lab/src/TabContext/TabContext.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import * as React from 'react';
import * as PropTypes from 'prop-types';

/**
* @type {React.Context<{ idPrefix: string; value: string } | null>}
*/
const Context = React.createContext(null);
if (process.env.NODE_ENV !== 'production') {
Context.displayName = 'TabContext';
}

function useUniquePrefix() {
const [id, setId] = React.useState(null);
React.useEffect(() => {
setId(`mui-p-${Math.round(Math.random() * 1e5)}`);
}, []);
return id;
}

export default function TabContext(props) {
const { children, value } = props;
const idPrefix = useUniquePrefix();

const context = React.useMemo(() => {
return { idPrefix, value };
}, [idPrefix, value]);

return <Context.Provider value={context}>{children}</Context.Provider>;
}

TabContext.propTypes = {
// ----------------------------- Warning --------------------------------
// | These PropTypes are generated from the TypeScript type definitions |
// | To update them edit the d.ts file and run "yarn proptypes" |
// ----------------------------------------------------------------------
/**
* The content of the component.
*/
children: PropTypes.node,
/**
* The value of the currently selected `Tab`.
*/
value: PropTypes.string.isRequired,
};

/**
* @returns {unknown}
*/
export function useTabContext() {
return React.useContext(Context);
}

export function getPanelId(context, value) {
const { idPrefix } = context;
if (idPrefix === null) {
return null;
}
return `${context.idPrefix}-P-${value}`;
}

export function getTabId(context, value) {
const { idPrefix } = context;
if (idPrefix === null) {
return null;
}
return `${context.idPrefix}-T-${value}`;
}
Loading

0 comments on commit 3426f17

Please sign in to comment.