| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181 | // @flow
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { setToolbarHovered } from '../actions';
import StatelessToolbar from './StatelessToolbar';
import ToolbarButton from './ToolbarButton';
/**
 * Implements a toolbar in React/Web. It is a strip that contains a set of
 * toolbar items such as buttons. Toolbar is commonly placed inside of a
 * Toolbox.
 *
 * @class Toolbar
 * @extends Component
 */
class Toolbar extends Component<*> {
    /**
     * Base toolbar component's property types.
     *
     * @static
     */
    static propTypes = {
        /**
         * Children of current React component.
         */
        children: PropTypes.element,
        /**
         * Toolbar's class name.
         */
        className: PropTypes.string,
        /**
         * Used to dispatch an action when a button is clicked or on mouse
         * out/in event.
         */
        dispatch: PropTypes.func,
        /**
         * Map with toolbar buttons.
         */
        toolbarButtons: PropTypes.instanceOf(Map),
        /**
         * Indicates the position of the tooltip.
         */
        tooltipPosition: PropTypes.oneOf([ 'bottom', 'left', 'right', 'top' ])
    };
    /**
     * Constructor of Primary toolbar class.
     *
     * @param {Object} props - Object containing React component properties.
     */
    constructor(props: Object) {
        super(props);
        // Bind callbacks to preverse this.
        this._onMouseOut = this._onMouseOut.bind(this);
        this._onMouseOver = this._onMouseOver.bind(this);
        this._renderToolbarButton = this._renderToolbarButton.bind(this);
    }
    /**
     * Implements React's {@link Component#render()}.
     *
     * @inheritdoc
     * @returns {ReactElement}
     */
    render(): React$Element<*> {
        const props = {
            className: this.props.className,
            onMouseOut: this._onMouseOut,
            onMouseOver: this._onMouseOver
        };
        return (
            <StatelessToolbar { ...props }>
                {
                    [ ...this.props.toolbarButtons.entries() ]
                        .map(this._renderToolbarButton)
                }
                {
                    this.props.children
                }
            </StatelessToolbar>
        );
    }
    _onMouseOut: () => void;
    /**
     * Dispatches an action signalling that toolbar is no being hovered.
     *
     * @protected
     * @returns {void}
     */
    _onMouseOut() {
        this.props.dispatch(setToolbarHovered(false));
    }
    _onMouseOver: () => void;
    /**
     * Dispatches an action signalling that toolbar is now being hovered.
     *
     * @protected
     * @returns {void}
     */
    _onMouseOver() {
        this.props.dispatch(setToolbarHovered(true));
    }
    _renderToolbarButton: (Array<*>) => React$Element<*>;
    /**
     * Renders toolbar button. Method is passed to map function.
     *
     * @param {Array} keyValuePair - Key value pair containing button and its
     * key.
     * @private
     * @returns {ReactElement} A toolbar button.
     */
    _renderToolbarButton([ key, button ]): React$Element<*> {
        const { tooltipPosition } = this.props;
        if (button.component) {
            return (
                <button.component
                    key = { key }
                    toggled = { button.toggled }
                    tooltipPosition = { tooltipPosition } />
            );
        }
        const {
            childComponent: ChildComponent,
            onClick,
            onMount,
            onUnmount
        } = button;
        const onClickWithDispatch = (...args) =>
            onClick && onClick(this.props.dispatch, ...args);
        return (
            <ToolbarButton
                button = { button }
                key = { key }
                // TODO The following disables an eslint error alerting about a
                // known potential/theoretical performance pernalty:
                //
                // A bind call or arrow function in a JSX prop will create a
                // brand new function on every single render. This is bad for
                // performance, as it will result in the garbage collector being
                // invoked way more than is necessary. It may also cause
                // unnecessary re-renders if a brand new function is passed as a
                // prop to a component that uses reference equality check on the
                // prop to determine if it should update.
                //
                // I'm not addressing the potential/theoretical performance
                // penalty at the time of this writing because I don't know for
                // a fact that it's a practical performance penalty in the case.
                //
                // eslint-disable-next-line react/jsx-no-bind
                onClick = { onClickWithDispatch }
                onMount = { onMount }
                onUnmount = { onUnmount }
                tooltipPosition = { tooltipPosition }>
                { button.html || null }
                { ChildComponent ? <ChildComponent /> : null }
            </ToolbarButton>
        );
    }
}
export default connect()(Toolbar);
 |