Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Tabs] Fix RTL indicator #26470

Merged
merged 13 commits into from
Jun 2, 2021
18 changes: 18 additions & 0 deletions docs/src/pages/components/tabs/HoverTabs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as React from 'react';
import Box from '@material-ui/core/Box';
import Tabs from '@material-ui/core/Tabs';
import Tab from '@material-ui/core/Tab';

export default function CenteredTabs() {
const [value, setValue] = React.useState(false);

return (
<Box sx={{ width: '100%', bgcolor: 'background.paper' }}>
<Tabs value={value} centered onMouseLeave={() => setValue(false)}>
<Tab label="Item One" onMouseOver={() => setValue(0)} />
<Tab label="Item Two" onMouseOver={() => setValue(1)} />
<Tab label="Item Three" onMouseOver={() => setValue(2)} />
</Tabs>
</Box>
);
}
18 changes: 18 additions & 0 deletions docs/src/pages/components/tabs/HoverTabs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as React from 'react';
import Box from '@material-ui/core/Box';
import Tabs from '@material-ui/core/Tabs';
import Tab from '@material-ui/core/Tab';

export default function CenteredTabs() {
oliviertassinari marked this conversation as resolved.
Show resolved Hide resolved
const [value, setValue] = React.useState<false | number>(false);

return (
<Box sx={{ width: '100%', bgcolor: 'background.paper' }}>
siriwatknp marked this conversation as resolved.
Show resolved Hide resolved
<Tabs value={value} centered onMouseLeave={() => setValue(false)}>
siriwatknp marked this conversation as resolved.
Show resolved Hide resolved
<Tab label="Item One" onMouseOver={() => setValue(0)} />
oliviertassinari marked this conversation as resolved.
Show resolved Hide resolved
<Tab label="Item Two" onMouseOver={() => setValue(1)} />
<Tab label="Item Three" onMouseOver={() => setValue(2)} />
</Tabs>
</Box>
);
}
2 changes: 1 addition & 1 deletion docs/src/pages/components/tabs/ScrollableTabsButtonAuto.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default function ScrollableTabsButtonAuto() {
};

