import React, { useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef } from 'react';
import { colors } from '../../config.json';
import { messages } from '../../shared/gettext';
import { useAppContext } from '../context';
import { useHistory } from '../lib/history';
import { useCombinedRefs } from '../lib/utilityHooks';
import CustomScrollbars, { CustomScrollbarsRef, IScrollEvent } from './CustomScrollbars';
import { BackActionContext } from './KeyboardNavigation';
import {
StyledBackBarItemButton,
StyledBackBarItemIcon,
StyledNavigationBar,
StyledNavigationBarSeparator,
StyledNavigationItems,
StyledTitleBarItemLabel,
} 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,
};
public componentDidMount() {
this.updateBarAppearance({ scrollLeft: 0, scrollTop: 0 });
}
public render() {
return (
{this.props.children}
);
}
public onScroll = (event: IScrollEvent) => {
this.updateBarAppearance(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 {
className?: string;
fillContainer?: boolean;
children?: React.ReactNode;
}
export const NavigationScrollbars = React.forwardRef(function NavigationScrollbarsT(
props: INavigationScrollbarsProps,
forwardedRef?: React.Ref,
) {
const history = useHistory();
const { setNavigationHistory } = useAppContext();
const { onScroll } = useContext(NavigationScrollContext);
const ref = useRef();
const combinedRefs = useCombinedRefs(forwardedRef, ref);
useEffect(() => {
const beforeunload = () => {
if (ref.current) {
history.location.state.scrollPosition = ref.current.getScrollPosition();
setNavigationHistory(history.asObject);
}
};
window.addEventListener('beforeunload', beforeunload);
return () => window.removeEventListener('beforeunload', beforeunload);
}, []);
useLayoutEffect(() => {
const location = history.location;
if (history.action === 'POP') {
ref.current?.scrollTo(...location.state.scrollPosition);
}
return () => {
if (history.action === 'PUSH' && ref.current) {
location.state.scrollPosition = ref.current.getScrollPosition();
setNavigationHistory(history.asObject);
}
};
}, []);
const handleScroll = useCallback((event: IScrollEvent) => {
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);
return (
{props.children}
{showsBarSeparator && }
);
};
interface INavigationItemsProps {
children: React.ReactNode;
}
export function NavigationItems(props: INavigationItemsProps) {
const { parentBackAction } = useContext(BackActionContext);
return (
{parentBackAction && }
{props.children}
);
}
interface ITitleBarItemProps {
children?: React.ReactText;
}
export const TitleBarItem = React.memo(function TitleBarItemT(props: ITitleBarItemProps) {
const { visible } = useContext(TitleBarItemContext);
return {props.children};
});
export function BackBarItem() {
const history = useHistory();
const backIcon = useMemo(() => history.length > 2, []);
const { parentBackAction } = useContext(BackActionContext);
const iconSource = backIcon ? 'icon-back' : 'icon-close-down';
const ariaLabel = backIcon ? messages.gettext('Back') : messages.gettext('Close');
return (
);
}