import React, { useEffect, useState, useRef, useMemo, useCallback, } from 'react'; import { Nav, NavItem, NavLink, } from 'reactstrap'; import { ICustomNavTabMappings } from '~/interfaces/ui'; import styles from './CustomNav.module.scss'; function getBreakpointOneLevelLarger(breakpoint) { switch (breakpoint) { case 'sm': return 'md'; case 'md': return 'lg'; case 'lg': return 'xl'; case 'xl': default: return 'xxl'; } } type CustomNavDropdownProps = { navTabMapping: ICustomNavTabMappings, activeTab: string, onNavSelected: (activeTabKey: string) => void, }; export const CustomNavDropdown = (props: CustomNavDropdownProps): JSX.Element => { const { activeTab, navTabMapping, onNavSelected, } = props; const { Icon, i18n } = navTabMapping[activeTab]; const menuItemClickHandler = useCallback((key) => { if (onNavSelected != null) { onNavSelected(key); } }, [onNavSelected]); return (
{Object.entries(navTabMapping).map(([key, value]) => { const isActive = activeTab === key; const _isLinkEnabled = value.isLinkEnabled ?? true; const isLinkEnabled = typeof _isLinkEnabled === 'boolean' ? _isLinkEnabled : _isLinkEnabled(value); const { Icon, i18n } = value; return ( ); })}
); }; type CustomNavTabProps = { activeTab: string, navTabMapping: ICustomNavTabMappings, onNavSelected?: (selectedTabKey: string) => void, hideBorderBottom?: boolean, breakpointToHideInactiveTabsDown?: 'xs' | 'sm' | 'md' | 'lg' | 'xl', navRightElement?: JSX.Element, }; export const CustomNavTab = (props: CustomNavTabProps): JSX.Element => { const [sliderWidth, setSliderWidth] = useState(0); const [sliderMarginLeft, setSliderMarginLeft] = useState(0); const { activeTab, navTabMapping, onNavSelected, hideBorderBottom, breakpointToHideInactiveTabsDown, navRightElement, } = props; const navContainerRef = useRef(null); const navTabRefs: { [key: string]: HTMLAnchorElement } = useMemo(() => { const obj = {}; Object.keys(navTabMapping).forEach((key) => { obj[key] = React.createRef(); }); return obj; }, [navTabMapping]); const navLinkClickHandler = useCallback((key) => { if (onNavSelected != null) { onNavSelected(key); } }, [onNavSelected]); function registerNavLink(key: string, anchorElem: HTMLAnchorElement) { if (anchorElem != null) { navTabRefs[key] = anchorElem; } } // Might make this dynamic for px, %, pt, em function getPercentage(min, max) { return min / max * 100; } useEffect(() => { if (activeTab == null || activeTab === '') { return; } if (navContainerRef.current == null) { return; } const navContainer = navContainerRef.current; let marginLeft = 0; for (const [key, anchorElem] of Object.entries(navTabRefs)) { const width = getPercentage(anchorElem.offsetWidth, navContainer.offsetWidth); if (key === activeTab) { setSliderWidth(width); setSliderMarginLeft(marginLeft); break; } marginLeft += width; } }, [activeTab, navTabRefs, navTabMapping]); // determine inactive classes to hide NavItem const inactiveClassnames = []; if (breakpointToHideInactiveTabsDown != null) { const breakpointOneLevelLarger = getBreakpointOneLevelLarger(breakpointToHideInactiveTabsDown); inactiveClassnames.push('d-none'); inactiveClassnames.push(`d-${breakpointOneLevelLarger}-block`); } return (
{navRightElement}

{ !hideBorderBottom &&
}
); }; const CustomNav = (props) => { const tabClassnames = ['d-none']; const dropdownClassnames = ['d-block']; // determine classes to show/hide const breakpointOneLevelLarger = getBreakpointOneLevelLarger(props.breakpointToSwitchDropdownDown); tabClassnames.push(`d-${breakpointOneLevelLarger}-block`); dropdownClassnames.push(`d-${breakpointOneLevelLarger}-none`); return (
); }; CustomNav.propTypes = { activeTab: PropTypes.string.isRequired, navTabMapping: PropTypes.object.isRequired, onNavSelected: PropTypes.func, hideBorderBottom: PropTypes.bool, breakpointToHideInactiveTabsDown: PropTypes.oneOf(['xs', 'sm', 'md', 'lg', 'xl']), breakpointToSwitchDropdownDown: PropTypes.oneOf(['xs', 'sm', 'md', 'lg', 'xl']), }; CustomNav.defaultProps = { hideBorderBottom: false, breakpointToSwitchDropdownDown: 'sm', }; export default CustomNav;