import { MarkdownHeading } from '../@types/graphql-types';
import HeadingAnchor from './HeadingAnchor';
-const MINIMUM_TOP_MARGIN = 80;
const HEADER_SCROLL_MARGIN = 100;
interface Props {
interface State {
activeIndex: number;
headers: MarkdownHeading[];
- marginTop: number;
}
export default class HeadingsLink extends React.PureComponent<Props, State> {
activeIndex: -1,
headers: props.headers.filter(
h => h.depth === 2 && h.value && h.value.toLowerCase() !== 'table of contents'
- ),
- marginTop: MINIMUM_TOP_MARGIN
+ )
};
}
componentDidMount() {
document.addEventListener('scroll', this.scrollHandler, true);
- this.scrollHandler();
}
componentWillReceiveProps(nextProps: Props) {
this.setState({
+ activeIndex: -1,
headers: nextProps.headers.filter(
h => h.depth === 2 && h.value && h.value.toLowerCase() !== 'table of contents'
)
document.removeEventListener('scroll', this.scrollHandler, true);
}
+ scrollHandler = () => {
+ if (this.skipScrollingHandler) {
+ this.skipScrollingHandler = false;
+ return;
+ }
+
+ const scrollTop = window.pageYOffset || document.body.scrollTop;
+ this.highlightHeading(scrollTop);
+ };
+
highlightHeading = (scrollTop: number) => {
let headingIndex = 0;
for (let i = 0; i < this.state.headers.length; i++) {
}
headingIndex = i;
}
- const scrollLimit = document.body.scrollHeight - document.body.clientHeight;
- this.setState({
- activeIndex: headingIndex,
- marginTop: Math.max(MINIMUM_TOP_MARGIN, Math.min(scrollTop, scrollLimit))
- });
+ this.setState({ activeIndex: headingIndex });
this.markH2(headingIndex + 1, false);
};
}
};
- scrollHandler = () => {
- if (this.skipScrollingHandler) {
- this.skipScrollingHandler = false;
- return;
- }
-
- const scrollTop = window.pageYOffset || document.body.scrollTop;
- this.highlightHeading(scrollTop);
- };
-
clickHandler = (index: number) => {
this.markH2(index, true);
};
}
return (
- <div className="headings-container" style={{ marginTop: this.state.marginTop + 'px' }}>
- <span>On this page</span>
- <ul>
- {headers.map((header, index) => {
- return (
- <HeadingAnchor
- active={this.state.activeIndex === index}
- clickHandler={this.clickHandler}
- index={index + 1}
- key={index}>
- {header.value}
- </HeadingAnchor>
- );
- })}
- </ul>
+ <div className="headings-container">
+ <div className="headings-container-fixed">
+ <span>On this page</span>
+ <ul>
+ {headers.map((header, index) => {
+ return (
+ <HeadingAnchor
+ active={this.state.activeIndex === index}
+ clickHandler={this.clickHandler}
+ index={index + 1}
+ key={index}>
+ {header.value}
+ </HeadingAnchor>
+ );
+ })}
+ </ul>
+ </div>
</div>
);
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { shallow } from 'enzyme';
+import * as React from 'react';
+import HeadingsLink from '../HeadingsLink';
+
+it('should render correctly', () => {
+ expect(shallowRender()).toMatchSnapshot();
+});
+
+function shallowRender(props: Partial<HeadingsLink['props']> = {}) {
+ return shallow(
+ <HeadingsLink
+ headers={[
+ { value: 'Table of Contents', depth: 2 },
+ { value: 'Foo', depth: 2 },
+ { value: 'Br', depth: 2 }
+ ]}
+ {...props}
+ />
+ );
+}