diff --git a/src/ui/components/ContextMenu.tsx b/src/ui/components/ContextMenu.tsx index f36b4a825..bd1ca9995 100644 --- a/src/ui/components/ContextMenu.tsx +++ b/src/ui/components/ContextMenu.tsx @@ -7,9 +7,9 @@ * @format */ -import * as React from 'react'; +import {createElement, useContext, useCallback} from 'react'; +import {ContextMenuContext} from './ContextMenuProvider'; import FlexColumn from './FlexColumn'; -import PropTypes from 'prop-types'; import {MenuItemConstructorOptions} from 'electron'; export type MenuTemplate = Array; @@ -22,7 +22,7 @@ type Props = { /** Nodes that should have a context menu */ children: React.ReactNode; /** The component that is used to wrap the children. Defaults to `FlexColumn`. */ - component: React.ComponentType | string; + component?: React.ComponentType | string; onMouseDown?: (e: React.MouseEvent) => any; } & C; @@ -33,39 +33,27 @@ type Props = { * * Separators can be added by `{type: 'separator'}` */ -export default class ContextMenu extends React.Component> { - static defaultProps = { - component: FlexColumn, - }; - - static contextTypes = { - appendToContextMenu: PropTypes.func, - }; - - onContextMenu = () => { - if (typeof this.context.appendToContextMenu === 'function') { - if (this.props.items != null) { - this.context.appendToContextMenu(this.props.items); - } else if (this.props.buildItems != null) { - this.context.appendToContextMenu(this.props.buildItems()); - } +export default function ContextMenu({ + items, + buildItems, + component, + children, + ...otherProps +}: Props) { + const contextMenuManager = useContext(ContextMenuContext); + const onContextMenu = useCallback(() => { + if (items != null) { + contextMenuManager?.appendToContextMenu(items); + } else if (buildItems != null) { + contextMenuManager?.appendToContextMenu(buildItems()); } - }; - - render() { - const { - items: _items, - buildItems: _buildItems, - component, - ...props - } = this.props; - return React.createElement( - component, - { - onContextMenu: this.onContextMenu, - ...props, - }, - this.props.children, - ); - } + }, []); + return createElement( + component || FlexColumn, + { + onContextMenu, + ...otherProps, + }, + children, + ); } diff --git a/src/ui/components/ContextMenuProvider.tsx b/src/ui/components/ContextMenuProvider.tsx index 7e32fbcc5..20a6d00c0 100644 --- a/src/ui/components/ContextMenuProvider.tsx +++ b/src/ui/components/ContextMenuProvider.tsx @@ -7,52 +7,50 @@ * @format */ -import {Component} from 'react'; import styled from '@emotion/styled'; import electron, {MenuItemConstructorOptions} from 'electron'; -import React from 'react'; -import PropTypes from 'prop-types'; +import React, {useRef, memo, createContext, useMemo, useCallback} from 'react'; type MenuTemplate = Array; +interface ContextMenuManager { + appendToContextMenu(items: MenuTemplate): void; +} const Container = styled.div({ display: 'contents', }); Container.displayName = 'ContextMenuProvider:Container'; +export const ContextMenuContext = createContext( + undefined, +); /** * Flipper's root is already wrapped with this component, so plugins should not * need to use this. ContextMenu is what you probably want to use. */ -export default class ContextMenuProvider extends Component<{ - children: React.ReactNode; -}> { - static childContextTypes = { - appendToContextMenu: PropTypes.func, - }; +const ContextMenuProvider: React.FC<{}> = memo(function ContextMenuProvider({ + children, +}) { + const menuTemplate = useRef([]); + const contextMenuManager = useMemo( + () => ({ + appendToContextMenu(items: MenuTemplate) { + menuTemplate.current = menuTemplate.current.concat(items); + }, + }), + [], + ); + const onContextMenu = useCallback(() => { + const menu = electron.remote.Menu.buildFromTemplate(menuTemplate.current); + menuTemplate.current = []; + menu.popup({window: electron.remote.getCurrentWindow()}); + }, []); - getChildContext() { - return {appendToContextMenu: this.appendToContextMenu}; - } + return ( + + {children} + + ); +}); - _menuTemplate: MenuTemplate = []; - - appendToContextMenu = (items: MenuTemplate) => { - this._menuTemplate = this._menuTemplate.concat(items); - }; - - onContextMenu = () => { - const menu = electron.remote.Menu.buildFromTemplate(this._menuTemplate); - this._menuTemplate = []; - // @ts-ignore: async is private electron API - menu.popup({window: electron.remote.getCurrentWindow(), async: true}); - }; - - render() { - return ( - - {this.props.children} - - ); - } -} +export default ContextMenuProvider;