CustomNavigation.jsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. import React, {
  2. useEffect, useState, useRef, useMemo, useCallback,
  3. } from 'react';
  4. import PropTypes from 'prop-types';
  5. import {
  6. Nav, NavItem, NavLink, TabContent, TabPane,
  7. } from 'reactstrap';
  8. export const CustomNav = (props) => {
  9. const navContainer = useRef();
  10. const [sliderWidth, setSliderWidth] = useState(0);
  11. const [sliderMarginLeft, setSliderMarginLeft] = useState(0);
  12. const { activeTab, navTabMapping, onNavSelected } = props;
  13. const navTabRefs = useMemo(() => {
  14. const obj = {};
  15. Object.keys(navTabMapping).forEach((key) => {
  16. obj[key] = React.createRef();
  17. });
  18. return obj;
  19. }, [navTabMapping]);
  20. const navLinkClickHandler = useCallback((key) => {
  21. if (onNavSelected != null) {
  22. onNavSelected(key);
  23. }
  24. }, [onNavSelected]);
  25. function registerNavLink(key, elm) {
  26. if (elm != null) {
  27. navTabRefs[key] = elm;
  28. }
  29. }
  30. // Might make this dynamic for px, %, pt, em
  31. function getPercentage(min, max) {
  32. return min / max * 100;
  33. }
  34. useEffect(() => {
  35. if (activeTab === '') {
  36. return;
  37. }
  38. if (navContainer == null) {
  39. return;
  40. }
  41. let tempML = 0;
  42. const styles = Object.entries(navTabRefs).map((el) => {
  43. const width = getPercentage(el[1].offsetWidth, navContainer.current.offsetWidth);
  44. const marginLeft = tempML;
  45. tempML += width;
  46. return { width, marginLeft };
  47. });
  48. const { width, marginLeft } = styles[navTabMapping[activeTab].index];
  49. setSliderWidth(width);
  50. setSliderMarginLeft(marginLeft);
  51. }, [activeTab, navTabRefs, navTabMapping]);
  52. return (
  53. <>
  54. <div ref={navContainer}>
  55. <Nav className="nav-title grw-custom-navbar" id="grw-custom-navbar">
  56. {Object.entries(navTabMapping).map(([key, value]) => {
  57. const isActive = activeTab === key;
  58. const isLinkEnabled = value.isLinkEnabled != null ? value.isLinkEnabled(value) : true;
  59. const { Icon, i18n } = value;
  60. return (
  61. <NavItem
  62. key={key}
  63. className={`p-0 grw-custom-navtab ${isActive && 'active'}`}
  64. >
  65. <NavLink type="button" key={key} innerRef={elm => registerNavLink(key, elm)} disabled={!isLinkEnabled} onClick={() => navLinkClickHandler(key)}>
  66. <Icon /> {i18n}
  67. </NavLink>
  68. </NavItem>
  69. );
  70. })}
  71. </Nav>
  72. </div>
  73. <hr className="my-0 grw-nav-slide-hr border-none" style={{ width: `${sliderWidth}%`, marginLeft: `${sliderMarginLeft}%` }} />
  74. </>
  75. );
  76. };
  77. CustomNav.propTypes = {
  78. activeTab: PropTypes.string.isRequired,
  79. navTabMapping: PropTypes.object.isRequired,
  80. onNavSelected: PropTypes.func,
  81. };
  82. export const CustomTabContent = (props) => {
  83. const { activeTab, navTabMapping, additionalClassNames } = props;
  84. return (
  85. <TabContent activeTab={activeTab} className={additionalClassNames.join(' ')}>
  86. {Object.entries(navTabMapping).map(([key, value]) => {
  87. const { Content } = value;
  88. return (
  89. <TabPane key={key} tabId={key}>
  90. <Content />
  91. </TabPane>
  92. );
  93. })}
  94. </TabContent>
  95. );
  96. };
  97. CustomTabContent.propTypes = {
  98. activeTab: PropTypes.string.isRequired,
  99. navTabMapping: PropTypes.object.isRequired,
  100. additionalClassNames: PropTypes.arrayOf(PropTypes.string),
  101. };
  102. CustomTabContent.defaultProps = {
  103. additionalClassNames: [],
  104. };
  105. const CustomNavigation = (props) => {
  106. const { navTabMapping, defaultTabIndex, tabContentClasses } = props;
  107. const [activeTab, setActiveTab] = useState(Object.keys(props.navTabMapping)[defaultTabIndex || 0]);
  108. return (
  109. <React.Fragment>
  110. <CustomNav activeTab={activeTab} navTabMapping={navTabMapping} onNavSelected={setActiveTab} />
  111. <CustomTabContent activeTab={activeTab} navTabMapping={navTabMapping} additionalClassNames={tabContentClasses} />
  112. </React.Fragment>
  113. );
  114. };
  115. CustomNavigation.propTypes = {
  116. navTabMapping: PropTypes.object.isRequired,
  117. defaultTabIndex: PropTypes.number,
  118. tabContentClasses: PropTypes.arrayOf(PropTypes.string),
  119. };
  120. CustomNavigation.defaultProps = {
  121. tabContentClasses: ['p-4'],
  122. };
  123. export default CustomNavigation;