<commit_before>import * as React from 'react';
import { props, t, ReactChildren } from '../utils';
import { ActionsMenu, optionType } from './ActionsMenu';
import FlexView from 'react-flexview';
import { Icon } from '../Icon/Icon';
import { Popover } from '../Popover/Popover';
import * as cx from 'classnames';

export type MenuRequiredProps = {
  /** custom content for the menu trigger */
  children?: JSX.Element | string,
  /** custom renderer for menu items. if none is passed, it renders ActionsMenu */
  menuRenderer?: (options: ActionsMenu.Option[]) => JSX.Element,
  /** menu options */
  options: ActionsMenu.Option[],
  /** className for the buil-in icon rendered in the menu trigger (if children is passed, this is ignored) */
  iconClassName?: string,
  /** called when menu is opened */
  onOpen: () => void,
  /** called when menu is closed */
  onClose: () => void,
  /** the a set of built-in max heights for the menu (if maxHeight is passed, this is ignored) */
  size?: Menu.Size,
  /** custom max height for the menu */
  maxHeight?: number,
  className?: string
}

export type MenuDefaultProps = {
  /** whether the menu is open or not */
  isOpen: boolean,
  /** whether the menu should be closed when clicking outside */
  dismissOnClickOutside: boolean
}

export namespace Menu {
  export type Size =  'small' | 'medium' | 'large';

  export type Props = MenuRequiredProps & Partial<MenuDefaultProps>;
}
type MenuDefaultedProps = MenuRequiredProps & MenuDefaultProps;

export const Props = {
  children: t.maybe(ReactChildren),
  menuRenderer: t.maybe(t.Function),
  options: t.list(optionType),
  iconClassName: t.maybe(t.String), // if children is passed, this is ignored
  isOpen: t.maybe(t.Boolean),
  onOpen: t.Function,
  onClose: t.Function,
  dismissOnClickOutside: t.maybe(t.Boolean),
  size: t.maybe(t.enums.of(['small', 'medium', 'large']), ''),
  maxHeight: t.maybe(t.Number),
  className: t.maybe(t.String)
};

/**
 *  A menu with actions
 */
@props(Props)
export class Menu extends React.PureComponent<Menu.Props> {

  static defaultProps: MenuDefaultProps = {
    isOpen: false,
    dismissOnClickOutside: true
  };

  toggleMenu = () => {
    const { isOpen, onOpen, onClose } = this.props as MenuDefaultedProps;
    if (isOpen) {
      onClose();
    } else {
      onOpen();
    }
  };

  getActionsMenuMaxHeight = (): number | undefined => {
    if (this.props.maxHeight) {
      return this.props.maxHeight;
    }

    if (this.props.size) {
      switch (this.props.size) {
        case 'small': return 250;
        case 'medium': return 400;
        case 'large': return 600;
      }
    }
  };

