Sidebar.jsx 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. import React from 'react';
  2. import PropTypes from 'prop-types';
  3. import {
  4. withNavigationUIController,
  5. LayoutManager,
  6. NavigationProvider,
  7. ThemeProvider,
  8. } from '@atlaskit/navigation-next';
  9. import { createSubscribedElement } from './UnstatedUtils';
  10. import AppContainer from '../services/AppContainer';
  11. import SidebarNav from './Sidebar/SidebarNav';
  12. import RecentChanges from './Sidebar/RecentChanges';
  13. import CustomSidebar from './Sidebar/CustomSidebar';
  14. const sidebarDefaultWidth = 240;
  15. class Sidebar extends React.Component {
  16. static propTypes = {
  17. appContainer: PropTypes.instanceOf(AppContainer).isRequired,
  18. navigationUIController: PropTypes.any.isRequired,
  19. };
  20. state = {
  21. currentContentsId: 'recent',
  22. };
  23. componentWillMount() {
  24. this.hackUIController();
  25. this.initBreakpointEvents();
  26. }
  27. /**
  28. * hack and override UIController.storeState
  29. *
  30. * Since UIController is an unstated container, setState() in storeState method should be awaited before writing to cache.
  31. */
  32. hackUIController() {
  33. const { navigationUIController } = this.props;
  34. // see: @atlaskit/navigation-next/dist/esm/ui-controller/UIController.js
  35. const orgStoreState = navigationUIController.storeState;
  36. navigationUIController.storeState = async(state) => {
  37. await navigationUIController.setState(state);
  38. orgStoreState(state);
  39. };
  40. }
  41. initBreakpointEvents() {
  42. const { appContainer, navigationUIController } = this.props;
  43. const mdOrAvobeHandler = (mql) => {
  44. // sm -> md
  45. if (mql.matches) {
  46. appContainer.setState({ isDrawerOpened: false });
  47. navigationUIController.enableResize();
  48. // restore width
  49. if (this.sidebarWidthCached != null) {
  50. navigationUIController.setState({ productNavWidth: this.sidebarWidthCached });
  51. }
  52. }
  53. // md -> sm
  54. else {
  55. // cache width
  56. this.sidebarWidthCached = navigationUIController.state.productNavWidth;
  57. appContainer.setState({ isDrawerOpened: false });
  58. navigationUIController.disableResize();
  59. navigationUIController.expand();
  60. // fix width
  61. navigationUIController.setState({ productNavWidth: sidebarDefaultWidth });
  62. }
  63. };
  64. appContainer.addBreakpointListener('md', mdOrAvobeHandler, true);
  65. }
  66. backdropClickedHandler = () => {
  67. const { appContainer } = this.props;
  68. appContainer.setState({ isDrawerOpened: false });
  69. }
  70. itemSelectedHandler = (contentsId) => {
  71. const { navigationUIController } = this.props;
  72. const { currentContentsId } = this.state;
  73. // already selected
  74. if (currentContentsId === contentsId) {
  75. navigationUIController.toggleCollapse();
  76. }
  77. // switch and expand
  78. else {
  79. this.setState({ currentContentsId: contentsId });
  80. navigationUIController.expand();
  81. }
  82. }
  83. renderGlobalNavigation = () => (
  84. <SidebarNav currentContentsId={this.state.currentContentsId} onItemSelected={this.itemSelectedHandler} />
  85. );
  86. renderSidebarContents = () => {
  87. let contents = <CustomSidebar />;
  88. switch (this.state.currentContentsId) {
  89. case 'recent':
  90. contents = <RecentChanges />;
  91. break;
  92. }
  93. return <div className="grw-sidebar-content-container">{contents}</div>;
  94. }
  95. render() {
  96. const { isDrawerOpened } = this.props.appContainer.state;
  97. return (
  98. <>
  99. <div className={`grw-sidebar ${isDrawerOpened ? 'open' : ''}`}>
  100. <ThemeProvider
  101. theme={theme => ({
  102. ...theme,
  103. context: 'product',
  104. })}
  105. >
  106. <LayoutManager
  107. globalNavigation={this.renderGlobalNavigation}
  108. productNavigation={() => null}
  109. containerNavigation={this.renderSidebarContents}
  110. experimental_hideNavVisuallyOnCollapse
  111. experimental_flyoutOnHover
  112. experimental_alternateFlyoutBehaviour
  113. // experimental_fullWidthFlyout
  114. shouldHideGlobalNavShadow
  115. showContextualNavigation
  116. >
  117. </LayoutManager>
  118. </ThemeProvider>
  119. </div>
  120. { isDrawerOpened && (
  121. <div className="grw-sidebar-backdrop modal-backdrop show" onClick={this.backdropClickedHandler}></div>
  122. ) }
  123. </>
  124. );
  125. }
  126. }
  127. const SidebarWithNavigationUI = withNavigationUIController(Sidebar);
  128. /**
  129. * Wrapper component for using unstated
  130. */
  131. const SidebarWrapper = (props) => {
  132. return createSubscribedElement(SidebarWithNavigationUI, props, [AppContainer]);
  133. };
  134. export default () => (
  135. <NavigationProvider><SidebarWrapper /></NavigationProvider>
  136. );