Skip to content

Commit 89896f9

Browse files
committed
Improve animation of section tabs
1 parent ff50ac2 commit 89896f9

File tree

1 file changed

+24
-20
lines changed

1 file changed

+24
-20
lines changed

packages/gitbook/src/components/SiteSectionTabs/SiteSectionTabs.tsx

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { tcls } from '@/lib/tailwind';
88
import { Button, Link } from '../primitives';
99

1010
/**
11-
* A set of tabs representing site sections for multi-section sites
11+
* A set of navigational tabs representing site sections for multi-section sites
1212
*/
1313
export function SiteSectionTabs(props: { sections: SiteSection[]; section: SiteSection }) {
1414
const { sections, section: currentSection } = props;
@@ -22,34 +22,36 @@ export function SiteSectionTabs(props: { sections: SiteSection[]; section: SiteS
2222
const currentTabRef = React.useRef<HTMLAnchorElement>(null);
2323
const navRef = React.useRef<HTMLDivElement>(null);
2424

25-
const [currentIndex, setCurrentIndex] = React.useState(
26-
sections.findIndex((section) => section.id === currentSection?.id),
27-
);
25+
// we don't set the current tab with a click event because the tab will navigate to a different section and trigger a page reload.
26+
const currentIndex = sections.findIndex((section) => section.id === currentSection?.id);
27+
2828
const [tabDimensions, setTabDimensions] = React.useState<{
2929
left: number;
3030
width: number;
3131
} | null>(null);
3232

33-
React.useEffect(() => {
33+
const updateTabDimensions = React.useCallback(() => {
3434
if (currentTabRef.current && navRef.current) {
3535
const rect = currentTabRef.current.getBoundingClientRect();
3636
const navRect = navRef.current.getBoundingClientRect();
3737
setTabDimensions({ left: rect.left - navRect.left, width: rect.width });
3838
}
39-
}, [currentIndex]);
39+
}, []);
40+
41+
React.useEffect(() => {
42+
updateTabDimensions();
43+
}, [currentIndex, updateTabDimensions]);
4044

4145
React.useLayoutEffect(() => {
42-
function onResize() {
43-
if (currentTabRef.current && navRef.current) {
44-
const rect = currentTabRef.current.getBoundingClientRect();
45-
const navRect = navRef.current.getBoundingClientRect();
46-
setTabDimensions({ left: rect.left - navRect.left, width: rect.width });
47-
}
46+
window.addEventListener('load', updateTabDimensions);
47+
window.addEventListener('resize', updateTabDimensions);
48+
() => {
49+
window.removeEventListener('resize', updateTabDimensions);
50+
window.removeEventListener('load', updateTabDimensions);
4851
}
49-
window.addEventListener('resize', onResize);
50-
() => window.removeEventListener('resize', onResize);
51-
}, []);
52+
}, [updateTabDimensions]);
5253

54+
const opacity = Boolean(tabDimensions) ? 1 : 0.0;
5355
const scale = (tabDimensions?.width ?? 0) * 0.01;
5456
const startPos = `${tabDimensions?.left ?? 0}px`;
5557

@@ -62,6 +64,7 @@ export function SiteSectionTabs(props: { sections: SiteSection[]; section: SiteS
6264
className="flex flex-nowrap items-center max-w-screen mb-px"
6365
style={
6466
{
67+
'--tab-opacity': `${opacity}`,
6568
'--tab-scale': `${scale}`,
6669
'--tab-start': `${startPos}`,
6770
} as React.CSSProperties
@@ -80,9 +83,12 @@ export function SiteSectionTabs(props: { sections: SiteSection[]; section: SiteS
8083
'after:absolute',
8184
'after:-bottom-px',
8285
'after:left-0',
86+
'after:opacity-[--tab-opacity]',
8387
'after:scale-x-[--tab-scale]',
84-
'after:transition-transform',
88+
'after:transition-all',
89+
'after:motion-reduce:transition-none',
8590
'after:translate-x-[var(--tab-start)]',
91+
'after:will-change-transform',
8692
'after:h-0.5',
8793
'after:w-[100px]',
8894
'after:bg-primary',
@@ -97,7 +103,6 @@ export function SiteSectionTabs(props: { sections: SiteSection[]; section: SiteS
97103
label={tab.label}
98104
href={tab.path}
99105
ref={currentIndex === index ? currentTabRef : null}
100-
onClick={() => setCurrentIndex(index)}
101106
/>
102107
))}
103108
</div>
@@ -111,9 +116,9 @@ export function SiteSectionTabs(props: { sections: SiteSection[]; section: SiteS
111116
*/
112117
const Tab = React.forwardRef<
113118
HTMLSpanElement,
114-
{ active: boolean; href: string; label: string; onClick?: () => void }
119+
{ active: boolean; href: string; label: string; }
115120
>(function Tab(props, ref) {
116-
const { active, href, label, onClick } = props;
121+
const { active, href, label } = props;
117122
return (
118123
<Link
119124
className={tcls(
@@ -124,7 +129,6 @@ const Tab = React.forwardRef<
124129
)}
125130
role="tab"
126131
href={href}
127-
onClick={onClick}
128132
>
129133
<span ref={ref} className={tcls('inline-flex w-full truncate')}>
130134
{label}

0 commit comments

Comments
 (0)