import { isFunction, throttle } from 'lodash'; import React from 'react'; import type { CoreReactInject } from '../../reactShims/react.injector'; import { ScrollToService } from '../../utils/scrollTo/scrollTo.service'; import { UUIDGenerator } from '../../utils/uuid.service'; export interface INavigationPage { key: string; label: string; visible?: boolean; badge?: string; } export interface IPageNavigatorProps { scrollableContainer: string; deepLinkParam?: string; hideNavigation?: boolean; reactInjector: CoreReactInject; } export interface IPageNavigatorState { id: string; currentPageKey: string; pages: INavigationPage[]; } export class PageNavigator extends React.Component { 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 && this.props.reactInjector.$stateParams[deepLinkParam]) { this.setCurrentSection(this.props.reactInjector.$stateParams[deepLinkParam]); } const pages = React.Children.map(children, (child: any) => { if (child.props.pageKey && child.props.label) { 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(); const 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) { this.props.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 (
{!hideNavigation && (
)}
{updatedChildren}
); } }