CustomNavigation.jsx 4.3 KB

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