You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

185 lines
5.8 KiB

  1. import React from 'react';
  2. import PropTypes from 'prop-types';
  3. import { createPortal } from 'react-dom';
  4. import classNames from 'classnames';
  5. import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
  6. import Icon from 'mastodon/components/icon';
  7. const messages = defineMessages({
  8. show: { id: 'column_header.show_settings', defaultMessage: 'Show settings' },
  9. hide: { id: 'column_header.hide_settings', defaultMessage: 'Hide settings' },
  10. moveLeft: { id: 'column_header.moveLeft_settings', defaultMessage: 'Move column to the left' },
  11. moveRight: { id: 'column_header.moveRight_settings', defaultMessage: 'Move column to the right' },
  12. });
  13. export default @injectIntl
  14. class ColumnHeader extends React.PureComponent {
  15. static contextTypes = {
  16. router: PropTypes.object,
  17. };
  18. static propTypes = {
  19. intl: PropTypes.object.isRequired,
  20. title: PropTypes.node,
  21. icon: PropTypes.string,
  22. active: PropTypes.bool,
  23. multiColumn: PropTypes.bool,
  24. extraButton: PropTypes.node,
  25. showBackButton: PropTypes.bool,
  26. children: PropTypes.node,
  27. pinned: PropTypes.bool,
  28. placeholder: PropTypes.bool,
  29. onPin: PropTypes.func,
  30. onMove: PropTypes.func,
  31. onClick: PropTypes.func,
  32. };
  33. state = {
  34. collapsed: true,
  35. animating: false,
  36. };
  37. historyBack = () => {
  38. if (window.history && window.history.length === 1) {
  39. this.context.router.history.push('/');
  40. } else {
  41. this.context.router.history.goBack();
  42. }
  43. }
  44. handleToggleClick = (e) => {
  45. e.stopPropagation();
  46. this.setState({ collapsed: !this.state.collapsed, animating: true });
  47. }
  48. handleTitleClick = () => {
  49. this.props.onClick();
  50. }
  51. handleMoveLeft = () => {
  52. this.props.onMove(-1);
  53. }
  54. handleMoveRight = () => {
  55. this.props.onMove(1);
  56. }
  57. handleBackClick = () => {
  58. this.historyBack();
  59. }
  60. handleTransitionEnd = () => {
  61. this.setState({ animating: false });
  62. }
  63. handlePin = () => {
  64. if (!this.props.pinned) {
  65. this.historyBack();
  66. }
  67. this.props.onPin();
  68. }
  69. render () {
  70. const { title, icon, active, children, pinned, multiColumn, extraButton, showBackButton, intl: { formatMessage }, placeholder } = this.props;
  71. const { collapsed, animating } = this.state;
  72. const wrapperClassName = classNames('column-header__wrapper', {
  73. 'active': active,
  74. });
  75. const buttonClassName = classNames('column-header', {
  76. 'active': active,
  77. });
  78. const collapsibleClassName = classNames('column-header__collapsible', {
  79. 'collapsed': collapsed,
  80. 'animating': animating,
  81. });
  82. const collapsibleButtonClassName = classNames('column-header__button', {
  83. 'active': !collapsed,
  84. });
  85. let extraContent, pinButton, moveButtons, backButton, collapseButton;
  86. if (children) {
  87. extraContent = (
  88. <div key='extra-content' className='column-header__collapsible__extra'>
  89. {children}
  90. </div>
  91. );
  92. }
  93. if (multiColumn && pinned) {
  94. pinButton = <button key='pin-button' className='text-btn column-header__setting-btn' onClick={this.handlePin}><Icon id='times' /> <FormattedMessage id='column_header.unpin' defaultMessage='Unpin' /></button>;
  95. moveButtons = (
  96. <div key='move-buttons' className='column-header__setting-arrows'>
  97. <button title={formatMessage(messages.moveLeft)} aria-label={formatMessage(messages.moveLeft)} className='text-btn column-header__setting-btn' onClick={this.handleMoveLeft}><Icon id='chevron-left' /></button>
  98. <button title={formatMessage(messages.moveRight)} aria-label={formatMessage(messages.moveRight)} className='text-btn column-header__setting-btn' onClick={this.handleMoveRight}><Icon id='chevron-right' /></button>
  99. </div>
  100. );
  101. } else if (multiColumn) {
  102. pinButton = <button key='pin-button' className='text-btn column-header__setting-btn' onClick={this.handlePin}><Icon id='plus' /> <FormattedMessage id='column_header.pin' defaultMessage='Pin' /></button>;
  103. }
  104. if (!pinned && (multiColumn || showBackButton)) {
  105. backButton = (
  106. <button onClick={this.handleBackClick} className='column-header__back-button'>
  107. <Icon id='chevron-left' className='column-back-button__icon' fixedWidth />
  108. <FormattedMessage id='column_back_button.label' defaultMessage='Back' />
  109. </button>
  110. );
  111. }
  112. const collapsedContent = [
  113. extraContent,
  114. ];
  115. if (multiColumn) {
  116. collapsedContent.push(moveButtons);
  117. collapsedContent.push(pinButton);
  118. }
  119. if (children || multiColumn) {
  120. collapseButton = <button className={collapsibleButtonClassName} title={formatMessage(collapsed ? messages.show : messages.hide)} aria-label={formatMessage(collapsed ? messages.show : messages.hide)} aria-pressed={collapsed ? 'false' : 'true'} onClick={this.handleToggleClick}><Icon id='sliders' /></button>;
  121. }
  122. const hasTitle = icon && title;
  123. const component = (
  124. <div className={wrapperClassName}>
  125. <h1 className={buttonClassName}>
  126. {hasTitle && (
  127. <button onClick={this.handleTitleClick}>
  128. <Icon id={icon} fixedWidth className='column-header__icon' />
  129. {title}
  130. </button>
  131. )}
  132. {!hasTitle && backButton}
  133. <div className='column-header__buttons'>
  134. {hasTitle && backButton}
  135. {extraButton}
  136. {collapseButton}
  137. </div>
  138. </h1>
  139. <div className={collapsibleClassName} tabIndex={collapsed ? -1 : null} onTransitionEnd={this.handleTransitionEnd}>
  140. <div className='column-header__collapsible-inner'>
  141. {(!collapsed || animating) && collapsedContent}
  142. </div>
  143. </div>
  144. </div>
  145. );
  146. if (multiColumn || placeholder) {
  147. return component;
  148. } else {
  149. return createPortal(component, document.getElementById('tabs-bar__portal'));
  150. }
  151. }
  152. }