Skip to content

Commit e9586bd

Browse files
fix(tabs): change scrollintoview logic on load (#4143)
* fix(tabs): change scrollintoview logic on load. Use container scorll instead * fix(tabs): changeset * fix(tabs): linting --------- Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
1 parent aa19228 commit e9586bd

File tree

7 files changed

+194
-27
lines changed

7 files changed

+194
-27
lines changed

.changeset/cold-eagles-search.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@twilio-paste/tabs": patch
3+
"@twilio-paste/core": patch
4+
---
5+
6+
[Tabs] fix issue with currently selected item causing vertical scroll

.changeset/hot-owls-dream.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@twilio-paste/in-page-navigation": patch
3+
"@twilio-paste/core": patch
4+
---
5+
6+
[InPageNavigation] fix issue with currently selected item causing vertical scroll

packages/paste-core/components/in-page-navigation/src/InPageNavigation.tsx

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -106,15 +106,19 @@ const InPageNavigation = React.forwardRef<HTMLDivElement, InPageNavigationProps>
106106
// Scroll to the selected tab if it exists on mount
107107
React.useEffect(() => {
108108
if (listRef.current && scrollableRef.current) {
109-
setTimeout(
110-
() =>
111-
listRef.current
112-
?.querySelector(`[aria-current="page"]`)
113-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
114-
// @ts-ignore - behavior is typed incorrectly in Typescript v4, fixed in v5+
115-
?.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "center" }),
116-
1,
117-
);
109+
const currentSelectedTab = listRef.current.querySelector(`[aria-current="page"]`);
110+
const scrollableWidth = scrollableRef.current.getBoundingClientRect().width;
111+
112+
if (
113+
currentSelectedTab &&
114+
(currentSelectedTab?.getBoundingClientRect().x < 0 ||
115+
currentSelectedTab?.getBoundingClientRect().right > scrollableWidth)
116+
) {
117+
const scrollLeft =
118+
currentSelectedTab.getBoundingClientRect().x - scrollableRef.current.getBoundingClientRect().x;
119+
scrollableRef.current.scrollLeft += scrollLeft;
120+
}
121+
118122
scrollableRef.current?.addEventListener("scroll", handleScrollEvent);
119123
window.addEventListener("resize", handleScrollEvent);
120124
determineElementsOutOfBounds(scrollableRef.current, listRef.current);

packages/paste-core/components/in-page-navigation/stories/index.stories.tsx

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,39 @@ export const LinkOverflowExample: StoryFn = () => {
112112
);
113113
};
114114

115+
export const LinkOverflowExampleScrollTest: StoryFn = () => {
116+
/* using UID here to make unique labels for landmarks in Storybook for axe testing */
117+
return (
118+
<Box width="size60">
119+
<Box height="1800px" />
120+
<InPageNavigation aria-label={`get started ${useUID()}`}>
121+
<InPageNavigationItem href="#">Super SIM</InPageNavigationItem>
122+
<InPageNavigationItem href="#" title="Programmable Wireless">
123+
Programmable Wireless
124+
</InPageNavigationItem>
125+
<InPageNavigationItem href="#" title="Super Duper SIM">
126+
Super Duper SIM
127+
</InPageNavigationItem>
128+
<InPageNavigationItem href="#" title="Programmable Wirefull">
129+
Programmable Wirefull
130+
</InPageNavigationItem>
131+
<InPageNavigationItem href="#" title="Super SIM">
132+
Super SIM
133+
</InPageNavigationItem>
134+
<InPageNavigationItem href="#" title="Programmable Wireless">
135+
Programmable Wireless
136+
</InPageNavigationItem>
137+
<InPageNavigationItem currentPage={true} href="#" title="Super Duper SIM">
138+
Super Duper SIM
139+
</InPageNavigationItem>
140+
<InPageNavigationItem href="#" title="Programmable Wirefull">
141+
Programmable Wirefull
142+
</InPageNavigationItem>
143+
</InPageNavigation>
144+
</Box>
145+
);
146+
};
147+
115148
export const Customized: StoryFn = () => {
116149
const theme = useTheme();
117150
return (

packages/paste-core/components/tabs/src/TabList.tsx

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ const HorizontalTabList: React.FC<React.PropsWithChildren<{ variant?: Variants;
6565
element,
6666
}) => {
6767
const ref = React.useRef<HTMLElement>(null);
68+
const { selectedId } = React.useContext(TabsContext);
6869
// ref to the scrollable element
6970
const scrollableRef = React.useRef<HTMLDivElement>(null);
7071
const isInverse = variant === "inverse" || variant === "inverse_fitted";
@@ -85,6 +86,22 @@ const HorizontalTabList: React.FC<React.PropsWithChildren<{ variant?: Variants;
8586
}
8687
}, [ref.current, scrollableRef.current]);
8788

89+
React.useEffect(() => {
90+
if (scrollableRef.current && selectedId) {
91+
// eslint-disable-next-line unicorn/prefer-query-selector
92+
const selectedTabEl = document.getElementById(selectedId);
93+
const scrollableWidth = scrollableRef.current.getBoundingClientRect().width;
94+
95+
if (
96+
selectedTabEl &&
97+
(selectedTabEl?.getBoundingClientRect().x < 0 || selectedTabEl?.getBoundingClientRect().right > scrollableWidth)
98+
) {
99+
const scrollLeft = selectedTabEl.getBoundingClientRect().x - scrollableRef.current.getBoundingClientRect().x;
100+
scrollableRef.current.scrollLeft += scrollLeft;
101+
}
102+
}
103+
}, [scrollableRef.current, selectedId]);
104+
88105
// Cleanup event listeners on destroy
89106
React.useEffect(() => {
90107
return () => {

packages/paste-core/components/tabs/src/Tabs.tsx

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ export interface TabsProps extends TabPrimitiveInitialState {
4848
const Tabs = React.forwardRef<HTMLDivElement, TabsProps>(
4949
({ children, element, orientation = "horizontal", state, variant, ...initialState }, ref) => {
5050
// If returned state from primitive has orientation set to undefined, use the default "horizontal"
51-
const [prevSelectedTab, setPrevSelectedTab] = React.useState<string | undefined>(undefined);
5251
const { orientation: tabOrientation = orientation, ...tab } =
5352
state || useTabPrimitiveState({ orientation, ...initialState });
5453
const elementName = getElementName(tabOrientation, "TABS", element);
@@ -57,23 +56,6 @@ const Tabs = React.forwardRef<HTMLDivElement, TabsProps>(
5756
[tab, tabOrientation, variant],
5857
);
5958

60-
const { selectedId } = tab;
61-
// Scroll to the selected tab if it exists on mount
62-
React.useEffect(() => {
63-
if (typeof selectedId === "string") {
64-
setTimeout(() => {
65-
document
66-
// eslint-disable-next-line unicorn/prefer-query-selector
67-
.getElementById(selectedId)
68-
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
69-
// @ts-ignore - behavior is typed incorrectly in Typescript v4, fixed in v5+
70-
?.scrollIntoView({ behavior: prevSelectedTab === undefined ? "instant" : "smooth" });
71-
72-
setPrevSelectedTab(selectedId);
73-
}, 1);
74-
}
75-
}, [prevSelectedTab, selectedId]);
76-
7759
const returnValue = <TabsContext.Provider value={value}>{children}</TabsContext.Provider>;
7860

7961
if (tabOrientation === "vertical") {

packages/paste-core/components/tabs/stories/index.stories.tsx

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,68 @@ export const HorizontalTabsOverflow = (): JSX.Element => {
128128
);
129129
};
130130

131+
export const HorizontalTabOverflowScrollCheck = (): JSX.Element => {
132+
const selectedId = useUID();
133+
const uniqueBaseID = useUID();
134+
return (
135+
<Box maxWidth="size80">
136+
<Box height="1800px" />
137+
<Tabs selectedId={selectedId} baseId={`${uniqueBaseID}-horizontal-tabs-example`}>
138+
<TabList aria-label="LGBTQ+ Projects">
139+
<Tab>Inside Out</Tab>
140+
<Tab>Transgender District</Tab>
141+
<Tab>Transgender District</Tab>
142+
<Tab>Transgender District</Tab>
143+
<Tab>Transgender District</Tab>
144+
<Tab>Transgender District</Tab>
145+
<Tab id={selectedId}>Transgender District</Tab>
146+
<Tab>Audre Lorde Project</Tab>
147+
<Tab disabled>Coming soon...</Tab>
148+
</TabList>
149+
<TabPanels>
150+
<TabPanel>
151+
<Heading as="h2" variant="heading20">
152+
Inside Out
153+
</Heading>
154+
<Paragraph>
155+
Inside Out empowers, educates, and advocates for LGBTQ+ of youth from the Pikes Peak Region in Southern
156+
Colorado. Inside Out does this by creating safe spaces, support systems and teaching life skills to all
157+
youth in the community and working to make the community safer and more accepting of gender and sexual
158+
orientation diversity.
159+
</Paragraph>
160+
<Anchor href="https://insideoutys.org/">Support Inside Out</Anchor>
161+
</TabPanel>
162+
<TabPanel>
163+
<Heading as="h2" variant="heading20">
164+
Transgender District
165+
</Heading>
166+
<Paragraph>
167+
The mission of the Transgender District is to create an urban environment that fosters the rich history,
168+
culture, legacy, and empowerment of transgender people and its deep roots in the southeastern Tenderloin
169+
neighborhood. The transgender district aims to stabilize and economically empower the transgender
170+
community through ownership of homes, businesses, historic and cultural sites, and safe community spaces.
171+
</Paragraph>
172+
<Anchor href="https://www.transgenderdistrictsf.com/">Support The Transgender District</Anchor>
173+
</TabPanel>
174+
<TabPanel>
175+
<Heading as="h2" variant="heading20">
176+
Audre Lorde Project
177+
</Heading>
178+
<Paragraph>
179+
The Audre Lorde Project is a Lesbian, Gay, Bisexual, Two Spirit, Trans and Gender Non Conforming People of
180+
Color center for community organizing, focusing on the New York City area. Through mobilization, education
181+
and capacity-building, they work for community wellness and progressive social and economic justice.
182+
Committed to struggling across differences, they seek to responsibly reflect, represent and serve their
183+
various communities.
184+
</Paragraph>
185+
<Anchor href="https://alp.org/">Support The Audre Lorde Project</Anchor>
186+
</TabPanel>
187+
</TabPanels>
188+
</Tabs>
189+
</Box>
190+
);
191+
};
192+
131193
export const FittedTabs = (): JSX.Element => {
132194
const selectedId = useUID();
133195
const uniqueBaseID = useUID();
@@ -236,6 +298,63 @@ export const VerticalTabs = (): JSX.Element => {
236298
);
237299
};
238300

301+
export const VerticalTabsScrollCheck = (): JSX.Element => {
302+
const selectedId = useUID();
303+
const uniqueBaseID = useUID();
304+
return (
305+
<Box>
306+
<Box height="1800px" />
307+
<Tabs orientation="vertical" selectedId={selectedId} baseId={`${uniqueBaseID}-vertical-tabs-example`}>
308+
<TabList aria-label="LGBTQ+ Projects">
309+
<Tab id={selectedId}>Inside Out</Tab>
310+
<Tab>Transgender District</Tab>
311+
<Tab>Audre Lorde Project</Tab>
312+
<Tab disabled>Coming soon...</Tab>
313+
</TabList>
314+
<TabPanels>
315+
<TabPanel>
316+
<Heading as="h2" variant="heading20">
317+
Inside Out
318+
</Heading>
319+
<Paragraph>
320+
Inside Out empowers, educates, and advocates for LGBTQ+ of youth from the Pikes Peak Region in Southern
321+
Colorado. Inside Out does this by creating safe spaces, support systems and teaching life skills to all
322+
youth in the community and working to make the community safer and more accepting of gender and sexual
323+
orientation diversity.
324+
</Paragraph>
325+
<Anchor href="https://insideoutys.org/">Support Inside Out</Anchor>
326+
</TabPanel>
327+
<TabPanel>
328+
<Heading as="h2" variant="heading20">
329+
Transgender District
330+
</Heading>
331+
<Paragraph>
332+
The mission of the Transgender District is to create an urban environment that fosters the rich history,
333+
culture, legacy, and empowerment of transgender people and its deep roots in the southeastern Tenderloin
334+
neighborhood. The transgender district aims to stabilize and economically empower the transgender
335+
community through ownership of homes, businesses, historic and cultural sites, and safe community spaces.
336+
</Paragraph>
337+
<Anchor href="https://www.transgenderdistrictsf.com/">Support The Transgender District</Anchor>
338+
</TabPanel>
339+
<TabPanel>
340+
<Heading as="h2" variant="heading20">
341+
Audre Lorde Project
342+
</Heading>
343+
<Paragraph>
344+
The Audre Lorde Project is a Lesbian, Gay, Bisexual, Two Spirit, Trans and Gender Non Conforming People of
345+
Color center for community organizing, focusing on the New York City area. Through mobilization, education
346+
and capacity-building, they work for community wellness and progressive social and economic justice.
347+
Committed to struggling across differences, they seek to responsibly reflect, represent and serve their
348+
various communities.
349+
</Paragraph>
350+
<Anchor href="https://alp.org/">Support The Audre Lorde Project</Anchor>
351+
</TabPanel>
352+
</TabPanels>
353+
</Tabs>
354+
</Box>
355+
);
356+
};
357+
239358
export const VerticalTabsOverflow = (): JSX.Element => {
240359
const selectedId = useUID();
241360
const uniqueBaseID = useUID();

0 commit comments

Comments
 (0)