| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234 |
- import React, {
- useEffect, useState, useRef, useMemo, useCallback,
- } from 'react';
- import PropTypes from 'prop-types';
- import {
- Nav, NavItem, NavLink,
- } from 'reactstrap';
- function getBreakpointOneLevelLarger(breakpoint) {
- switch (breakpoint) {
- case 'sm':
- return 'md';
- case 'md':
- return 'lg';
- case 'lg':
- return 'xl';
- case 'xl':
- default:
- return '2xl';
- }
- }
- export const CustomNavDropdown = (props) => {
- const {
- activeTab, navTabMapping, onNavSelected,
- } = props;
- const activeObj = navTabMapping[activeTab];
- const menuItemClickHandler = useCallback((key) => {
- if (onNavSelected != null) {
- onNavSelected(key);
- }
- }, [onNavSelected]);
- return (
- <div className="grw-custom-nav-dropdown btn-group btn-block">
- <button
- className="btn btn-outline-primary btn-lg btn-block dropdown-toggle text-right"
- type="button"
- data-toggle="dropdown"
- aria-haspopup="true"
- aria-expanded="false"
- >
- <span className="float-left">
- { activeObj != null && (
- <><activeObj.Icon /> {activeObj.i18n}</>
- ) }
- </span>
- </button>
- <div className="dropdown-menu dropdown-menu-right">
- {Object.entries(navTabMapping).map(([key, value]) => {
- const isActive = activeTab === key;
- const isLinkEnabled = value.isLinkEnabled != null ? value.isLinkEnabled(value) : true;
- const { Icon, i18n } = value;
- return (
- <button
- key={key}
- type="button"
- className={`dropdown-item px-3 py-2 ${isActive ? 'active' : ''}`}
- disabled={!isLinkEnabled}
- onClick={() => menuItemClickHandler(key)}
- >
- <Icon /> {i18n}
- </button>
- );
- })}
- </div>
- </div>
- );
- };
- CustomNavDropdown.propTypes = {
- activeTab: PropTypes.string.isRequired,
- navTabMapping: PropTypes.object.isRequired,
- onNavSelected: PropTypes.func,
- };
- export const CustomNavTab = (props) => {
- const navContainer = useRef();
- const [sliderWidth, setSliderWidth] = useState(0);
- const [sliderMarginLeft, setSliderMarginLeft] = useState(0);
- const {
- activeTab, navTabMapping, onNavSelected, hideBorderBottom, breakpointToHideInactiveTabsDown, navRightElement,
- } = props;
- const navTabRefs = 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, elm) {
- if (elm != null) {
- navTabRefs[key] = elm;
- }
- }
- // Might make this dynamic for px, %, pt, em
- function getPercentage(min, max) {
- return min / max * 100;
- }
- useEffect(() => {
- if (activeTab === '') {
- return;
- }
- if (navContainer == null) {
- return;
- }
- let tempML = 0;
- const styles = Object.entries(navTabRefs).map((el) => {
- const width = getPercentage(el[1].offsetWidth, navContainer.current.offsetWidth);
- const marginLeft = tempML;
- tempML += width;
- return { width, marginLeft };
- });
- const { width, marginLeft } = styles[navTabMapping[activeTab].index];
- setSliderWidth(width);
- setSliderMarginLeft(marginLeft);
- }, [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 (
- <div className="grw-custom-nav-tab">
- <div ref={navContainer} className="d-flex justify-content-between">
- <Nav className="nav-title">
- {Object.entries(navTabMapping).map(([key, value]) => {
- const isActive = activeTab === key;
- const isLinkEnabled = value.isLinkEnabled != null ? value.isLinkEnabled(value) : true;
- const { Icon, i18n } = value;
- return (
- <NavItem
- key={key}
- className={`p-0 ${isActive ? 'active' : inactiveClassnames.join(' ')}`}
- >
- <NavLink type="button" key={key} innerRef={elm => registerNavLink(key, elm)} disabled={!isLinkEnabled} onClick={() => navLinkClickHandler(key)}>
- <Icon /> {i18n}
- </NavLink>
- </NavItem>
- );
- })}
- </Nav>
- {navRightElement}
- </div>
- <hr className="my-0 grw-nav-slide-hr border-none" style={{ width: `${sliderWidth}%`, marginLeft: `${sliderMarginLeft}%` }} />
- { !hideBorderBottom && <hr className="my-0 border-top-0 border-bottom" /> }
- </div>
- );
- };
- CustomNavTab.propTypes = {
- activeTab: PropTypes.string.isRequired,
- navTabMapping: PropTypes.object.isRequired,
- onNavSelected: PropTypes.func,
- hideBorderBottom: PropTypes.bool,
- breakpointToHideInactiveTabsDown: PropTypes.oneOf(['xs', 'sm', 'md', 'lg', 'xl']),
- navRightElement: PropTypes.node,
- };
- CustomNavTab.defaultProps = {
- hideBorderBottom: false,
- };
- 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 (
- <div className="grw-custom-nav">
- <div className={tabClassnames.join(' ')}>
- <CustomNavTab {...props} />
- </div>
- <div className={dropdownClassnames.join(' ')}>
- <CustomNavDropdown {...props} />
- </div>
- </div>
- );
- };
- 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;
|