return (
<Box sx={{ width: '100%', bgcolor: 'background.paper' }}>
<Box sx={{ maxWidth: 480, bgcolor: 'background.paper' }}>
<Tabs
value={value}
onChange={handleChange}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default function ScrollableTabsButtonAuto() {
};

return (
<Box sx={{ width: '100%', bgcolor: 'background.paper' }}>
<Box sx={{ maxWidth: 480, bgcolor: 'background.paper' }}>
<Tabs
value={value}
onChange={handleChange}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default function ScrollableTabsButtonForce() {
};

return (
<Box sx={{ width: '100%', bgcolor: 'background.paper' }}>
<Box sx={{ maxWidth: 480, bgcolor: 'background.paper' }}>
<Tabs
value={value}
onChange={handleChange}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default function ScrollableTabsButtonForce() {
};

return (
<Box sx={{ width: '100%', bgcolor: 'background.paper' }}>
<Box sx={{ maxWidth: 480, bgcolor: 'background.paper' }}>
<Tabs
value={value}
onChange={handleChange}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default function ScrollableTabsButtonPrevent() {
};

return (
<Box sx={{ width: '100%', bgcolor: 'background.paper' }}>
<Box sx={{ maxWidth: 480, bgcolor: 'background.paper' }}>
<Tabs
value={value}
onChange={handleChange}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default function ScrollableTabsButtonPrevent() {
};

return (
<Box sx={{ width: '100%', bgcolor: 'background.paper' }}>
<Box sx={{ maxWidth: 480, bgcolor: 'background.paper' }}>
<Tabs
value={value}
onChange={handleChange}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default function ScrollableTabsButtonVisible() {
};

return (
<Box sx={{ flexGrow: 1, width: '100%', bgcolor: 'background.paper' }}>
<Box sx={{ flexGrow: 1, maxWidth: 480, bgcolor: 'background.paper' }}>
<Tabs
value={value}
onChange={handleChange}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default function ScrollableTabsButtonVisible() {
};

return (
<Box sx={{ flexGrow: 1, width: '100%', bgcolor: 'background.paper' }}>
<Box sx={{ flexGrow: 1, maxWidth: 480, bgcolor: 'background.paper' }}>
<Tabs
value={value}
onChange={handleChange}
Expand Down
6 changes: 6 additions & 0 deletions docs/src/pages/components/tabs/tabs.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,12 @@ By default, tabs use a `button` element, but you can provide your custom tag or

{{"demo": "pages/components/tabs/NavTabs.js"}}

## Hover tabs

set active tab when hover on it and clear by setting the value to false.
siriwatknp marked this conversation as resolved.
Show resolved Hide resolved

{{"demo": "pages/components/tabs/HoverTabs.js"}}

## Icon tabs

Tab labels may be either all icons or all text.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const TabScrollButtonRoot = experimentalStyled(ButtonBase, {
width: '100%',
height: 40,
'& svg': {
transform: 'rotate(90deg)',
transform: `rotate(${styleProps.isRtl ? -90 : 90}deg)`,
oliviertassinari marked this conversation as resolved.
Show resolved Hide resolved
},
}),
}));
Expand Down
47 changes: 36 additions & 11 deletions packages/material-ui/src/Tabs/Tabs.js
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,18 @@ const TabsScrollbarSize = experimentalStyled(ScrollbarSize, {

const defaultIndicatorStyle = {};

function getStartKey(vertical, isRtl) {
if (vertical) return 'top';
if (isRtl) return 'right';
return 'left';
}

function getEndKey(vertical, isRtl) {
if (vertical) return 'bottom';
if (isRtl) return 'left';
return 'right';
}

const Tabs = React.forwardRef(function Tabs(inProps, ref) {
const props = useThemeProps({ props: inProps, name: 'MuiTabs' });
const theme = useTheme();
Expand Down Expand Up @@ -256,8 +268,8 @@ const Tabs = React.forwardRef(function Tabs(inProps, ref) {
const vertical = orientation === 'vertical';

const scrollStart = vertical ? 'scrollTop' : 'scrollLeft';
const start = vertical ? 'top' : 'left';
const end = vertical ? 'bottom' : 'right';
const start = getStartKey(vertical, isRtl);
const end = getEndKey(vertical, isRtl);
const clientSize = vertical ? 'clientHeight' : 'clientWidth';
const size = vertical ? 'height' : 'width';

Expand Down Expand Up @@ -364,7 +376,7 @@ const Tabs = React.forwardRef(function Tabs(inProps, ref) {
const correction = isRtl
? tabsMeta.scrollLeftNormalized + tabsMeta.clientWidth - tabsMeta.scrollWidth
: tabsMeta.scrollLeft;
startValue = tabMeta.left - tabsMeta.left + correction;
startValue = (isRtl ? -1 : 1) * (tabMeta[start] - tabsMeta[start] + correction);
oliviertassinari marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand Down Expand Up @@ -488,14 +500,27 @@ const Tabs = React.forwardRef(function Tabs(inProps, ref) {
return;
}

if (tabMeta[start] < tabsMeta[start]) {
// left side of button is out of view
const nextScrollStart = tabsMeta[scrollStart] + (tabMeta[start] - tabsMeta[start]);
scroll(nextScrollStart, { animation });
} else if (tabMeta[end] > tabsMeta[end]) {
// right side of button is out of view
const nextScrollStart = tabsMeta[scrollStart] + (tabMeta[end] - tabsMeta[end]);
scroll(nextScrollStart, { animation });
if (isRtl) {
if (tabMeta[end] < tabsMeta[end]) {
// left side of button is out of view
const nextScrollStart = tabsMeta[scrollStart] + (tabMeta[end] - tabsMeta[end]);
scroll(nextScrollStart, { animation });
} else if (tabMeta[start] > tabsMeta[start]) {
// right side of button is out of view
const nextScrollStart = tabsMeta[scrollStart] + (tabMeta[start] - tabsMeta[start]);
scroll(nextScrollStart, { animation });
}
} else {
// eslint-disable-next-line no-lonely-if
if (tabMeta[start] < tabsMeta[start]) {
// left side of button is out of view
const nextScrollStart = tabsMeta[scrollStart] + (tabMeta[start] - tabsMeta[start]);
scroll(nextScrollStart, { animation });
} else if (tabMeta[end] > tabsMeta[end]) {
// right side of button is out of view
const nextScrollStart = tabsMeta[scrollStart] + (tabMeta[end] - tabsMeta[end]);
scroll(nextScrollStart, { animation });
}
siriwatknp marked this conversation as resolved.
Show resolved Hide resolved
}
});

Expand Down
49 changes: 49 additions & 0 deletions packages/material-ui/src/Tabs/Tabs.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,55 @@ describe('<Tabs />', () => {
expect(style.left).to.equal('60px');
expect(style.width).to.equal('50px');
});

it('should have "right" for RLT', function test() {
if (isJSDOM) {
this.skip();
}
siriwatknp marked this conversation as resolved.
Show resolved Hide resolved

const { forceUpdate, container, getByRole } = render(
<div dir="rtl">
<Tabs value={1}>
<Tab />
<Tab />
</Tabs>
</div>,
{
wrapper: ({ children }) => (
<ThemeProvider theme={createTheme({ direction: 'rtl' })}>{children}</ThemeProvider>
),
},
);

const tablistContainer = getByRole('tablist').parentElement;
const tab = getByRole('tablist').children[1];

Object.defineProperty(tablistContainer, 'clientWidth', { value: 100 });
Object.defineProperty(tablistContainer, 'scrollWidth', { value: 100 });
tablistContainer.getBoundingClientRect = () => ({
left: 0,
right: 100,
});
tab.getBoundingClientRect = () => ({
left: 50,
width: 50,
right: 100,
});
forceUpdate();
let style;
style = container.querySelector(`.${classes.indicator}`).style;
expect(style.right).to.equal('0px');
expect(style.width).to.equal('50px');
siriwatknp marked this conversation as resolved.
Show resolved Hide resolved
tab.getBoundingClientRect = () => ({
left: 40,
width: 50,
right: 90,
});
forceUpdate();
style = container.querySelector(`.${classes.indicator}`).style;
expect(style.right).to.equal('10px');
expect(style.width).to.equal('50px');
});
});

describe('warnings', () => {
Expand Down