Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
Size: Mime:
/* eslint-disable react/no-unused-prop-types */

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Wrapper as RAMWrapper, Button as RAMButton, Menu as RAMMenu } from 'react-aria-menubutton';
import classNames from 'classnames';

import Icon from '../../../visuals/Icon';
import HamburgerIcon from '../../../visuals/Icon/svg/ic_menu.svg';

import UserArea from './UserArea';
import Submenu from './Submenu';
import MenuItem from './MenuItem';
import { defaultItems, adminMenuItem } from '../../../user/UserMenu/UserMenu';
import { userPropType, isLoggedIn } from '../../../utils/user';
import { translateMenuItems } from '../../../utils/translate';
import { separatorToken, submenuKey } from '../constants';
import navigateToUrl from '../../../utils/helper';

class HamburgerMenu extends Component {
  constructor(props) {
    super(props);
    this.state = {
      openSubmenus: [],
    };
    this.handleClickLogout = this.handleClickLogout.bind(this);
  }

  /**
   * Handles calling the onClickLogout and passes the default behaviour
   * into the onClickLogout callback as an argument.
   * To override or add behaviour, see onClickLogout prop.
   */
  handleClickLogout() {
    const { onClickLogout } = this.props;
    onClickLogout(() => navigateToUrl('/logout'));
  }

  /**
   * Callback required by the react-aria-menubutton. This is only used for triggering
   * any action props that have been set on menu items.
   *
   * @param {string} selectedValue - The label of the menu item that was invoked.
   */
  handleSelection = finalItems => selectedValue => {
    finalItems.forEach(item => {
      if (item === separatorToken) {
        return;
      }

      const isSubmenu = submenuKey in item;

      let selectedItem;
      if (item.label === selectedValue) {
        selectedItem = item;
      } else if (isSubmenu) {
        selectedItem = item.submenu.find(subItem => subItem.label === selectedValue);
      }

      if (selectedItem && selectedItem.action) {
        selectedItem.action();
      }
    });
  };

  toggleSubmenu = item => {
    const { openSubmenus } = this.state;
    const newOpen = openSubmenus.includes(item.label)
      ? openSubmenus.filter(label => label !== item.label)
      : [...openSubmenus, item.label];
    this.setState({ openSubmenus: newOpen });
  };

  createMenuItems = () => {
    const { user, items: itemsFromProps, intl, isOrgAdmin } = this.props;

    // Add toggleSubmenu action to all submenu heads
    const items = itemsFromProps.map(
      item =>
        submenuKey in item
          ? {
              ...item,
              action: () => {
                this.toggleSubmenu(item);
              },
            }
          : item
    );

    // Add user items if we're logged in
    if (isLoggedIn(user)) {
      const userItems = translateMenuItems(defaultItems, intl);

      const userMenuItems = [
        userItems[0], // Dashboard
        userItems[1], // Account settings
        separatorToken,
        ...items,
        userItems[2],
        separatorToken,
        {
          // Log out
          label: userItems[3].label,
          action: this.handleClickLogout,
        },
      ];

      if (isOrgAdmin) {
        userMenuItems.splice(2, 0, translateMenuItems(adminMenuItem, intl)[0]);
      }

      return userMenuItems;
    }
    return items;
  };

  render() {
    const { className, user, onClickLogin, onClickSignup, intl } = this.props;
    const { openSubmenus } = this.state;

    const finalItems = this.createMenuItems();

    let addSeparatorToNext = false;
    return (
      <RAMWrapper
        tag="nav"
        className={classNames('Menu', className)}
        onSelection={this.handleSelection(finalItems)}
        closeOnSelection={false}
      >
        <RAMButton className={classNames('Button', 'Button--linkDark', 'Button--compact')}>
          <Icon icon={HamburgerIcon} />
        </RAMButton>

        <RAMMenu className="HamburgerMenu-menu" tag="ul">
          <UserArea user={user} onClickLogin={onClickLogin} onClickSignup={onClickSignup} intl={intl} />
          {finalItems.length > 0 &&
            finalItems.map(item => {
              if (item === separatorToken) {
                addSeparatorToNext = true;
                return null;
              }

              if (submenuKey in item) {
                const open = openSubmenus.includes(item.label);
                return (
                  <Submenu
                    head={item}
                    items={item.submenu}
                    open={open}
                    toggleSubmenu={this.toggleSubmenu}
                    key={item.label}
                  />
                );
              }

              const separatorClass = addSeparatorToNext ? 'u-separatorBefore' : '';
              addSeparatorToNext = false;
              return <MenuItem item={item} key={item.label} className={separatorClass} />;
            })}
        </RAMMenu>
      </RAMWrapper>
    );
  }
}

HamburgerMenu.propTypes = {
  className: PropTypes.string,

  /** Defines the user and the shape of the data. */
  user: userPropType,

  /** Defines the menu items within the Hamburger Menu */
  items: PropTypes.array,

  /**
   * Callback executed when the user clicks on the "Login" button.
   * It can be overridden to add custom functionality such as tracking.
   * You can execute the default button behaviour within onClickLogin by running
   * the function provided as first parameter. If your custom code is asynchronous,
   * run the default behaviour as callback of your asynchronous action. This guarantees
   * that your custom action completes before navigating to another page.
   * @param {Function} defaultBehaviour - Function to execute the default click behaviour
   */
  onClickLogin: PropTypes.func,

  /**
   * Callback executed when the user clicks on the "Logout" button.
   * It can be overridden to add custom functionality such as tracking.
   * You can execute the default button behaviour within onClickLogout by running
   * the function provided as first parameter. If your custom code is asynchronous,
   * run the default behaviour as callback of your asynchronous action. This guarantees
   * that your custom action completes before navigating to another page.
   * @param {Function} defaultBehaviour - Function to execute the default click behaviour
   */
  onClickLogout: PropTypes.func,

  /**
   * Callback executed when the user clicks on the "Signup" button.
   * It can be overridden to add custom functionality such as tracking.
   * You can execute the default button behaviour within onClickSignup by running
   * the function provided as first parameter. If your custom code is asynchronous,
   * run the default behaviour as callback of your asynchronous action. This guarantees
   * that your custom action completes before navigating to another page.
   * @param {Function} defaultBehaviour - Function to execute the default click behaviour
   */
  onClickSignup: PropTypes.func,

  /** An object used for internationalization. */
  intl: PropTypes.object,

  /** Is the logged in user an admin of an organisation
   */
  isOrgAdmin: PropTypes.bool,
};

HamburgerMenu.defaultProps = {
  className: '',
  user: null,
  items: [],
  onClickLogin: defaultBehaviour => defaultBehaviour(),
  onClickLogout: defaultBehaviour => defaultBehaviour(),
  onClickSignup: defaultBehaviour => defaultBehaviour(),
  intl: null,
  isOrgAdmin: false,
};

export default HamburgerMenu;