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.

Toolbar.web.js 5.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. // @flow
  2. import PropTypes from 'prop-types';
  3. import React, { Component } from 'react';
  4. import { connect } from 'react-redux';
  5. import { setToolbarHovered } from '../actions';
  6. import StatelessToolbar from './StatelessToolbar';
  7. import ToolbarButton from './ToolbarButton';
  8. /**
  9. * Implements a toolbar in React/Web. It is a strip that contains a set of
  10. * toolbar items such as buttons. Toolbar is commonly placed inside of a
  11. * Toolbox.
  12. *
  13. * @class Toolbar
  14. * @extends Component
  15. */
  16. class Toolbar extends Component<*> {
  17. /**
  18. * Base toolbar component's property types.
  19. *
  20. * @static
  21. */
  22. static propTypes = {
  23. /**
  24. * Children of current React component.
  25. */
  26. children: PropTypes.element,
  27. /**
  28. * Toolbar's class name.
  29. */
  30. className: PropTypes.string,
  31. /**
  32. * Used to dispatch an action when a button is clicked or on mouse
  33. * out/in event.
  34. */
  35. dispatch: PropTypes.func,
  36. /**
  37. * Map with toolbar buttons.
  38. */
  39. toolbarButtons: PropTypes.instanceOf(Map),
  40. /**
  41. * Indicates the position of the tooltip.
  42. */
  43. tooltipPosition: PropTypes.oneOf([ 'bottom', 'left', 'right', 'top' ])
  44. };
  45. /**
  46. * Constructor of Primary toolbar class.
  47. *
  48. * @param {Object} props - Object containing React component properties.
  49. */
  50. constructor(props: Object) {
  51. super(props);
  52. // Bind callbacks to preverse this.
  53. this._onMouseOut = this._onMouseOut.bind(this);
  54. this._onMouseOver = this._onMouseOver.bind(this);
  55. this._renderToolbarButton = this._renderToolbarButton.bind(this);
  56. }
  57. /**
  58. * Implements React's {@link Component#render()}.
  59. *
  60. * @inheritdoc
  61. * @returns {ReactElement}
  62. */
  63. render(): React$Element<*> {
  64. const props = {
  65. className: this.props.className,
  66. onMouseOut: this._onMouseOut,
  67. onMouseOver: this._onMouseOver
  68. };
  69. return (
  70. <StatelessToolbar { ...props }>
  71. {
  72. [ ...this.props.toolbarButtons.entries() ]
  73. .map(this._renderToolbarButton)
  74. }
  75. {
  76. this.props.children
  77. }
  78. </StatelessToolbar>
  79. );
  80. }
  81. _onMouseOut: () => void;
  82. /**
  83. * Dispatches an action signalling that toolbar is no being hovered.
  84. *
  85. * @protected
  86. * @returns {void}
  87. */
  88. _onMouseOut() {
  89. this.props.dispatch(setToolbarHovered(false));
  90. }
  91. _onMouseOver: () => void;
  92. /**
  93. * Dispatches an action signalling that toolbar is now being hovered.
  94. *
  95. * @protected
  96. * @returns {void}
  97. */
  98. _onMouseOver() {
  99. this.props.dispatch(setToolbarHovered(true));
  100. }
  101. _renderToolbarButton: (Array<*>) => React$Element<*>;
  102. /**
  103. * Renders toolbar button. Method is passed to map function.
  104. *
  105. * @param {Array} keyValuePair - Key value pair containing button and its
  106. * key.
  107. * @private
  108. * @returns {ReactElement} A toolbar button.
  109. */
  110. _renderToolbarButton([ key, button ]): React$Element<*> {
  111. const { tooltipPosition } = this.props;
  112. if (button.component) {
  113. return (
  114. <button.component
  115. key = { key }
  116. toggled = { button.toggled }
  117. tooltipPosition = { tooltipPosition } />
  118. );
  119. }
  120. const {
  121. childComponent: ChildComponent,
  122. onClick,
  123. onMount,
  124. onUnmount
  125. } = button;
  126. const onClickWithDispatch = (...args) =>
  127. onClick && onClick(this.props.dispatch, ...args);
  128. return (
  129. <ToolbarButton
  130. button = { button }
  131. key = { key }
  132. // TODO The following disables an eslint error alerting about a
  133. // known potential/theoretical performance pernalty:
  134. //
  135. // A bind call or arrow function in a JSX prop will create a
  136. // brand new function on every single render. This is bad for
  137. // performance, as it will result in the garbage collector being
  138. // invoked way more than is necessary. It may also cause
  139. // unnecessary re-renders if a brand new function is passed as a
  140. // prop to a component that uses reference equality check on the
  141. // prop to determine if it should update.
  142. //
  143. // I'm not addressing the potential/theoretical performance
  144. // penalty at the time of this writing because I don't know for
  145. // a fact that it's a practical performance penalty in the case.
  146. //
  147. // eslint-disable-next-line react/jsx-no-bind
  148. onClick = { onClickWithDispatch }
  149. onMount = { onMount }
  150. onUnmount = { onUnmount }
  151. tooltipPosition = { tooltipPosition }>
  152. { button.html || null }
  153. { ChildComponent ? <ChildComponent /> : null }
  154. </ToolbarButton>
  155. );
  156. }
  157. }
  158. export default connect()(Toolbar);