Skip to content

Commit c8d269d

Browse files
authored
Hide header when scrolling down and show header when scrolling up (#1185)
* Hide header when scrolling down and show header when scrolling up * Mini navbar now extends whole width of the screen
1 parent a9db5f2 commit c8d269d

File tree

2 files changed

+106
-83
lines changed

2 files changed

+106
-83
lines changed

src/main/webapp/app/pages/genePage/SomaticGermlineGenePage.tsx

-1
Original file line numberDiff line numberDiff line change
@@ -673,7 +673,6 @@ export default class SomaticGermlineGenePage extends React.Component<
673673
/>
674674
</span>
675675
}
676-
linkUnderlineColor={COLOR_SOMATIC}
677676
/>
678677
)}
679678
<Container>

src/main/webapp/app/shared/nav/StickyMiniNavBar.tsx

+106-82
Original file line numberDiff line numberDiff line change
@@ -36,25 +36,28 @@ function useScrollToHash({ stickyHeight }: { stickyHeight: number }) {
3636

3737
type IStickyMiniNavBar = {
3838
title: string | JSX.Element;
39-
linkUnderlineColor: string;
4039
stickyBackgroundColor?: string;
4140
};
4241

42+
function getHeader() {
43+
return document.querySelector('header');
44+
}
45+
4346
export default function StickyMiniNavBar({
4447
title,
45-
linkUnderlineColor,
4648
stickyBackgroundColor,
4749
}: IStickyMiniNavBar) {
4850
const [headerHeight, setHeaderHeight] = useState(0);
4951
const [isSticky, setIsSticky] = useState(false);
52+
const [isScrollingUp, setIsScrollingUp] = useState(false);
5053
const [sections, setSections] = useState<
5154
{ id: string; label: string | null }[]
5255
>([]);
5356
const [passedElements, setPassedElements] = useState<
5457
Record<string, { isPassed: boolean; isInView: boolean }>
5558
>({});
5659
const stickyDivRef = useRef<HTMLDivElement | null>(null);
57-
const hash = useScrollToHash({
60+
useScrollToHash({
5861
stickyHeight:
5962
headerHeight +
6063
(stickyDivRef.current?.getBoundingClientRect().height ?? 0),
@@ -71,7 +74,7 @@ export default function StickyMiniNavBar({
7174
});
7275
setSections(newSections);
7376

74-
const headerElement = document.querySelector('header');
77+
const headerElement = getHeader();
7578

7679
const updateHeaderHeight = () => {
7780
if (headerElement) {
@@ -123,107 +126,128 @@ export default function StickyMiniNavBar({
123126
};
124127
}, [sections]);
125128

126-
const handleScroll = () => {
127-
if (stickyDivRef.current) {
128-
const stickyOffset = stickyDivRef.current.getBoundingClientRect().top;
129-
setIsSticky(stickyOffset <= headerHeight);
130-
}
131-
};
132-
133129
useEffect(() => {
130+
return () => {
131+
const headerElement = getHeader();
132+
if (headerElement) {
133+
headerElement.setAttribute('style', '');
134+
}
135+
};
136+
}, []);
137+
useEffect(() => {
138+
let lastScrollY = 0;
139+
const handleScroll = () => {
140+
if (stickyDivRef.current) {
141+
const stickyOffset = stickyDivRef.current.getBoundingClientRect().top;
142+
setIsSticky(stickyOffset <= headerHeight);
143+
}
144+
const headerElement = getHeader();
145+
const currentScrollY = window.scrollY;
146+
const newIsScrollingUp = currentScrollY < lastScrollY;
147+
lastScrollY = currentScrollY;
148+
if (headerElement) {
149+
if (newIsScrollingUp) {
150+
headerElement.setAttribute('style', '');
151+
} else {
152+
headerElement.setAttribute('style', 'position: static;');
153+
}
154+
}
155+
setIsScrollingUp(newIsScrollingUp);
156+
};
134157
window.addEventListener('scroll', handleScroll);
135158

136159
return () => {
137160
window.removeEventListener('scroll', handleScroll);
138161
};
139162
}, [headerHeight]);
140163

141-
const currentSectionId = [...sections].sort(
142-
({ id: leftId }, { id: rightId }) => {
143-
const leftPassedInfo = passedElements[leftId] ?? {};
144-
const rightPassedInfo = passedElements[rightId] ?? {};
145-
if (`#${leftId}` === hash && leftPassedInfo.isInView) {
146-
return -1;
147-
} else if (`#${rightId}` === hash && rightPassedInfo.isInView) {
148-
return 1;
149-
} else if (leftPassedInfo.isInView && !rightPassedInfo.isInView) {
150-
return -1;
151-
} else if (rightPassedInfo.isInView && !leftPassedInfo.isInView) {
152-
return 1;
153-
} else if (rightPassedInfo.isPassed) {
154-
return 1;
155-
} else if (leftPassedInfo.isPassed) {
156-
return -1;
157-
} else {
158-
return 0;
164+
let currentSectionId: string | undefined = undefined;
165+
166+
// If a section header is visible then the header at the top of the page wins
167+
for (const section of sections) {
168+
const passedInfo = passedElements[section.id] ?? {};
169+
if (passedInfo.isInView) {
170+
currentSectionId = section.id;
171+
break;
172+
}
173+
}
174+
175+
// If no section header is in view then pick the last section header
176+
// that was scrolled passed
177+
if (currentSectionId === undefined) {
178+
for (const section of sections) {
179+
const passedInfo = passedElements[section.id] ?? {};
180+
if (passedInfo.isPassed) {
181+
currentSectionId = section.id;
159182
}
160183
}
161-
)[0]?.id;
184+
}
162185

163186
return (
164-
<Container
187+
<div
165188
className={classNames(
166-
'container',
167189
styles.container,
168190
isSticky ? styles.containerSticky : ''
169191
)}
170192
style={{
171-
top: headerHeight,
193+
top: isScrollingUp ? headerHeight : '0px',
172194
backgroundColor:
173195
isSticky && stickyBackgroundColor ? stickyBackgroundColor : undefined,
174196
}}
175197
>
176-
<Row className="justify-content-center">
177-
<Col md={11}>
178-
<nav
179-
ref={stickyDivRef}
180-
className={classnames('d-flex flex-row', styles.nav)}
181-
style={{
182-
gap: '40px',
183-
height: '49px',
184-
}}
185-
>
186-
{isSticky && (
187-
<Link
188-
className={classnames(styles.stickyHeader)}
189-
// # is removed from link so we have to use onclick to scroll to the top
190-
to="#"
191-
onClick={e => {
192-
e.preventDefault();
193-
window.scrollTo({
194-
top: 0,
195-
behavior: 'smooth',
196-
});
197-
}}
198-
>
199-
{title}
200-
</Link>
201-
)}
202-
<div
203-
className="d-flex flex-row"
198+
<Container>
199+
<Row className="justify-content-center">
200+
<Col md={11}>
201+
<nav
202+
ref={stickyDivRef}
203+
className={classnames('d-flex flex-row', styles.nav)}
204204
style={{
205-
gap: '10px',
205+
gap: '40px',
206+
height: '49px',
206207
}}
207208
>
208-
{sections.map(({ id, label }) => {
209-
const isInSection = currentSectionId === id;
210-
return (
211-
<Link
212-
key={id}
213-
to={`#${id}`}
214-
className={classNames(
215-
styles.stickySection,
216-
isInSection ? styles.stickySectionSelected : ''
217-
)}
218-
>
219-
{label}
220-
</Link>
221-
);
222-
})}
223-
</div>
224-
</nav>
225-
</Col>
226-
</Row>
227-
</Container>
209+
{isSticky && (
210+
<Link
211+
className={classnames(styles.stickyHeader)}
212+
// # is removed from link so we have to use onclick to scroll to the top
213+
to="#"
214+
onClick={e => {
215+
e.preventDefault();
216+
window.scrollTo({
217+
top: 0,
218+
behavior: 'smooth',
219+
});
220+
}}
221+
>
222+
{title}
223+
</Link>
224+
)}
225+
<div
226+
className="d-flex flex-row"
227+
style={{
228+
gap: '10px',
229+
}}
230+
>
231+
{sections.map(({ id, label }) => {
232+
const isInSection = currentSectionId === id;
233+
return (
234+
<Link
235+
key={id}
236+
to={`#${id}`}
237+
className={classNames(
238+
styles.stickySection,
239+
isInSection ? styles.stickySectionSelected : ''
240+
)}
241+
>
242+
{label}
243+
</Link>
244+
);
245+
})}
246+
</div>
247+
</nav>
248+
</Col>
249+
</Row>
250+
</Container>
251+
</div>
228252
);
229253
}

0 commit comments

Comments
 (0)