Sidebar.jsx 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  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 { withUnstatedContainers } from './UnstatedUtils';
  10. import AppContainer from '../services/AppContainer';
  11. import NavigationContainer from '../services/NavigationContainer';
  12. import SidebarNav from './Sidebar/SidebarNav';
  13. import RecentChanges from './Sidebar/RecentChanges';
  14. import CustomSidebar from './Sidebar/CustomSidebar';
  15. const sidebarDefaultWidth = 240;
  16. class Sidebar extends React.Component {
  17. static propTypes = {
  18. appContainer: PropTypes.instanceOf(AppContainer).isRequired,
  19. navigationContainer: PropTypes.instanceOf(NavigationContainer).isRequired,
  20. navigationUIController: PropTypes.any.isRequired,
  21. isDrawerModeOnInit: PropTypes.bool,
  22. };
  23. state = {
  24. currentContentsId: 'recent',
  25. };
  26. componentWillMount() {
  27. this.hackUIController();
  28. }
  29. componentDidUpdate(prevProps, prevState) {
  30. this.toggleDrawerMode(this.isDrawerMode);
  31. }
  32. /**
  33. * hack and override UIController.storeState
  34. *
  35. * Since UIController is an unstated container, setState() in storeState method should be awaited before writing to cache.
  36. */
  37. hackUIController() {
  38. const { navigationUIController } = this.props;
  39. // see: @atlaskit/navigation-next/dist/esm/ui-controller/UIController.js
  40. const orgStoreState = navigationUIController.storeState;
  41. navigationUIController.storeState = async(state) => {
  42. await navigationUIController.setState(state);
  43. orgStoreState(state);
  44. };
  45. }
  46. /**
  47. * return whether drawer mode or not
  48. */
  49. get isDrawerMode() {
  50. let isDrawerMode = this.props.navigationContainer.state.isDrawerMode;
  51. if (isDrawerMode == null) {
  52. isDrawerMode = this.props.isDrawerModeOnInit;
  53. }
  54. return isDrawerMode;
  55. }
  56. toggleDrawerMode(bool) {
  57. const { navigationUIController } = this.props;
  58. const isStateModified = navigationUIController.state.isResizeDisabled !== bool;
  59. if (!isStateModified) {
  60. return;
  61. }
  62. // Drawer <-- Dock
  63. if (bool) {
  64. // cache state
  65. this.sidebarCollapsedCached = navigationUIController.state.isCollapsed;
  66. this.sidebarWidthCached = navigationUIController.state.productNavWidth;
  67. // clear transition temporary
  68. if (this.sidebarCollapsedCached) {
  69. this.addCssClassTemporary('grw-sidebar-supress-transitions-to-drawer');
  70. }
  71. navigationUIController.disableResize();
  72. // fix width
  73. navigationUIController.setState({ productNavWidth: sidebarDefaultWidth });
  74. }
  75. // Drawer --> Dock
  76. else {
  77. // clear transition temporary
  78. if (this.sidebarCollapsedCached) {
  79. this.addCssClassTemporary('grw-sidebar-supress-transitions-to-dock');
  80. }
  81. navigationUIController.enableResize();
  82. // restore width
  83. if (this.sidebarWidthCached != null) {
  84. navigationUIController.setState({ productNavWidth: this.sidebarWidthCached });
  85. }
  86. }
  87. }
  88. get sidebarElem() {
  89. return document.querySelector('.grw-sidebar');
  90. }
  91. addCssClassTemporary(className) {
  92. // clear
  93. this.sidebarElem.classList.add(className);
  94. // restore after 300ms
  95. setTimeout(() => {
  96. this.sidebarElem.classList.remove(className);
  97. }, 300);
  98. }
  99. backdropClickedHandler = () => {
  100. const { navigationContainer } = this.props;
  101. navigationContainer.setState({ isDrawerOpened: false });
  102. }
  103. itemSelectedHandler = (contentsId) => {
  104. const { navigationUIController } = this.props;
  105. const { currentContentsId } = this.state;
  106. // already selected
  107. if (currentContentsId === contentsId) {
  108. navigationUIController.toggleCollapse();
  109. }
  110. // switch and expand
  111. else {
  112. this.setState({ currentContentsId: contentsId });
  113. navigationUIController.expand();
  114. }
  115. }
  116. renderGlobalNavigation = () => (
  117. <SidebarNav currentContentsId={this.state.currentContentsId} onItemSelected={this.itemSelectedHandler} />
  118. );
  119. renderSidebarContents = () => {
  120. let contents = <CustomSidebar />;
  121. switch (this.state.currentContentsId) {
  122. case 'recent':
  123. contents = <RecentChanges />;
  124. break;
  125. }
  126. return <div className="grw-sidebar-content-container">{contents}</div>;
  127. }
  128. render() {
  129. const { isDrawerOpened } = this.props.navigationContainer.state;
  130. return (
  131. <>
  132. <div className={`grw-sidebar ${this.isDrawerMode ? 'grw-sidebar-drawer' : ''} ${isDrawerOpened ? 'open' : ''}`}>
  133. <ThemeProvider
  134. theme={theme => ({
  135. ...theme,
  136. context: 'product',
  137. })}
  138. >
  139. <LayoutManager
  140. globalNavigation={this.renderGlobalNavigation}
  141. productNavigation={() => null}
  142. containerNavigation={this.renderSidebarContents}
  143. experimental_hideNavVisuallyOnCollapse
  144. experimental_flyoutOnHover
  145. experimental_alternateFlyoutBehaviour
  146. // experimental_fullWidthFlyout
  147. shouldHideGlobalNavShadow
  148. showContextualNavigation
  149. >
  150. </LayoutManager>
  151. </ThemeProvider>
  152. </div>
  153. { isDrawerOpened && (
  154. <div className="grw-sidebar-backdrop modal-backdrop show" onClick={this.backdropClickedHandler}></div>
  155. ) }
  156. </>
  157. );
  158. }
  159. }
  160. const SidebarWithNavigationUIController = withNavigationUIController(Sidebar);
  161. /**
  162. * Wrapper component for using unstated
  163. */
  164. const SidebarWithNavigation = (props) => {
  165. const { preferDrawerModeByUser: isDrawerModeOnInit } = props.navigationContainer.state;
  166. const initUICForDrawerMode = isDrawerModeOnInit
  167. // generate initialUIController for Drawer mode
  168. ? {
  169. isCollapsed: false,
  170. isResizeDisabled: true,
  171. productNavWidth: sidebarDefaultWidth,
  172. }
  173. // set undefined (should be initialized by cache)
  174. : undefined;
  175. return (
  176. <NavigationProvider initialUIController={initUICForDrawerMode}>
  177. <SidebarWithNavigationUIController {...props} isDrawerModeOnInit={isDrawerModeOnInit} />
  178. </NavigationProvider>
  179. );
  180. };
  181. SidebarWithNavigation.propTypes = {
  182. appContainer: PropTypes.instanceOf(AppContainer).isRequired,
  183. navigationContainer: PropTypes.instanceOf(NavigationContainer).isRequired,
  184. };
  185. export default withUnstatedContainers(SidebarWithNavigation, [AppContainer, NavigationContainer]);