CustomNavigation.jsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  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. type="button"
  64. className={`p-0 grw-custom-navtab ${isActive && 'active'}}`}
  65. >
  66. <NavLink key={key} innerRef={elm => registerNavLink(key, elm)} disabled={!isLinkEnabled} onClick={() => navLinkClickHandler(key)}>
  67. <Icon /> {i18n}
  68. </NavLink>
  69. </NavItem>
  70. );
  71. })}
  72. </Nav>
  73. </div>
  74. <hr className="my-0 grw-nav-slide-hr border-none" style={{ width: `${sliderWidth}%`, marginLeft: `${sliderMarginLeft}%` }} />
  75. </>
  76. );
  77. };
  78. CustomNav.propTypes = {
  79. activeTab: PropTypes.string.isRequired,
  80. navTabMapping: PropTypes.object.isRequired,
  81. onNavSelected: PropTypes.func,
  82. };
  83. export const CustomTabContent = (props) => {
  84. const { activeTab, navTabMapping, additionalClassNames } = props;
  85. return (
  86. <TabContent activeTab={activeTab} className={additionalClassNames.join(' ')}>
  87. {Object.entries(navTabMapping).map(([key, value]) => {
  88. const { Content } = value;
  89. return (
  90. <TabPane key={key} tabId={key}>
  91. <Content />
  92. </TabPane>
  93. );
  94. })}
  95. </TabContent>
  96. );
  97. };
  98. CustomTabContent.propTypes = {
  99. activeTab: PropTypes.string.isRequired,
  100. navTabMapping: PropTypes.object.isRequired,
  101. additionalClassNames: PropTypes.arrayOf(PropTypes.string),
  102. };
  103. CustomTabContent.defaultProps = {
  104. additionalClassNames: [],
  105. };
  106. const CustomNavigation = (props) => {
  107. const { navTabMapping, defaultTabIndex, tabContentClasses } = props;
  108. const [activeTab, setActiveTab] = useState(Object.keys(props.navTabMapping)[defaultTabIndex || 0]);
  109. return (
  110. <React.Fragment>
  111. <CustomNav activeTab={activeTab} navTabMapping={navTabMapping} onNavSelected={setActiveTab} />
  112. <CustomTabContent activeTab={activeTab} navTabMapping={navTabMapping} additionalClassNames={tabContentClasses} />
  113. </React.Fragment>
  114. );
  115. };
  116. CustomNavigation.propTypes = {
  117. navTabMapping: PropTypes.object.isRequired,
  118. defaultTabIndex: PropTypes.number,
  119. tabContentClasses: PropTypes.arrayOf(PropTypes.string),
  120. };
  121. CustomNavigation.defaultProps = {
  122. tabContentClasses: ['p-4'],
  123. };
  124. export default CustomNavigation;