Skip to content

Commit

Permalink
[Tabs] New option to show scrollbar (mui#22438)
Browse files Browse the repository at this point in the history
  • Loading branch information
LogyLeo authored Sep 2, 2020
1 parent f90c864 commit 995fa45
Show file tree
Hide file tree
Showing 6 changed files with 65 additions and 15 deletions.
5 changes: 4 additions & 1 deletion docs/pages/api-docs/tabs.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ The `MuiTabs` name can be used for providing [default props](/customization/glob
| <span class="prop-name">textColor</span> | <span class="prop-type">'inherit'<br>&#124;&nbsp;'primary'<br>&#124;&nbsp;'secondary'</span> | <span class="prop-default">'inherit'</span> | Determines the color of the `Tab`. |
| <span class="prop-name">value</span> | <span class="prop-type">any</span> | | The value of the currently selected `Tab`. If you don't want any selected `Tab`, you can set this prop to `false`. |
| <span class="prop-name">variant</span> | <span class="prop-type">'fullWidth'<br>&#124;&nbsp;'scrollable'<br>&#124;&nbsp;'standard'</span> | <span class="prop-default">'standard'</span> | Determines additional display behavior of the tabs:<br> - `scrollable` will invoke scrolling properties and allow for horizontally scrolling (or swiping) of the tab bar. -`fullWidth` will make the tabs grow to use all the available space, which should be used for small views, like on mobile. - `standard` will render the default state. |
| <span class="prop-name">visibleScrollbar</span> | <span class="prop-type">bool</span> | <span class="prop-default">false</span> | If `true`, the scrollbar will be visible. It can be useful when displaying a long vertical list of tabs. |

The `ref` is forwarded to the root element.

Expand All @@ -62,7 +63,9 @@ Any other props supplied will be provided to the root element (native element).
| <span class="prop-name">centered</span> | <span class="prop-name">.MuiTabs-centered</span> | Styles applied to the flex container element if `centered={true}` & `!variant="scrollable"`.
| <span class="prop-name">scroller</span> | <span class="prop-name">.MuiTabs-scroller</span> | Styles applied to the tablist element.
| <span class="prop-name">fixed</span> | <span class="prop-name">.MuiTabs-fixed</span> | Styles applied to the tablist element if `!variant="scrollable"`.
| <span class="prop-name">scrollable</span> | <span class="prop-name">.MuiTabs-scrollable</span> | Styles applied to the tablist element if `variant="scrollable"`.
| <span class="prop-name">scrollableX</span> | <span class="prop-name">.MuiTabs-scrollableX</span> | Styles applied to the tablist element if `variant="scrollable"` and `orientation="horizontal"`.
| <span class="prop-name">scrollableY</span> | <span class="prop-name">.MuiTabs-scrollableY</span> | Styles applied to the tablist element if `variant="scrollable"` and `orientation="vertical"`.
| <span class="prop-name">hideScrollbar</span> | <span class="prop-name">.MuiTabs-hideScrollbar</span> | Styles applied to the tablist element if `variant="scrollable"` and `visibleScrollbar={false}`.
| <span class="prop-name">scrollButtons</span> | <span class="prop-name">.MuiTabs-scrollButtons</span> | Styles applied to the `ScrollButtonComponent` component.
| <span class="prop-name">scrollButtonsDesktop</span> | <span class="prop-name">.MuiTabs-scrollButtonsDesktop</span> | Styles applied to the `ScrollButtonComponent` component if `scrollButtons="auto"` or scrollButtons="desktop"`.
| <span class="prop-name">indicator</span> | <span class="prop-name">.MuiTabs-indicator</span> | Styles applied to the `TabIndicator` component.
Expand Down
4 changes: 4 additions & 0 deletions docs/src/pages/components/tabs/tabs.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,12 @@ Here is an example of customizing the component. You can learn more about this i

## Vertical tabs

To make vertical tabs instead of default horizontal ones, there is `orientation="vertical"`:

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

Note that you can restore the scrollbar with `visibleScrollbar`.

## Nav Tabs

By default tabs use a `button` element, but you can provide your own custom tag or component. Here's an example of implementing tabbed navigation:
Expand Down
6 changes: 6 additions & 0 deletions framer/Material-UI.framerfx/code/Tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ interface Props {
selectionFollowsFocus?: boolean;
textColor: 'inherit' | 'primary' | 'secondary';
variant: 'fullWidth' | 'scrollable' | 'standard';
visibleScrollbar: boolean;
appBarColor?: 'default' | 'primary' | 'secondary' | 'inherit';
icons: string[];
labels: string[];
Expand Down Expand Up @@ -57,6 +58,7 @@ Tabs.defaultProps = {
scrollButtons: 'auto' as 'auto',
textColor: 'inherit' as 'inherit',
variant: 'standard' as 'standard',
visibleScrollbar: false,
icons: ['phone', 'favorite', 'person_pin'],
labels: ['Tab 1', 'Tab 2', 'Tab 3'],
width: 500,
Expand Down Expand Up @@ -92,6 +94,10 @@ addPropertyControls(Tabs, {
title: 'Variant',
options: ['fullWidth', 'scrollable', 'standard'],
},
visibleScrollbar: {
type: ControlType.Boolean,
title: 'Visible scrollbar',
},
appBarColor: {
type: ControlType.Enum,
title: 'App bar color',
Expand Down
13 changes: 11 additions & 2 deletions packages/material-ui/src/Tabs/Tabs.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,12 @@ export interface TabsTypeMap<P = {}, D extends React.ElementType = typeof Button
scroller?: string;
/** Styles applied to the tablist element if `!variant="scrollable"`. */
fixed?: string;
/** Styles applied to the tablist element if `variant="scrollable"`. */
scrollable?: string;
/** Styles applied to the tablist element if `variant="scrollable"` and `orientation="horizontal"`. */
scrollableX?: string;
/** Styles applied to the tablist element if `variant="scrollable"` and `orientation="vertical"`. */
scrollableY?: string;
/** Styles applied to the tablist element if `variant="scrollable"` and `visibleScrollbar={false}`. */
hideScrollbar?: string;
/** Styles applied to the `ScrollButtonComponent` component. */
scrollButtons?: string;
/** Styles applied to the `ScrollButtonComponent` component if `scrollButtons="auto"` or scrollButtons="desktop"`. */
Expand Down Expand Up @@ -118,6 +122,11 @@ export interface TabsTypeMap<P = {}, D extends React.ElementType = typeof Button
* - `standard` will render the default state.
*/
variant?: 'standard' | 'scrollable' | 'fullWidth';
/**
* If `true`, the scrollbar will be visible. It can be useful when displaying
* a long vertical list of tabs.
*/
visibleScrollbar?: boolean;
};
defaultComponent: D;
}
Expand Down
46 changes: 37 additions & 9 deletions packages/material-ui/src/Tabs/Tabs.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,18 @@ export const styles = (theme) => ({
overflowX: 'hidden',
width: '100%',
},
/* Styles applied to the tablist element if `variant="scrollable"`. */
scrollable: {
overflowX: 'scroll',
/* Styles applied to the tablist element if `variant="scrollable"` and `orientation="horizontal"`. */
scrollableX: {
overflowX: 'auto',
overflowY: 'hidden',
},
/* Styles applied to the tablist element if `variant="scrollable"` and `orientation="vertical"`. */
scrollableY: {
overflowY: 'auto',
overflowX: 'hidden',
},
/* Styles applied to the tablist element if `variant="scrollable"` and `visibleScrollbar={false}`. */
hideScrollbar: {
// Hide dimensionless scrollbar on MacOS
scrollbarWidth: 'none', // Firefox
'&::-webkit-scrollbar': {
Expand Down Expand Up @@ -92,6 +101,7 @@ const Tabs = React.forwardRef(function Tabs(props, ref) {
textColor = 'inherit',
value,
variant = 'standard',
visibleScrollbar = false,
...other
} = props;
const theme = useTheme();
Expand Down Expand Up @@ -123,7 +133,7 @@ const Tabs = React.forwardRef(function Tabs(props, ref) {

const [scrollerStyle, setScrollerStyle] = React.useState({
overflow: 'hidden',
marginBottom: null,
scrollbarWidth: 0,
});

const valueToIndex = new Map();
Expand Down Expand Up @@ -237,17 +247,23 @@ const Tabs = React.forwardRef(function Tabs(props, ref) {
moveTabsScroll(tabsRef.current[clientSize]);
};

const handleScrollbarSizeChange = React.useCallback((scrollbarHeight) => {
// TODO Remove <ScrollbarSize /> as browser support for hidding the scrollbar
// with CSS improves.
const handleScrollbarSizeChange = React.useCallback((scrollbarWidth) => {
setScrollerStyle({
overflow: null,
marginBottom: -scrollbarHeight,
scrollbarWidth,
});
}, []);

const getConditionalElements = () => {
const conditionalElements = {};

conditionalElements.scrollbarSizeListener = scrollable ? (
<ScrollbarSize className={classes.scrollable} onChange={handleScrollbarSizeChange} />
<ScrollbarSize
onChange={handleScrollbarSizeChange}
className={clsx(classes.scrollableX, classes.hideScrollbar)}
/>
) : null;

const scrollButtonsActive = displayScroll.start || displayScroll.end;
Expand Down Expand Up @@ -484,9 +500,16 @@ const Tabs = React.forwardRef(function Tabs(props, ref) {
<div
className={clsx(classes.scroller, {
[classes.fixed]: !scrollable,
[classes.scrollable]: scrollable,
[classes.hideScrollbar]: scrollable && !visibleScrollbar,
[classes.scrollableX]: scrollable && !vertical,
[classes.scrollableY]: scrollable && vertical,
})}
style={scrollerStyle}
style={{
overflow: scrollerStyle.overflow,
[vertical ? `margin${isRtl ? 'Left' : 'Right'}` : 'marginBottom']: visibleScrollbar
? undefined
: -scrollerStyle.scrollbarWidth,
}}
ref={tabsRef}
onScroll={handleTabsScroll}
>
Expand Down Expand Up @@ -617,6 +640,11 @@ Tabs.propTypes = {
* - `standard` will render the default state.
*/
variant: PropTypes.oneOf(['fullWidth', 'scrollable', 'standard']),
/**
* If `true`, the scrollbar will be visible. It can be useful when displaying
* a long vertical list of tabs.
*/
visibleScrollbar: PropTypes.bool,
};

export default withStyles(styles, { name: 'MuiTabs' })(Tabs);
6 changes: 3 additions & 3 deletions packages/material-ui/src/Tabs/Tabs.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ describe('<Tabs />', () => {

it('should render with the scrollable class', () => {
const { container } = render(tabs);
const selector = `.${classes.scroller}.${classes.scrollable}`;
const selector = `.${classes.scroller}.${classes.scrollableX}`;
expect(container.querySelector(selector).tagName).to.equal('DIV');
expect(container.querySelectorAll(selector)).to.have.lengthOf(1);
});
Expand All @@ -390,7 +390,7 @@ describe('<Tabs />', () => {
expect(hasLeftScrollButton(container)).to.equal(true);
expect(hasRightScrollButton(container)).to.equal(true);
tablistContainer.scrollLeft = 0;
fireEvent.scroll(container.querySelector(`.${classes.scroller}.${classes.scrollable}`));
fireEvent.scroll(container.querySelector(`.${classes.scroller}.${classes.scrollableX}`));
clock.tick(166);

expect(hasLeftScrollButton(container)).to.equal(false);
Expand Down Expand Up @@ -422,7 +422,7 @@ describe('<Tabs />', () => {
</Tabs>,
);
const baseSelector = `.${classes.scroller}`;
const selector = `.${classes.scroller}.${classes.scrollable}`;
const selector = `.${classes.scroller}.${classes.scrollableX}`;
expect(container.querySelector(baseSelector)).not.to.equal(null);
expect(container.querySelector(selector)).to.equal(null);
});
Expand Down

0 comments on commit 995fa45

Please sign in to comment.