1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-25 11:28:51 +01:00

Extract Menu component from ConnectedAccountsListOptions (#8632)

A `Menu` component has been extracted from the
`ConnectedAccountsListOptions` component. The menu and the menu items
are now the `Menu` and `MenuItem` components respectively.

A custom body was added to the Storybook preview to ensure that the
`popover-content` element was present in the DOM before the Menu was
constructed.
This commit is contained in:
Mark Stacey 2020-05-20 14:52:23 -03:00 committed by GitHub
parent 91953f062b
commit f886686db2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 179 additions and 99 deletions

View File

@ -0,0 +1,2 @@
<div id="custom-root"></div>
<div id="popover-content"></div>

View File

@ -1,26 +0,0 @@
import classnames from 'classnames'
import PropTypes from 'prop-types'
import React, { PureComponent } from 'react'
export default class ConnectedAccountsListOptionsItem extends PureComponent {
static propTypes = {
iconClassNames: PropTypes.string.isRequired,
children: PropTypes.node.isRequired,
onClick: PropTypes.func,
}
static defaultProps = {
onClick: undefined,
}
render () {
const { children, iconClassNames, onClick } = this.props
return (
<button className="connected-accounts-options__row" onClick={onClick}>
<i className={classnames('connected-accounts-options__row-icon', iconClassNames)} />
<span>{children}</span>
</button>
)
}
}

View File

@ -1 +0,0 @@
export { default } from './connected-accounts-list-options-item.component'

View File

@ -1,34 +1,23 @@
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import React, { useRef, useState } from 'react' import React, { useState } from 'react'
import { createPortal } from 'react-dom' import { Menu } from '../../../ui/menu'
import { usePopper } from 'react-popper'
const ConnectedAccountsListOptions = ({ children, onShowOptions, onHideOptions, show }) => { const ConnectedAccountsListOptions = ({ children, onShowOptions, onHideOptions, show }) => {
const [optionsButtonElement, setOptionsButtonElement] = useState(null) const [optionsButtonElement, setOptionsButtonElement] = useState(null)
const [popperElement, setPopperElement] = useState(null)
const popoverContainerElement = useRef(document.getElementById('popover-content'))
const { attributes, styles } = usePopper(optionsButtonElement, popperElement, {
modifiers: [{ name: 'preventOverflow', options: { altBoundary: true } }],
})
return ( return (
<> <>
<button className="fas fa-ellipsis-v connected-accounts-options__button" onClick={onShowOptions} ref={setOptionsButtonElement} /> <button className="fas fa-ellipsis-v connected-accounts-options__button" onClick={onShowOptions} ref={setOptionsButtonElement} />
{ {
show show
? createPortal( ? (
<> <Menu
<div className="connected-accounts-options__background" onClick={onHideOptions} /> anchorElement={optionsButtonElement}
<div onHide={onHideOptions}
className="connected-accounts-options" popperOptions={{ modifiers: [{ name: 'preventOverflow', options: { altBoundary: true } }] }}
ref={setPopperElement} >
style={styles.popper} { children }
{...attributes.popper} </Menu>
>
{ children }
</div>
</>,
popoverContainerElement.current
) )
: null : null
} }

View File

@ -4,7 +4,7 @@ import React, { PureComponent } from 'react'
import ConnectedAccountsListPermissions from './connected-accounts-list-permissions' import ConnectedAccountsListPermissions from './connected-accounts-list-permissions'
import ConnectedAccountsListItem from './connected-accounts-list-item' import ConnectedAccountsListItem from './connected-accounts-list-item'
import ConnectedAccountsListOptions from './connected-accounts-list-options' import ConnectedAccountsListOptions from './connected-accounts-list-options'
import ConnectedAccountsListOptionsItem from './connected-accounts-list-options-item' import { MenuItem } from '../../ui/menu'
export default class ConnectedAccountsList extends PureComponent { export default class ConnectedAccountsList extends PureComponent {
static contextTypes = { static contextTypes = {
@ -112,20 +112,20 @@ export default class ConnectedAccountsList extends PureComponent {
> >
{ {
address === selectedAddress ? null : ( address === selectedAddress ? null : (
<ConnectedAccountsListOptionsItem <MenuItem
iconClassNames="fas fa-random" iconClassName="fas fa-random"
onClick={this.switchAccount} onClick={this.switchAccount}
> >
{t('switchToThisAccount')} {t('switchToThisAccount')}
</ConnectedAccountsListOptionsItem> </MenuItem>
) )
} }
<ConnectedAccountsListOptionsItem <MenuItem
iconClassNames="fas fa-ban" iconClassName="fas fa-ban"
onClick={this.disconnectAccount} onClick={this.disconnectAccount}
> >
{t('disconnectThisAccount')} {t('disconnectThisAccount')}
</ConnectedAccountsListOptionsItem> </MenuItem>
</ConnectedAccountsListOptions> </ConnectedAccountsListOptions>
)} )}
/> />

View File

@ -63,55 +63,11 @@
} }
.connected-accounts-options { .connected-accounts-options {
background: $white;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.214);
border-radius: 8px;
width: 225px;
color: $Black-100;
display: flex;
flex-direction: column;
align-items: center;
padding: 0 16px;
right: 24px;
font-family: Roboto, 'sans-serif';
font-size: 14px;
font-weight: normal;
line-height: 20px;
z-index: 1050;
&__row {
background: none;
font-family: inherit;
font-size: inherit;
display: flex;
flex-direction: row;
align-items: center;
width: 100%;
padding: 14px 0;
cursor: pointer;
&-icon {
margin-right: 8px;
}
}
&__button { &__button {
background: inherit; background: inherit;
color: $Grey-500; color: $Grey-500;
font-size: 22px; font-size: 22px;
} }
&__background {
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 1050;
}
} }
.connected-accounts-permissions { .connected-accounts-permissions {

View File

@ -30,6 +30,8 @@
@import '../ui/identicon/index'; @import '../ui/identicon/index';
@import '../ui/menu/menu';
@import 'info-box/index'; @import 'info-box/index';
@import 'menu-bar/index'; @import 'menu-bar/index';

View File

@ -0,0 +1,2 @@
export { default as Menu } from './menu'
export { default as MenuItem } from './menu-item'

View File

@ -0,0 +1,31 @@
import React from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
const MenuItem = ({ children, className, iconClassName, onClick }) => (
<button className={classnames('menu-item', className)} onClick={onClick}>
{
iconClassName
? (
<i className={classnames('menu-item__icon', iconClassName)} />
)
: null
}
<span>{children}</span>
</button>
)
MenuItem.propTypes = {
children: PropTypes.node.isRequired,
className: PropTypes.string,
iconClassName: PropTypes.string,
onClick: PropTypes.func,
}
MenuItem.defaultProps = {
className: undefined,
iconClassName: undefined,
onClick: undefined,
}
export default MenuItem

View File

@ -0,0 +1,43 @@
import PropTypes from 'prop-types'
import React, { useRef, useState } from 'react'
import { createPortal } from 'react-dom'
import { usePopper } from 'react-popper'
import classnames from 'classnames'
const Menu = ({ anchorElement, children, className, onHide, popperOptions }) => {
const [popperElement, setPopperElement] = useState(null)
const popoverContainerElement = useRef(document.getElementById('popover-content'))
const { attributes, styles } = usePopper(anchorElement, popperElement, popperOptions)
return createPortal(
<>
<div className="menu__background" onClick={onHide} />
<div
className={classnames('menu__container', className)}
ref={setPopperElement}
style={styles.popper}
{...attributes.popper}
>
{ children }
</div>
</>,
popoverContainerElement.current
)
}
Menu.propTypes = {
anchorElement: PropTypes.instanceOf(window.Element),
children: PropTypes.node.isRequired,
className: PropTypes.string,
onHide: PropTypes.func.isRequired,
popperOptions: PropTypes.object,
}
Menu.defaultProps = {
anchorElement: undefined,
className: undefined,
popperOptions: undefined,
}
export default Menu

View File

@ -0,0 +1,46 @@
.menu {
&__container {
background: $white;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.214);
border-radius: 8px;
width: 225px;
color: $Black-100;
display: flex;
flex-direction: column;
align-items: center;
padding: 0 16px;
font-family: Roboto, 'sans-serif';
font-size: 14px;
font-weight: normal;
line-height: 20px;
z-index: 1050;
}
&__background {
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 1050;
}
}
.menu-item {
background: none;
font-family: inherit;
font-size: inherit;
display: flex;
flex-direction: row;
align-items: center;
width: 100%;
padding: 14px 0;
cursor: pointer;
&__icon {
margin-right: 8px;
}
}

View File

@ -0,0 +1,36 @@
import React, { useState } from 'react'
import { Menu, MenuItem } from '.'
import { action } from '@storybook/addon-actions'
export default {
title: 'Menu',
}
export const basic = () => {
return (
<Menu
onHide={action('Hide')}
>
<MenuItem iconClassName="fas fa-bullseye" onClick={action('Menu Item 1')}>Menu Item 1</MenuItem>
<MenuItem onClick={action('Menu Item 2')}>Menu Item 2</MenuItem>
<MenuItem iconClassName="fas fa-bold" onClick={action('Menu Item 3')}>Menu Item 3</MenuItem>
</Menu>
)
}
export const anchored = () => {
const [anchorElement, setAnchorElement] = useState(null)
return (
<>
<button ref={setAnchorElement}>Menu</button>
<Menu
anchorElement={anchorElement}
onHide={action('Hide')}
>
<MenuItem iconClassName="fas fa-bullseye" onClick={action('Menu Item 1')}>Menu Item 1</MenuItem>
<MenuItem onClick={action('Menu Item 2')}>Menu Item 2</MenuItem>
<MenuItem iconClassName="fas fa-bold" onClick={action('Menu Item 3')}>Menu Item 3</MenuItem>
</Menu>
</>
)
}