Sidebar.jsx 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. import {Component} from 'react';
  2. import cls from 'classnames';
  3. import Button from './Button';
  4. import Icon from './Icon';
  5. import s from './Sidebar.css';
  6. const toggleTime = 200;
  7. export default class Sidebar extends Component {
  8. static defaultProps = {
  9. pinned: false,
  10. position: 'left',
  11. };
  12. allowHide = true;
  13. toggling = false;
  14. hideContentTimeout = null;
  15. width = null;
  16. state = {
  17. visible: true,
  18. renderContent: true,
  19. };
  20. componentDidMount() {
  21. this.hideTimeoutId = setTimeout(() => this.toggleVisibility(false), 3000);
  22. }
  23. componentWillUnmount() {
  24. clearTimeout(this.hideTimeoutId);
  25. clearTimeout(this.hideContentTimeout);
  26. }
  27. render() {
  28. const {position, pinned, children} = this.props;
  29. const {visible, renderContent} = this.state;
  30. const className = cls({
  31. [s.container]: true,
  32. [s.pinned]: pinned,
  33. [s.left]: position === 'left',
  34. [s.hidden]: !visible,
  35. [s.empty]: !renderContent,
  36. });
  37. return (
  38. <div
  39. ref={this.saveNode}
  40. className={className}
  41. onClick={this.handleClick}
  42. onMouseLeave={this.handleMouseLeave}
  43. >
  44. {visible && (
  45. <Button
  46. type="button"
  47. title="Pin"
  48. className={s.pinButton}
  49. active={pinned}
  50. toggle
  51. onClick={this.handlePinButtonClick}
  52. >
  53. <Icon name="pin" size={13} />
  54. </Button>
  55. )}
  56. <Button
  57. type="button"
  58. title={visible ? 'Hide' : 'Show sidebar'}
  59. className={s.toggleButton}
  60. onClick={this.handleToggleButtonClick}
  61. >
  62. <Icon name="arrow-right" size={10} rotate={visible ? 180 : 0} />
  63. </Button>
  64. {pinned && visible && (
  65. <div className={s.resizer} onMouseDown={this.handleResizeStart} />
  66. )}
  67. <div
  68. className={s.content}
  69. onMouseEnter={this.handleMouseEnter}
  70. onMouseMove={this.handleMouseMove}
  71. >
  72. {renderContent ? children : null}
  73. </div>
  74. </div>
  75. );
  76. }
  77. handleClick = () => {
  78. this.allowHide = false;
  79. };
  80. handleMouseEnter = () => {
  81. if (!this.toggling && !this.props.pinned) {
  82. clearTimeout(this.hideTimeoutId);
  83. this.toggleVisibility(true);
  84. }
  85. };
  86. handleMouseMove = () => {
  87. this.allowHide = true;
  88. };
  89. handleMouseLeave = () => {
  90. if (this.allowHide && !this.toggling && !this.props.pinned) {
  91. this.toggleVisibility(false);
  92. }
  93. };
  94. handleToggleButtonClick = () => {
  95. this.toggleVisibility();
  96. };
  97. handlePinButtonClick = () => {
  98. const pinned = !this.props.pinned;
  99. this.width = pinned ? this.node.getBoundingClientRect().width : null;
  100. this.updateNodeWidth();
  101. this.props.onPinStateChange(pinned);
  102. };
  103. handleResizeStart = event => {
  104. this.resizeInfo = {
  105. startPageX: event.pageX,
  106. initialWidth: this.width,
  107. };
  108. document.body.classList.add('resizing', 'col');
  109. document.addEventListener('mousemove', this.handleResize, true);
  110. document.addEventListener('mouseup', this.handleResizeEnd, true);
  111. };
  112. handleResize = event => {
  113. this.width =
  114. this.resizeInfo.initialWidth + (event.pageX - this.resizeInfo.startPageX);
  115. this.updateNodeWidth();
  116. };
  117. handleResizeEnd = () => {
  118. document.body.classList.remove('resizing', 'col');
  119. document.removeEventListener('mousemove', this.handleResize, true);
  120. document.removeEventListener('mouseup', this.handleResizeEnd, true);
  121. this.props.onResize();
  122. };
  123. toggleVisibility(flag) {
  124. clearTimeout(this.hideContentTimeout);
  125. const {visible} = this.state;
  126. const {onToggle, pinned} = this.props;
  127. if (flag === undefined) {
  128. flag = !visible;
  129. } else if (flag === visible) {
  130. return;
  131. }
  132. this.setState({visible: flag});
  133. this.toggling = true;
  134. setTimeout(() => {
  135. this.toggling = false;
  136. }, toggleTime);
  137. if (pinned) {
  138. this.updateNodeWidth(flag ? this.width : null);
  139. }
  140. if (flag || pinned) {
  141. this.setState({renderContent: flag});
  142. onToggle(flag);
  143. } else if (!flag) {
  144. // Waiting for the CSS animation to finish and hiding content
  145. this.hideContentTimeout = setTimeout(() => {
  146. this.hideContentTimeout = null;
  147. this.setState({renderContent: false});
  148. onToggle(false);
  149. }, toggleTime);
  150. }
  151. }
  152. saveNode = node => (this.node = node);
  153. updateNodeWidth(width = this.width) {
  154. this.node.style.width = width ? `${width}px` : '';
  155. }
  156. }