import React, { useCallback, useContext, useLayoutEffect, useRef } from 'react'; import { colors } from '../../config.json'; import { messages } from '../../shared/gettext'; import useActions from '../lib/actionsHook'; import { useHistory } from '../lib/history'; import { useCombinedRefs } from '../lib/utilityHooks'; import { useSelector } from '../redux/store'; import userInterface from '../redux/userinterface/actions'; import CustomScrollbars, { CustomScrollbarsRef, IScrollEvent } from './CustomScrollbars'; import { StyledBackBarItemButton, StyledBackBarItemIcon, StyledBackBarItemLabel, StyledCloseBarItemButton, StyledCloseBarItemIcon, StyledNavigationBar, StyledNavigationBarSeparator, StyledTitleBarItemLabel, } from './NavigationBarStyles'; export { StyledNavigationItems as NavigationItems } from './NavigationBarStyles'; interface INavigationContainerProps { children?: React.ReactNode; } interface INavigationContainerState { showsBarTitle: boolean; showsBarSeparator: boolean; } const NavigationScrollContext = React.createContext({ showsBarTitle: false, showsBarSeparator: false, onScroll(_event: IScrollEvent): void { throw Error('NavigationScrollContext provider missing'); }, }); export class NavigationContainer extends React.Component< INavigationContainerProps, INavigationContainerState > { public state = { showsBarTitle: false, showsBarSeparator: false, }; private scrollEventListeners: Array<(event: IScrollEvent) => void> = []; public componentDidMount() { this.updateBarAppearance({ scrollLeft: 0, scrollTop: 0 }); } public render() { return ( {this.props.children} ); } public onScroll = (event: IScrollEvent) => { this.notifyScrollEventListeners(event); this.updateBarAppearance(event); }; public addScrollEventListener(fn: (event: IScrollEvent) => void) { const index = this.scrollEventListeners.indexOf(fn); if (index === -1) { this.scrollEventListeners.push(fn); } } public removeScrollEventListener(fn: (event: IScrollEvent) => void) { const index = this.scrollEventListeners.indexOf(fn); if (index !== -1) { this.scrollEventListeners.splice(index, 1); } } private notifyScrollEventListeners(event: IScrollEvent) { this.scrollEventListeners.forEach((listener) => listener(event)); } private updateBarAppearance(event: IScrollEvent) { // that's where SettingsHeader.HeaderTitle intersects the navigation bar const showsBarSeparator = event.scrollTop > 11; // that's when SettingsHeader.HeaderTitle goes behind the navigation bar const showsBarTitle = event.scrollTop > 20; if ( this.state.showsBarSeparator !== showsBarSeparator || this.state.showsBarTitle !== showsBarTitle ) { this.setState({ showsBarSeparator, showsBarTitle }); } } } interface INavigationScrollbarsProps { onScroll?: (value: IScrollEvent) => void; className?: string; fillContainer?: boolean; children?: React.ReactNode; } export const NavigationScrollbars = React.forwardRef(function NavigationScrollbarsT( props: INavigationScrollbarsProps, forwardedRef?: React.Ref, ) { const history = useHistory(); const { onScroll } = useContext(NavigationScrollContext); const ref = useRef(); const combinedRefs = useCombinedRefs(forwardedRef, ref); const { addScrollPosition, removeScrollPosition } = useActions(userInterface); const scrollPosition = useSelector( (state) => state.userInterface.scrollPosition[history.location.pathname], ); useLayoutEffect(() => { const path = history.location.pathname; if (history.action === 'POP' && scrollPosition) { ref.current?.scrollTo(...scrollPosition); removeScrollPosition(path); } return () => { if (history.action === 'PUSH' && ref.current) { addScrollPosition(path, ref.current.getScrollPosition()); } }; }, []); const handleScroll = useCallback((event: IScrollEvent) => { onScroll(event); props.onScroll?.(event); }, []); return ( {props.children} ); }); const TitleBarItemContext = React.createContext({ visible: false, }); interface INavigationBarProps { children?: React.ReactNode; alwaysDisplayBarTitle?: boolean; } export const NavigationBar = function NavigationBarT(props: INavigationBarProps) { const { showsBarSeparator, showsBarTitle } = useContext(NavigationScrollContext); const unpinnedWindow = useSelector((state) => state.settings.guiSettings.unpinnedWindow); return ( {props.children} {showsBarSeparator && } ); }; interface ITitleBarItemProps { children?: React.ReactText; } export const TitleBarItem = React.memo(function TitleBarItemT(props: ITitleBarItemProps) { const { visible } = useContext(TitleBarItemContext); return {props.children}; }); interface ICloseBarItemProps { action: () => void; } export function CloseBarItem(props: ICloseBarItemProps) { return ( ); } interface IBackBarItemProps { children?: React.ReactText; action: () => void; } export function BackBarItem(props: IBackBarItemProps) { return ( {props.children} ); }