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 and improve visibility of tab scroll buttons using the IntersectionObserver API #36071

Merged
merged 25 commits into from
Jul 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
f9c6983
[Tabs] Fix and improve visibility of scrollButtons using Intersection…
SaidMarar Feb 5, 2023
a2e8573
Merge branch 'master' into fix/scroll-buttons-tabs
SaidMarar Feb 12, 2023
62e219e
refacto observer intersection and fix e2e test
SaidMarar Feb 14, 2023
c2bc67d
add e2e test for dynamic tabs
SaidMarar Feb 14, 2023
55359fd
fix lint prettier
SaidMarar Feb 14, 2023
8b3183b
Merge branch 'master' into fix/scroll-buttons-tabs
SaidMarar Mar 5, 2023
b21af61
Merge branch 'master' into fix/scroll-buttons-tabs
ZeeshanTamboli May 3, 2023
56804c2
fix delay render of scroll buttons
SaidMarar Jun 24, 2023
bc978f6
use builtin waitFor
SaidMarar Jun 26, 2023
6a80135
Merge branch 'master' into fix/scroll-buttons-tabs
ZeeshanTamboli Jul 7, 2023
eec2b8c
Merge branch 'fix/scroll-buttons-tabs' of https://github.com/SaidMara…
ZeeshanTamboli Jul 7, 2023
207c960
fix decimal scroll
SaidMarar Jul 10, 2023
742a838
fix comments
SaidMarar Jul 16, 2023
67971a5
fix naming
SaidMarar Jul 16, 2023
389675b
use length as depcy, remove deprecated
SaidMarar Jul 18, 2023
72eff5e
Merge branch 'master' into fix/scroll-buttons-tabs
ZeeshanTamboli Jul 19, 2023
5d65ce8
Update packages/mui-material/src/Tabs/Tabs.js
SaidMarar Jul 19, 2023
762c6db
remove unnecessary tests and keep updateScrollButtons impl
SaidMarar Jul 19, 2023
9666111
add unit test for dynamic tabs in vertical mode
SaidMarar Jul 20, 2023
2f0d80c
remove unnecessary tests
SaidMarar Jul 21, 2023
ada70ec
fix lint
SaidMarar Jul 21, 2023
18c81f2
Merge branch 'master' into fix/scroll-buttons-tabs
ZeeshanTamboli Jul 21, 2023
6079054
updateScrollButtons action use IntersectionObserver implementation ef…
SaidMarar Jul 21, 2023
6f16162
Merge branch 'master' into fix/scroll-buttons-tabs
ZeeshanTamboli Jul 23, 2023
ca7b7b2
Merge branch 'master' into fix/scroll-buttons-tabs
ZeeshanTamboli Jul 23, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 58 additions & 38 deletions packages/mui-material/src/Tabs/Tabs.js
Original file line number Diff line number Diff line change
Expand Up @@ -312,10 +312,9 @@ const Tabs = React.forwardRef(function Tabs(inProps, ref) {

const [mounted, setMounted] = React.useState(false);
const [indicatorStyle, setIndicatorStyle] = React.useState(defaultIndicatorStyle);
const [displayScroll, setDisplayScroll] = React.useState({
start: false,
end: false,
});
const [displayStartScroll, setDisplayStartScroll] = React.useState(false);
const [displayEndScroll, setDisplayEndScroll] = React.useState(false);
const [updateScrollObserver, setUpdateScrollObserver] = React.useState(false);

const [scrollerStyle, setScrollerStyle] = React.useState({
overflow: 'hidden',
Expand Down Expand Up @@ -508,7 +507,7 @@ const Tabs = React.forwardRef(function Tabs(inProps, ref) {
/>
) : null;

const scrollButtonsActive = displayScroll.start || displayScroll.end;
SaidMarar marked this conversation as resolved.
Show resolved Hide resolved
const scrollButtonsActive = displayStartScroll || displayEndScroll;
const showScrollButtons =
scrollable && ((scrollButtons === 'auto' && scrollButtonsActive) || scrollButtons === true);

Expand All @@ -519,7 +518,7 @@ const Tabs = React.forwardRef(function Tabs(inProps, ref) {
orientation={orientation}
direction={isRtl ? 'right' : 'left'}
onClick={handleStartScrollClick}
disabled={!displayScroll.start}
disabled={!displayStartScroll}
{...TabScrollButtonProps}
className={clsx(classes.scrollButtons, TabScrollButtonProps.className)}
/>
Expand All @@ -534,7 +533,7 @@ const Tabs = React.forwardRef(function Tabs(inProps, ref) {
orientation={orientation}
direction={isRtl ? 'left' : 'right'}
onClick={handleEndScrollClick}
disabled={!displayScroll.end}
disabled={!displayEndScroll}
{...TabScrollButtonProps}
className={clsx(classes.scrollButtons, TabScrollButtonProps.className)}
/>
Expand Down Expand Up @@ -563,23 +562,7 @@ const Tabs = React.forwardRef(function Tabs(inProps, ref) {

const updateScrollButtonState = useEventCallback(() => {
if (scrollable && scrollButtons !== false) {
const { scrollTop, scrollHeight, clientHeight, scrollWidth, clientWidth } = tabsRef.current;
let showStartScroll;
let showEndScroll;

if (vertical) {
showStartScroll = scrollTop > 1;
showEndScroll = scrollTop < scrollHeight - clientHeight - 1;
} else {
const scrollLeft = getNormalizedScrollLeft(tabsRef.current, theme.direction);
// use 1 for the potential rounding error with browser zooms.
showStartScroll = isRtl ? scrollLeft < scrollWidth - clientWidth - 1 : scrollLeft > 1;
showEndScroll = !isRtl ? scrollLeft < scrollWidth - clientWidth - 1 : scrollLeft > 1;
}

if (showStartScroll !== displayScroll.start || showEndScroll !== displayScroll.end) {
setDisplayScroll({ start: showStartScroll, end: showEndScroll });
}
setUpdateScrollObserver(!updateScrollObserver);
}
});

Expand All @@ -593,7 +576,6 @@ const Tabs = React.forwardRef(function Tabs(inProps, ref) {
// replaced by Suspense with a fallback, once React is updated to version 18
if (tabsRef.current) {
updateIndicatorState();
updateScrollButtonState();
}
});
const win = ownerWindow(tabsRef.current);
Expand All @@ -615,29 +597,68 @@ const Tabs = React.forwardRef(function Tabs(inProps, ref) {
resizeObserver.disconnect();
}
};
}, [updateIndicatorState, updateScrollButtonState]);

const handleTabsScroll = React.useMemo(
() =>
debounce(() => {
updateScrollButtonState();
}),
[updateScrollButtonState],
);
}, [updateIndicatorState]);

/**
* Toggle visibility of start and end scroll buttons
* Using IntersectionObserver on first and last Tabs.
*/
React.useEffect(() => {
let firstObserver;
let lastObserver;
const tabListChildren = Array.from(tabListRef.current.children);
const length = tabListChildren.length;
const firstTab = tabListChildren[0];
const lastTab = tabListChildren[length - 1];
const threshold = 0.99;
const observerOptions = {
root: tabsRef.current,
threshold,
};

const handleScrollButtonStart = (entries) => {
let display = false;
entries.forEach(({ isIntersecting }) => {
display = !isIntersecting;
});
setDisplayStartScroll(display);
};

const handleScrollButtonEnd = (entries) => {
ZeeshanTamboli marked this conversation as resolved.
Show resolved Hide resolved
let display = false;
entries.forEach(({ isIntersecting }) => {
display = !isIntersecting;
});
setDisplayEndScroll(display);
ZeeshanTamboli marked this conversation as resolved.
Show resolved Hide resolved
};

if (
typeof IntersectionObserver !== 'undefined' &&
length > 0 &&
scrollable &&
scrollButtons !== false
) {
firstObserver = new IntersectionObserver(handleScrollButtonStart, observerOptions);
firstObserver.observe(firstTab);

if (length > 1) {
ZeeshanTamboli marked this conversation as resolved.
Show resolved Hide resolved
lastObserver = new IntersectionObserver(handleScrollButtonEnd, observerOptions);
lastObserver.observe(lastTab);
}
}

return () => {
handleTabsScroll.clear();
firstObserver?.disconnect();
lastObserver?.disconnect();
};
}, [handleTabsScroll]);
}, [scrollable, scrollButtons, updateScrollObserver, childrenProp?.length]);
ZeeshanTamboli marked this conversation as resolved.
Show resolved Hide resolved

React.useEffect(() => {
setMounted(true);
}, []);

React.useEffect(() => {
updateIndicatorState();
updateScrollButtonState();
});

React.useEffect(() => {
Expand Down Expand Up @@ -763,7 +784,6 @@ const Tabs = React.forwardRef(function Tabs(inProps, ref) {
: -scrollerStyle.scrollbarWidth,
}}
ref={tabsRef}
onScroll={handleTabsScroll}
>
{/* The tablist isn't interactive but the tabs are */}
<FlexContainer
Expand Down
Loading