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

fix(Tabs): handle activation mode scrolling in separate useIsomorphicEffects #17920

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
43 changes: 43 additions & 0 deletions packages/react/src/components/Tabs/Tabs.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -719,3 +719,46 @@ Playground.argTypes = {
},
},
};

// TODO: remove before merge
export const Test = () => {
const [selectedIndex, setSelectedIndex] = useState(0);

const handleTabChange = (evt) => {
setSelectedIndex(evt.selectedIndex);
};

return (
<div style={{ width: '300px' }}>
<Button onClick={() => setSelectedIndex(3)}>Change to 4th tab</Button>
<Tabs selectedIndex={selectedIndex} onChange={handleTabChange}>
<TabList aria-label="List of tabs" scrollIntoView activation="manual">
<Tab>Dashboard</Tab>
<Tab>Monitoring</Tab>
<Tab>Activity</Tab>
<Tab>Settings</Tab>
</TabList>
<TabPanels>
<TabPanel>Tab Panel 1</TabPanel>
<TabPanel>Tab Panel 2</TabPanel>
<TabPanel>Tab Panel 3</TabPanel>
<TabPanel>Tab Panel 4</TabPanel>
</TabPanels>
</Tabs>
<Tabs selectedIndex={selectedIndex} onChange={handleTabChange}>
<TabList aria-label="List of tabs" scrollIntoView>
<Tab>Dashboard</Tab>
<Tab>Monitoring</Tab>
<Tab>Activity</Tab>
<Tab>Settings</Tab>
</TabList>
<TabPanels>
<TabPanel>Tab Panel 1</TabPanel>
<TabPanel>Tab Panel 2</TabPanel>
<TabPanel>Tab Panel 3</TabPanel>
<TabPanel>Tab Panel 4</TabPanel>
</TabPanels>
</Tabs>
</div>
);
};
72 changes: 44 additions & 28 deletions packages/react/src/components/Tabs/Tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,40 @@
}
}

/**
* Scroll the tab into view if it is not already visible
* @param tab - The tab to scroll into view
* @returns {void}
*/
function scrollTabIntoView(tab) {
if (!isScrollable || !ref.current) {
return;
}
if (tab) {
// The width of the "scroll buttons"
const { width: tabWidth } = tab.getBoundingClientRect();

Check warning on line 579 in packages/react/src/components/Tabs/Tabs.tsx

View check run for this annotation

Codecov / codecov/patch

packages/react/src/components/Tabs/Tabs.tsx#L579

Added line #L579 was not covered by tests

// The start and end position of the selected tab
const start = tab.offsetLeft;
const end = tab.offsetLeft + tabWidth;

Check warning on line 583 in packages/react/src/components/Tabs/Tabs.tsx

View check run for this annotation

Codecov / codecov/patch

packages/react/src/components/Tabs/Tabs.tsx#L582-L583

Added lines #L582 - L583 were not covered by tests

// The start and end of the visible area for the tabs
const visibleStart = ref.current.scrollLeft + buttonWidth;

Check warning on line 586 in packages/react/src/components/Tabs/Tabs.tsx

View check run for this annotation

Codecov / codecov/patch

packages/react/src/components/Tabs/Tabs.tsx#L586

Added line #L586 was not covered by tests
const visibleEnd =
ref.current.scrollLeft + ref.current.clientWidth - buttonWidth;

Check warning on line 588 in packages/react/src/components/Tabs/Tabs.tsx

View check run for this annotation

Codecov / codecov/patch

packages/react/src/components/Tabs/Tabs.tsx#L588

Added line #L588 was not covered by tests

// The beginning of the tab is clipped and not visible
if (start < visibleStart) {
setScrollLeft(start - buttonWidth);

Check warning on line 592 in packages/react/src/components/Tabs/Tabs.tsx

View check run for this annotation

Codecov / codecov/patch

packages/react/src/components/Tabs/Tabs.tsx#L592

Added line #L592 was not covered by tests
}

// The end of the tab is clipped and not visible
if (end > visibleEnd) {
setScrollLeft(end + buttonWidth - ref.current.clientWidth);

Check warning on line 597 in packages/react/src/components/Tabs/Tabs.tsx

View check run for this annotation

Codecov / codecov/patch

packages/react/src/components/Tabs/Tabs.tsx#L597

Added line #L597 was not covered by tests
}
}
}

useEffectOnce(() => {
const tab = tabs.current[selectedIndex];
if (scrollIntoView && tab) {
Expand Down Expand Up @@ -606,13 +640,13 @@

useIsomorphicEffect(() => {
if (ref.current) {
//adding 1 in calculation for firefox support
// adding 1 in calculation for firefox support
setIsScrollable(ref.current.scrollWidth > ref.current.clientWidth + 1);
}

function handler() {
if (ref.current) {
//adding 1 in calculation for firefox support
// adding 1 in calculation for firefox support
setIsScrollable(ref.current.scrollWidth > ref.current.clientWidth + 1);
}
}
Expand All @@ -632,39 +666,21 @@
}
}, [scrollLeft]);

// scroll manual tabs when active index changes (focus outline movement)
useIsomorphicEffect(() => {
if (!isScrollable || !ref.current) {
return;
}

const tab =
activation === 'manual'
? tabs.current[activeIndex]
: tabs.current[selectedIndex];
if (tab) {
// The width of the "scroll buttons"

// The start and end position of the selected tab
const { width: tabWidth } = tab.getBoundingClientRect();
const start = tab.offsetLeft;
const end = tab.offsetLeft + tabWidth;

// The start and end of the visible area for the tabs
const visibleStart = ref.current.scrollLeft + buttonWidth;
const visibleEnd =
ref.current.scrollLeft + ref.current.clientWidth - buttonWidth;

// The beginning of the tab is clipped and not visible
if (start < visibleStart) {
setScrollLeft(start - buttonWidth);
}
scrollTabIntoView(tab);
}, [activation, activeIndex]);

// The end of the tab is clipped and not visible
if (end > visibleEnd) {
setScrollLeft(end + buttonWidth - ref.current.clientWidth);
}
}
}, [activation, activeIndex, selectedIndex, isScrollable, children]);
// scroll tabs when selected index changes
useIsomorphicEffect(() => {
const tab = tabs.current[selectedIndex];
scrollTabIntoView(tab);
}, [selectedIndex, isScrollable, children]);

usePressable(previousButton, {
onPress({ longPress }) {
Expand Down
Loading