mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-22 09:57:02 +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:
parent
91953f062b
commit
f886686db2
2
.storybook/preview-body.html
Normal file
2
.storybook/preview-body.html
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<div id="custom-root"></div>
|
||||||
|
<div id="popover-content"></div>
|
@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
export { default } from './connected-accounts-list-options-item.component'
|
|
@ -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}
|
|
||||||
{...attributes.popper}
|
|
||||||
>
|
>
|
||||||
{ children }
|
{ children }
|
||||||
</div>
|
</Menu>
|
||||||
</>,
|
|
||||||
popoverContainerElement.current
|
|
||||||
)
|
)
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
@ -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 {
|
||||||
|
@ -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';
|
||||||
|
2
ui/app/components/ui/menu/index.js
Normal file
2
ui/app/components/ui/menu/index.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export { default as Menu } from './menu'
|
||||||
|
export { default as MenuItem } from './menu-item'
|
31
ui/app/components/ui/menu/menu-item.js
Normal file
31
ui/app/components/ui/menu/menu-item.js
Normal 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
|
43
ui/app/components/ui/menu/menu.js
Normal file
43
ui/app/components/ui/menu/menu.js
Normal 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
|
46
ui/app/components/ui/menu/menu.scss
Normal file
46
ui/app/components/ui/menu/menu.scss
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
36
ui/app/components/ui/menu/menu.stories.js
Normal file
36
ui/app/components/ui/menu/menu.stories.js
Normal 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>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user