Skip to content

Commit

Permalink
refactor(core): add React Components for PageSection and PageNavigator (
Browse files Browse the repository at this point in the history
  • Loading branch information
Jammy Louie committed May 15, 2019
1 parent ce8a163 commit dfc84b1
Show file tree
Hide file tree
Showing 5 changed files with 246 additions and 6 deletions.
1 change: 1 addition & 0 deletions app/scripts/modules/core/src/presentation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ export * from './collapsibleSection/CollapsibleSection';
export * from './forms';
export * from './robotToHumanFilter/robotToHuman.filter';
export * from './sortToggle';
export * from './navigation';
182 changes: 182 additions & 0 deletions app/scripts/modules/core/src/presentation/navigation/PageNavigator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import * as React from 'react';
import { isFunction, throttle } from 'lodash';

import { ReactInjector } from 'core/reactShims';
import { ScrollToService } from 'core/utils/scrollTo/scrollTo.service';
import { UUIDGenerator } from 'core/utils/uuid.service';

export interface INavigationPage {
key: string;
label: string;
visible?: boolean;
badge?: string;
}

export interface IPageNavigatorProps {
scrollableContainer: string;
deepLinkParam?: string;
hideNavigation?: boolean;
}

export interface IPageNavigatorState {
id: string;
currentPageKey: string;
pages: INavigationPage[];
}

export class PageNavigator extends React.Component<IPageNavigatorProps, IPageNavigatorState> {
private element: JQuery;
private container: any;
private navigator: any;

constructor(props: IPageNavigatorProps) {
super(props);
this.state = {
id: UUIDGenerator.generateUuid(),
currentPageKey: null,
pages: [],
};
}

public componentDidMount(): void {
const { children, deepLinkParam, hideNavigation, scrollableContainer } = this.props;
this.container = this.element.closest(scrollableContainer);
if (isFunction(this.container.bind) && !hideNavigation) {
this.container.bind(this.getEventKey(), throttle(() => this.handleScroll(), 20));
}
this.navigator = this.element.find('.page-navigation');
if (deepLinkParam && ReactInjector.$stateParams[deepLinkParam]) {
this.setCurrentSection(ReactInjector.$stateParams[deepLinkParam]);
}

const pages = React.Children.map(children, (child: any) => {
if (child.type && child.type.name === 'PageSection') {
return {
key: child.props.pageKey,
label: child.props.label,
visible: child.props.visible !== false,
badge: child.props.badge,
};
}
return null;
});
this.setState({
pages,
currentPageKey: pages.length > 0 ? pages[0].key : null,
});
}

public componentWillUnmount(): void {
if (isFunction(this.container.unbind) && !this.props.hideNavigation) {
this.container.unbind(this.getEventKey());
}
}

private setCurrentSection(key: string): void {
this.setState({ currentPageKey: key });
this.syncLocation(key);
ScrollToService.scrollTo(`[data-page-id=${key}]`, this.props.scrollableContainer, this.container.offset().top);
this.container.find('.highlighted').removeClass('highlighted');
this.container.find(`[data-page-id=${key}]`).addClass('highlighted');
}

private getEventKey(): string {
return `scroll.pageNavigation.${this.state.id}`;
}

private handleScroll(): void {
const navigatorRect = this.element.get(0).getBoundingClientRect(),
scrollableContainerTop = this.container.get(0).getBoundingClientRect().top;

const currentPage = this.state.pages.find(p => {
const content = this.container.find(`[data-page-content=${p.key}]`);
if (content.length) {
return content.get(0).getBoundingClientRect().bottom > scrollableContainerTop;
}
return false;
});
if (currentPage) {
this.setState({ currentPageKey: currentPage.key });
this.syncLocation(currentPage.key);
this.navigator.find('li').removeClass('current');
this.navigator.find(`[data-page-navigation-link=${currentPage.key}]`).addClass('current');
}

if (navigatorRect.top < scrollableContainerTop) {
this.navigator.css({
position: 'fixed',
width: this.navigator.get(0).getBoundingClientRect().width,
top: scrollableContainerTop,
});
} else {
this.navigator.css({
position: 'relative',
top: 0,
width: '100%',
});
}
}

private syncLocation(key: string): void {
const { deepLinkParam } = this.props;
if (deepLinkParam) {
ReactInjector.$state.go('.', { [deepLinkParam]: key }, { notify: false, location: 'replace' });
}
}

private refCallback = (element: HTMLDivElement): void => {
if (element) {
this.element = $(element);
}
};

private updatePagesConfig(page: INavigationPage): void {
const pages = [...this.state.pages];
const pageConfig = pages.find(p => p.key === page.key);
if (pageConfig) {
pageConfig.badge = page.badge;
pageConfig.label = page.label;
pageConfig.visible = page.visible;
this.setState({ pages });
}
}

public render(): JSX.Element {
const { children, hideNavigation } = this.props;
const { currentPageKey, pages } = this.state;
const updatedChildren = React.Children.map(children, (child: any) =>
React.cloneElement(child, { updatePagesConfig: (p: INavigationPage) => this.updatePagesConfig(p) }),
);
return (
<div className="page-navigator">
<div className="row" ref={this.refCallback}>
{!hideNavigation && (
<div className="col-md-3 hidden-sm hidden-xs">
<ul className="page-navigation">
{pages.map((page: INavigationPage, index: number) => {
return (
page.visible && (
<li
key={index}
data-page-navigation-link={page.key}
className={currentPageKey === page.key ? 'current' : ''}
>
<a onClick={() => this.setCurrentSection(page.key)}>
{page.label}
{page.badge && <span> {'(' + page.badge + ')'}</span>}
</a>
</li>
)
);
})}
</ul>
</div>
)}
<div className={'col-md-' + (hideNavigation ? '12' : '9') + ' col-sm-12'}>
<div className="sections">{updatedChildren}</div>
</div>
</div>
</div>
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import * as React from 'react';

import { INavigationPage } from './PageNavigator';

interface IPageSectionProps {
pageKey: string;
label: string;
badge?: string;
visible?: boolean;
noWrapper?: boolean;
updatePagesConfig?: (page: INavigationPage) => void;
}

export class PageSection extends React.Component<IPageSectionProps> {
constructor(props: IPageSectionProps) {
super(props);
}

public componentDidUpdate(prevProps: IPageSectionProps): void {
const { badge, pageKey, label, updatePagesConfig, visible } = this.props;
if (
prevProps.visible !== this.props.visible ||
prevProps.badge !== this.props.badge ||
prevProps.label !== this.props.label
) {
updatePagesConfig &&
updatePagesConfig({
key: pageKey,
label,
visible: visible !== false,
badge,
});
}
}

public render(): JSX.Element {
const { children, pageKey, label, noWrapper, visible } = this.props;

return visible !== false ? (
<div className="page-section">
<div className="page-subheading" data-page-id={pageKey}>
<h4 className="sticky-header">{label}</h4>
<div className={noWrapper ? 'no-wrapper' : 'section-body'} data-page-content={pageKey}>
{children}
</div>
</div>
</div>
) : (
<></>
);
}
}
2 changes: 2 additions & 0 deletions app/scripts/modules/core/src/presentation/navigation/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './PageNavigator';
export * from './PageSection';
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
@import (reference) "~core/presentation/less/imports/commonImports.less";
@import (reference) '~core/presentation/less/imports/commonImports.less';

page-navigator {
page-navigator,
.page-navigator {
display: block;
.page-navigation {
list-style-type: none;
Expand All @@ -12,7 +13,8 @@ page-navigator {
}
li {
display: block;
&:hover, &.current {
&:hover,
&.current {
background-color: var(--color-accent-g2);
a {
color: var(--color-primary);
Expand All @@ -31,9 +33,11 @@ page-navigator {
}
}
}
page-section {
page-section,
.page-section {
display: block;
.no-wrapper, .section-body {
.no-wrapper,
.section-body {
margin: 10px 10px 20px 10px;
}
.no-wrapper {
Expand All @@ -56,7 +60,6 @@ page-navigator {
animation: 0.5s ease-in-out 0 highlighted;
animation-iteration-count: 1;
}

}
}

Expand Down

0 comments on commit dfc84b1

Please sign in to comment.