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

Added mini-navbar #1172

Merged
merged 1 commit into from
Oct 17, 2024
Merged
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@
"@types/react-select": "^3.0.21",
"@types/react-spinkit": "^3.0.5",
"@types/reactstrap": "^8.0.4",
"@types/resize-observer-browser": "^0.1.11",
"@types/webpack-env": "1.15.2",
"@typescript-eslint/eslint-plugin": "2.29.0",
"@typescript-eslint/parser": "2.29.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ const GeneticTypeTabs: FunctionComponent<{

return (
<div className={styles.tabs}>
{[GENETIC_TYPE.SOMATIC, GENETIC_TYPE.GERMLINE].map(geneOrigin => (
{[GENETIC_TYPE.SOMATIC, GENETIC_TYPE.GERMLINE].map((geneOrigin, idx) => (
<div
key={idx}
style={{ width: '50%' }}
className={
selected === geneOrigin
Expand Down
4 changes: 2 additions & 2 deletions src/main/webapp/app/components/infoTile/InfoTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ const InfoTile: React.FunctionComponent<InfoTile> = props => {
<div className={classnames(styles.tile, 'mr-2', props.className)}>
<div className={'h6 font-bold mb-2'}>{props.title}</div>
<div className={'d-flex'}>
{props.categories.map(category => (
<Category {...category} className={styles.category} />
{props.categories.map((category, idx) => (
<Category key={idx} {...category} className={styles.category} />
))}
</div>
</div>
Expand Down
25 changes: 19 additions & 6 deletions src/main/webapp/app/pages/genePage/SomaticGermlineGenePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ import {
UserGoogleGroupLink,
} from 'app/shared/links/SocialMediaLinks';
import styles from './GenePage.module.scss';
import StickyMiniNavBar from 'app/shared/nav/StickyMiniNavBar';
import MiniNavBarHeader from 'app/shared/nav/MiniNavBarHeader';
import { GenomicIndicatorTable } from 'app/pages/genePage/GenomicIndicatorTable';

interface MatchParams {
Expand Down Expand Up @@ -659,6 +661,17 @@ export default class SomaticGermlineGenePage extends React.Component<
geneticType={this.selectedGeneticType}
/>
<Container>
<StickyMiniNavBar
title={`${this.store.hugoSymbol} (${
this.isGermline ? 'Germline' : 'Somatic'
})`}
linkUnderlineColor={
this.isGermline ? '#D2A106' : '#0968C3'
}
stickyBackgroundColor={
this.isGermline ? '#FCF4D6' : '#F0F5FF'
}
/>
<Row className={`justify-content-center`}>
<Col md={11}>
{!this.hasContent && (
Expand Down Expand Up @@ -840,9 +853,9 @@ export default class SomaticGermlineGenePage extends React.Component<
</If>
{this.isGermline && (
<>
<h4 className={'mt-4'}>
<MiniNavBarHeader id="genomic-indicators">
Genomic Indicators
</h4>
</MiniNavBarHeader>
<GenomicIndicatorTable
data={this.store.genomicIndicators.result}
isPending={
Expand All @@ -853,9 +866,9 @@ export default class SomaticGermlineGenePage extends React.Component<
)}
{this.hasClinicalImplications && (
<>
<h4 className={'mt-4'}>
<MiniNavBarHeader id="clinical-implications">
Clinical Implications
</h4>
</MiniNavBarHeader>
<AlterationTableTabs
selectedTab={this.defaultSelectedTab}
hugoSymbol={this.store.hugoSymbol}
Expand All @@ -879,12 +892,12 @@ export default class SomaticGermlineGenePage extends React.Component<
{this.store.filteredBiologicalAlterations
.length > 0 && (
<>
<h4 className={'mt-4'}>
<MiniNavBarHeader id="annotated">
Annotated{' '}
{this.isGermline
? 'Variants'
: 'Alterations'}
</h4>
</MiniNavBarHeader>
<AnnotatedAlterations
germline={this.isGermline}
hugoSymbol={this.store.hugoSymbol}
Expand Down
13 changes: 13 additions & 0 deletions src/main/webapp/app/shared/nav/MiniNavBarHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from 'react';

type IMiniNavBarHeader = {
id: string;
children: React.ReactNode;
};
export default function MiniNavBarHeader({ id, children }: IMiniNavBarHeader) {
return (
<h4 id={id} className={'mt-4'} mini-nav-bar-header="">
{children}
</h4>
);
}
217 changes: 217 additions & 0 deletions src/main/webapp/app/shared/nav/StickyMiniNavBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
import React, { useEffect, useRef, useState, useCallback } from 'react';
import { Link, useLocation } from 'react-router-dom';
import { Row, Col } from 'react-bootstrap';

function getNavBarSectionElements() {
return document.querySelectorAll('[mini-nav-bar-header]');
}

function useScrollToHash({ stickyHeight }: { stickyHeight: number }) {
const location = useLocation();

useEffect(() => {
const hash = location.hash;
if (hash) {
let element: Element | null;
try {
element = document.querySelector(hash);
} catch {
element = null;
}
if (element) {
const targetPosition =
element.getBoundingClientRect().top + window.scrollY;
window.scrollTo({
behavior: 'smooth',
top: targetPosition - stickyHeight,
});
}
}
}, [location]);
}

type IStickyMiniNavBar = {
title: string;
linkUnderlineColor: string;
stickyBackgroundColor: string;
};

export default function StickyMiniNavBar({
title,
linkUnderlineColor,
stickyBackgroundColor,
}: IStickyMiniNavBar) {
const [headerHeight, setHeaderHeight] = useState(0);
const [isSticky, setIsSticky] = useState(false);
const [sections, setSections] = useState<
{ id: string; label: string | null }[]
>([]);
const [passedElements, setPassedElements] = useState<Record<string, boolean>>(
{}
);
const stickyDivRef = useRef<HTMLDivElement | null>(null);
useScrollToHash({
stickyHeight:
headerHeight +
(stickyDivRef.current?.getBoundingClientRect().height ?? 0),
});

useEffect(() => {
const newSections: typeof sections = [];
const miniNavBarSections = getNavBarSectionElements();
miniNavBarSections.forEach(ele => {
newSections.push({
id: ele.id,
label: ele.textContent,
});
});
setSections(newSections);

const headerElement = document.querySelector('header');

const updateHeaderHeight = () => {
if (headerElement) {
setHeaderHeight(headerElement.getBoundingClientRect().height);
}
};

updateHeaderHeight();

const resizeObserver = new ResizeObserver(() => {
updateHeaderHeight();
});

if (headerElement) {
resizeObserver.observe(headerElement);
}

return () => {
if (headerElement) {
resizeObserver.unobserve(headerElement);
}
};
}, []);

useEffect(() => {
const miniNavBarSections = getNavBarSectionElements();
const intersectionObserver = new IntersectionObserver(entries => {
const newPassedElements: typeof passedElements = {};
entries.forEach(entry => {
const targetId = entry.target.getAttribute('id') ?? '';
const hasId = sections.find(x => x.id === targetId);
newPassedElements[targetId] =
hasId !== undefined &&
(entry.isIntersecting || entry.boundingClientRect.y < 0);
});
setPassedElements(x => {
return {
...x,
...newPassedElements,
};
});
});
miniNavBarSections.forEach(x => intersectionObserver.observe(x));
return () => {
miniNavBarSections.forEach(x => intersectionObserver.unobserve(x));
};
}, [sections]);

const handleScroll = () => {
if (stickyDivRef.current) {
const stickyOffset = stickyDivRef.current.getBoundingClientRect().top;
setIsSticky(stickyOffset <= headerHeight);
}
};

useEffect(() => {
window.addEventListener('scroll', handleScroll);

return () => {
window.removeEventListener('scroll', handleScroll);
};
}, [headerHeight]);

let currentSectionId = sections[0]?.id;
for (let i = sections.length - 1; i >= 0; i--) {
const id = sections[i].id;
if (passedElements[id]) {
currentSectionId = id;
break;
}
}

return (
<Row
className="justify-content-center"
style={{
position: 'sticky',
top: headerHeight,
zIndex: 100,
backgroundColor: isSticky ? stickyBackgroundColor : undefined,
}}
>
<Col md={11}>
<nav
ref={stickyDivRef}
className="d-flex flex-row"
style={{
gap: '40px',
}}
>
{isSticky && (
<Link
className="h6 font-weight-bold"
// # is removed from link so we have to use onclick to scroll to the top
to="#"
style={{
color: '#000000',
fontFamily: 'Gotham Bold',
padding: '7px 0px',
}}
onClick={e => {
e.preventDefault();
window.scrollTo({
top: 0,
behavior: 'smooth',
});
}}
>
{title}
</Link>
)}
<div
className="d-flex flex-row"
style={{
gap: '10px',
}}
>
{sections.map(({ id, label }) => {
const isInSection = currentSectionId === id;
return (
<Link
key={id}
to={`#${id}`}
className={`h6 ${
isInSection ? 'font-weight-bold' : 'font-weight-normal'
}`}
style={{
color: '#000000',
borderColor: isInSection
? linkUnderlineColor
: 'transparent',
borderBottomStyle: 'solid',
borderBottomWidth: '4px',
fontFamily: isInSection ? 'Gotham Bold' : 'Gotham Book',
padding: '7px 0px',
}}
>
{label}
</Link>
);
})}
</div>
</nav>
</Col>
</Row>
);
}
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"suppressImplicitAnyIndexErrors": true,
"outDir": "target/classes/static/app",
"lib": ["dom", "es2020"],
"types": ["jest", "webpack-env"],
"types": ["jest", "webpack-env", "resize-observer-browser"],
"allowJs": true,
"checkJs": false,
"baseUrl": "./",
Expand Down
13 changes: 9 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2726,6 +2726,11 @@
"@types/react" "*"
popper.js "^1.14.1"

"@types/resize-observer-browser@^0.1.11":
version "0.1.11"
resolved "https://registry.yarnpkg.com/@types/resize-observer-browser/-/resize-observer-browser-0.1.11.tgz#d3c98d788489d8376b7beac23863b1eebdd3c13c"
integrity sha512-cNw5iH8JkMkb3QkCoe7DaZiawbDQEUX8t7iuQaRTyLOyQCR2h+ibBD4GJt7p5yhUHrlOeL7ZtbxNHeipqNsBzQ==

"@types/resize-observer-browser@^0.1.5":
version "0.1.5"
resolved "https://registry.yarnpkg.com/@types/resize-observer-browser/-/resize-observer-browser-0.1.5.tgz#36d897708172ac2380cd486da7a3daf1161c1e23"
Expand Down Expand Up @@ -13180,10 +13185,10 @@ oncokb-styles@~0.1.2:
resolved "https://registry.yarnpkg.com/oncokb-styles/-/oncokb-styles-0.1.2.tgz#8b26c0a0829787cdc1b595d3a021b3266607102b"
integrity sha512-tuy5s3qFxgf1ogMATQSRPNgLlAMrvOOTCAN1dm/wJ+VZoStbJ7g36/qHwc99UPfh3vrB05broLodF+k58p5tUw==

oncokb-styles@~1.6.0-alpha.0:
version "1.6.0-alpha.0"
resolved "https://registry.yarnpkg.com/oncokb-styles/-/oncokb-styles-1.6.0-alpha.0.tgz#5985b23a91583503d9133a2fefbb785d65342699"
integrity sha512-1k5glbxYOg6R8HtMXyXhAnSAmP5MfaZ9ggX1ncLVdwqsHHire19Sxu/RoVtSjdzQQ/m98QfAmd01qGOQZU87WA==
oncokb-styles@~1.4.0-alpha.0:
version "1.4.2"
resolved "https://registry.yarnpkg.com/oncokb-styles/-/oncokb-styles-1.4.2.tgz#ad601699636875abe425d80b25c050d28d47c2bc"
integrity sha512-dq/w/OZv7oTjQzyXRo54ldC3PiHHu36eVuFmS0U5PGlk3Qx8XfB9XSwELHKTgmuen5H8YKQJxc/h3cBlFBF7Xw==

oncokb-ts-api-client@^1.0.4:
version "1.0.4"
Expand Down
Loading