  render() {
    const {
      iconClassName,
      children,
      dismissOnClickOutside,
      className,
      isOpen,
      onClose,
      options,
      menuRenderer
    } = this.props as MenuDefaultedProps;
    const { toggleMenu } = this;

    const maxHeight = this.getActionsMenuMaxHeight();

    return (
      <Popover
        popover={{
          isOpen,
          dismissOnClickOutside,
          event: 'click',
          anchor: 'end',
          position: 'bottom',
          className: 'actions-menu-popover',
          content:  menuRenderer ?
            menuRenderer(options) :
            <ActionsMenu onClick={onClose} options={options} maxHeight={maxHeight} />
        }}
      >
        <FlexView vAlignContent='center' className={cx('menu', className)} onClick={toggleMenu}>
          {dismissOnClickOutside && isOpen && (
            <div className='menu-overlay' onClick={onClose} />
          )}
          {children || (
            <FlexView vAlignContent='center' className={cx('menu-icon-container', { isOpen })}>
              <Icon icon={iconClassName} className='menu-icon' />
            </FlexView>
          )}
        </FlexView>
      </Popover><commit_msg>add position prop to enable user to render menu above the trigger<commit_after>import * as React from 'react';
import { props, t, ReactChildren } from '../utils';
import { ActionsMenu, optionType } from './ActionsMenu';
import FlexView from 'react-flexview';
import { Icon } from '../Icon/Icon';
import { Popover } from '../Popover/Popover';
import * as cx from 'classnames';

export type MenuRequiredProps = {
  /** custom content for the menu trigger */
  children?: JSX.Element | string,
  /** custom renderer for menu items. if none is passed, it renders ActionsMenu */
  menuRenderer?: (options: ActionsMenu.Option[]) => JSX.Element,
  /** menu options */
  options: ActionsMenu.Option[],
  /** className for the buil-in icon rendered in the menu trigger (if children is passed, this is ignored) */
  iconClassName?: string,
  /** called when menu is opened */
  onOpen: () => void,
  /** called when menu is closed */
  onClose: () => void,
  /** the a set of built-in max heights for the menu (if maxHeight is passed, this is ignored) */
  size?: Menu.Size,
  /** custom max height for the menu */
  maxHeight?: number,
  className?: string
}

export type MenuDefaultProps = {
  /** whether the menu is open or not */
  isOpen: boolean,
  /** whether the menu should be closed when clicking outside */
  dismissOnClickOutside: boolean,
  /** wheter the menu should be rendered on top or at the bottom of the trigger */
  position: 'top' | 'bottom',
}

export namespace Menu {
  export type Size =  'small' | 'medium' | 'large';

  export type Props = MenuRequiredProps & Partial<MenuDefaultProps>;
}
type MenuDefaultedProps = MenuRequiredProps & MenuDefaultProps;

export const Props = {
  children: t.maybe(ReactChildren),
  menuRenderer: t.maybe(t.Function),
  options: t.list(optionType),
  iconClassName: t.maybe(t.String), // if children is passed, this is ignored
  isOpen: t.maybe(t.Boolean),
  onOpen: t.Function,
  onClose: t.Function,
  dismissOnClickOutside: t.maybe(t.Boolean),
  size: t.maybe(t.enums.of(['small', 'medium', 'large'])),
  position: t.maybe(t.enums.of(['top', 'bottom'])),
  maxHeight: t.maybe(t.Number),
  className: t.maybe(t.String)
};

/**
 *  A menu with actions
 */
@props(Props)
export class Menu extends React.PureComponent<Menu.Props> {

  static defaultProps: MenuDefaultProps = {
    isOpen: false,
    dismissOnClickOutside: true,
    position: 'bottom'
  };

  toggleMenu = () => {
    const { isOpen, onOpen, onClose } = this.props as MenuDefaultedProps;
    if (isOpen) {
      onClose();
    } else {
      onOpen();
    }
  };

  getActionsMenuMaxHeight = (): number | undefined => {
    if (this.props.maxHeight) {
      return this.props.maxHeight;
    }

    if (this.props.size) {
      switch (this.props.size) {
        case 'small': return 250;
        case 'medium': return 400;
        case 'large': return 600;
      }
    }
  };

  render() {
    const {
      iconClassName,
      children,
      dismissOnClickOutside,
      className,
      isOpen,
      onClose,
      options,
      menuRenderer,
      position
    } = this.props as MenuDefaultedProps;
    const { toggleMenu } = this;

    const maxHeight = this.getActionsMenuMaxHeight();

    return (
      <Popover
        popover={{
          isOpen,
          dismissOnClickOutside,
          position,
          anchor: 'end',
          className: 'actions-menu-popover',
          content:  menuRenderer ?
            menuRenderer(options) :
            <ActionsMenu onClick={onClose} options={options} maxHeight={maxHeight} />
        }}
      >
        <FlexView vAlignContent='center' className={cx('menu', className)} onClick={toggleMenu}>
          {dismissOnClickOutside && isOpen && (
            <div className='menu-overlay' onClick={onClose} />
          )}
          {children || (
            <FlexView vAlignContent='center' className={cx('menu-icon-container', { isOpen })}>
              <Icon icon={iconClassName} className='menu-icon' />
            </FlexView>
          )}
        </FlexView>
      </Popover>
{'fraction_non_alphanumeric': 0.13047884469217128, 'fraction_numerical': 0.002280212819863187, 'mean_word_length': 2.814009661835749, 'pattern_counts': {'":': 0, '<': 25, '<?xml version=': 0, '>': 35, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1631011', 'n_tokens_mistral': 2352, 'n_tokens_neox': 2285, 'n_words': 661}