Adds sidebar component and refactors slide in wallet view sidebar to use it.

This commit is contained in:
Dan Miller 2018-08-22 23:57:35 -02:30
parent 4560df6e73
commit 40e0d92f57
24 changed files with 290 additions and 76 deletions

View File

@ -123,6 +123,7 @@
"modalState": {},
"previousModalState": {}
},
"sidebar": {},
"transForward": true,
"isLoading": false,
"warning": null,

View File

@ -141,6 +141,7 @@
"accountDetail": {
"subview": "transactions"
},
"sidebar": {},
"modal": {
"modalState": {},
"previousModalState": {}

View File

@ -162,6 +162,7 @@
"accountDetail": {
"subview": "transactions"
},
"sidebar": {},
"modal": {
"modalState": {},
"previousModalState": {}

View File

@ -120,6 +120,7 @@
"accountDetail": {
"subview": "transactions"
},
"sidebar": {},
"modal": {
"modalState": {},
"previousModalState": {}

View File

@ -48,6 +48,7 @@
"accountDetail": {
"subview": "transactions"
},
"sidebar": {},
"transForward": true,
"isLoading": false,
"warning": null,

View File

@ -141,6 +141,7 @@
"accountDetail": {
"subview": "transactions"
},
"sidebar": {},
"modal": {
"modalState": {},
"previousModalState": {}

View File

@ -120,6 +120,7 @@
"accountDetail": {
"subview": "transactions"
},
"sidebar": {},
"modal": {
"modalState": {},
"previousModalState": {}

View File

@ -99,6 +99,7 @@
"accountExport": "none",
"privateKey": ""
},
"sidebar": {},
"transForward": true,
"isLoading": false,
"warning": null,

View File

@ -118,6 +118,7 @@
"modalState": {},
"previousModalState": {}
},
"sidebar": {},
"transForward": true,
"isLoading": false,
"warning": null,

23
package-lock.json generated
View File

@ -16169,6 +16169,14 @@
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
"integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
},
"json2mq": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz",
"integrity": "sha1-tje9O6nqvhIsg+lyBIOusQ0skEo=",
"requires": {
"string-convert": "^0.2.0"
}
},
"json3": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/json3/-/json3-3.3.2.tgz",
@ -25028,6 +25036,16 @@
"xtend": "^4.0.1"
}
},
"react-media": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/react-media/-/react-media-1.8.0.tgz",
"integrity": "sha512-XcfqkDQj5/hmJod/kXUAZljJyMVkWrBWOkzwynAR8BXOGlbFLGBwezM0jQHtp2BrSymhf14/XrQrb3gGBnGK4g==",
"requires": {
"invariant": "^2.2.2",
"json2mq": "^0.2.0",
"prop-types": "^15.5.10"
}
},
"react-modal": {
"version": "3.4.4",
"resolved": "https://registry.npmjs.org/react-modal/-/react-modal-3.4.4.tgz",
@ -27885,6 +27903,11 @@
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz",
"integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM="
},
"string-convert": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/string-convert/-/string-convert-0.2.1.tgz",
"integrity": "sha1-aYLMMEn7tM2F+LJFaLnZvznu/5c="
},
"string-length": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/string-length/-/string-length-1.0.1.tgz",

View File

@ -1849,9 +1849,13 @@ function hideModal (payload) {
}
}
function showSidebar () {
function showSidebar ({ transitionName, type }) {
return {
type: actions.SIDEBAR_OPEN,
value: {
transitionName,
type,
},
}
}

View File

@ -15,7 +15,7 @@ const SendTransactionScreen = require('./components/send/send.container')
const ConfirmTransaction = require('./components/pages/confirm-transaction')
// slideout menu
const WalletView = require('./components/wallet-view')
const Sidebar = require('./components/sidebars').default
// other views
import Home from './components/pages/home'
@ -31,7 +31,6 @@ const CreateAccountPage = require('./components/pages/create-account')
const NoticeScreen = require('./components/pages/notice')
const Loading = require('./components/loading-screen')
const ReactCSSTransitionGroup = require('react-addons-css-transition-group')
const NetworkDropdown = require('./components/dropdowns/network-dropdown')
const AccountMenu = require('./components/account-menu')
@ -105,6 +104,7 @@ class App extends Component {
frequentRpcList,
currentView,
setMouseUserState,
sidebar,
} = this.props
const isLoadingNetwork = network === 'loading' && currentView.name !== 'config'
const loadMessage = loadingMessage || isLoadingNetwork ?
@ -137,7 +137,12 @@ class App extends Component {
h(AppHeader),
// sidebar
this.renderSidebar(),
h(Sidebar, {
sidebarOpen: sidebar.isOpen,
hideSidebar: this.props.hideSidebar,
transitionName: sidebar.transitionName,
type: sidebar.type,
}),
// network dropdown
h(NetworkDropdown, {
@ -157,51 +162,6 @@ class App extends Component {
)
}
renderSidebar () {
return h('div', [
h('style', `
.sidebar-enter {
transition: transform 300ms ease-in-out;
transform: translateX(-100%);
}
.sidebar-enter.sidebar-enter-active {
transition: transform 300ms ease-in-out;
transform: translateX(0%);
}
.sidebar-leave {
transition: transform 200ms ease-out;
transform: translateX(0%);
}
.sidebar-leave.sidebar-leave-active {
transition: transform 200ms ease-out;
transform: translateX(-100%);
}
`),
h(ReactCSSTransitionGroup, {
transitionName: 'sidebar',
transitionEnterTimeout: 300,
transitionLeaveTimeout: 200,
}, [
// A second instance of Walletview is used for non-mobile viewports
this.props.sidebarOpen ? h(WalletView, {
responsiveDisplayClassname: 'sidebar',
style: {},
}) : undefined,
]),
// overlay
// TODO: add onClick for overlay to close sidebar
this.props.sidebarOpen ? h('div.sidebar-overlay', {
style: {},
onClick: () => {
this.props.hideSidebar()
},
}, []) : undefined,
])
}
toggleMetamaskActive () {
if (!this.props.isUnlocked) {
// currently inactive: redirect to password box
@ -270,7 +230,7 @@ App.propTypes = {
provider: PropTypes.object,
frequentRpcList: PropTypes.array,
currentView: PropTypes.object,
sidebarOpen: PropTypes.bool,
sidebar: PropTypes.object,
alertOpen: PropTypes.bool,
hideSidebar: PropTypes.func,
isMascara: PropTypes.bool,
@ -306,7 +266,7 @@ function mapStateToProps (state) {
const { appState, metamask } = state
const {
networkDropdownOpen,
sidebarOpen,
sidebar,
alertOpen,
alertMessage,
isLoading,
@ -333,7 +293,7 @@ function mapStateToProps (state) {
return {
// state from plugin
networkDropdownOpen,
sidebarOpen,
sidebar,
alertOpen,
alertMessage,
isLoading,

View File

@ -459,7 +459,7 @@ const mapDispatchToProps = (dispatch) => {
function mapStateToProps (state) {
return {
keyrings: state.metamask.keyrings,
sidebarOpen: state.appState.sidebarOpen,
sidebarOpen: state.appState.sidebar.isOpen,
}
}

View File

@ -33,3 +33,7 @@
@import './transaction-list-item/index';
@import './transaction-status/index';
@import './app-header/index';
@import './sidebars/index';

View File

@ -3,17 +3,22 @@ import MenuBar from './menu-bar.component'
import { showSidebar, hideSidebar } from '../../actions'
const mapStateToProps = state => {
const { appState: { sidebarOpen, isMascara } } = state
const { appState: { sidebar: { isOpen }, isMascara } } = state
return {
sidebarOpen,
sidebarOpen: isOpen,
isMascara,
}
}
const mapDispatchToProps = dispatch => {
return {
showSidebar: () => dispatch(showSidebar()),
showSidebar: () => {
dispatch(showSidebar({
transitionName: 'sidebar-right',
type: 'wallet-view',
}))
},
hideSidebar: () => dispatch(hideSidebar()),
}
}

View File

@ -0,0 +1 @@
export { default } from './sidebar.component'

View File

@ -0,0 +1,74 @@
.sidebar-right-enter {
transition: transform 300ms ease-in-out;
transform: translateX(-100%);
}
.sidebar-right-enter.sidebar-right-enter-active {
transition: transform 300ms ease-in-out;
transform: translateX(0%);
}
.sidebar-right-leave {
transition: transform 200ms ease-out;
transform: translateX(0%);
}
.sidebar-right-leave.sidebar-right-leave-active {
transition: transform 200ms ease-out;
transform: translateX(-100%);
}
.sidebar-left-enter {
transition: transform 300ms ease-in-out;
transform: translateX(100%);
}
.sidebar-left-enter.sidebar-left-enter-active {
transition: transform 300ms ease-in-out;
transform: translateX(0%);
}
.sidebar-left-leave {
transition: transform 200ms ease-out;
transform: translateX(0%);
}
.sidebar-left-leave.sidebar-left-leave-active {
transition: transform 200ms ease-out;
transform: translateX(100%);
}
.sidebar-left {
flex: 1 0 230px;
background: rgb(250, 250, 250);
z-index: $sidebar-z-index;
position: fixed;
left: 15%;
right: 0;
bottom: 0;
opacity: 1;
visibility: visible;
will-change: transform;
overflow-y: auto;
box-shadow: rgba(0, 0, 0, .15) 2px 2px 4px;
width: 85%;
height: 100%;
@media screen and (min-width: 769px) {
width: 408px;
left: calc(100% - 408px);
}
}
.sidebar-overlay {
z-index: $sidebar-overlay-z-index;
position: fixed;
height: 100%;
width: 100%;
left: 0;
right: 0;
bottom: 0;
opacity: 1;
visibility: visible;
background-color: rgba(0, 0, 0, .3);
}

View File

@ -0,0 +1,49 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import ReactCSSTransitionGroup from 'react-addons-css-transition-group'
import WalletView from '../wallet-view'
import { WALLET_VIEW_SIDEBAR } from './sidebar.constants'
export default class Sidebar extends Component {
static propTypes = {
sidebarOpen: PropTypes.bool,
hideSidebar: PropTypes.func,
transitionName: PropTypes.string,
type: PropTypes.string,
};
renderOverlay () {
return <div className="sidebar-overlay" onClick={() => this.props.hideSidebar()} />
}
renderSidebarContent () {
const { type } = this.props
switch (type) {
case WALLET_VIEW_SIDEBAR:
return <WalletView responsiveDisplayClassname={'sidebar-right' } />
default:
return null
}
}
render () {
const { transitionName, sidebarOpen } = this.props
return (
<div>
<ReactCSSTransitionGroup
transitionName={transitionName}
transitionEnterTimeout={300}
transitionLeaveTimeout={200}
>
{ sidebarOpen ? this.renderSidebarContent() : null }
</ReactCSSTransitionGroup>
{ sidebarOpen ? this.renderOverlay() : null }
</div>
)
}
}

View File

@ -0,0 +1 @@
export const WALLET_VIEW_SIDEBAR = 'wallet-view'

View File

@ -0,0 +1,88 @@
import React from 'react'
import assert from 'assert'
import { shallow } from 'enzyme'
import sinon from 'sinon'
import ReactCSSTransitionGroup from 'react-addons-css-transition-group'
import Sidebar from '../sidebar.component.js'
import WalletView from '../../wallet-view'
const propsMethodSpies = {
hideSidebar: sinon.spy(),
}
describe('Sidebar Component', function () {
let wrapper
beforeEach(() => {
wrapper = shallow(<Sidebar
sidebarOpen={false}
hideSidebar={propsMethodSpies.hideSidebar}
transitionName={'someTransition'}
type={'wallet-view'}
/>)
})
afterEach(() => {
propsMethodSpies.hideSidebar.resetHistory()
})
describe('renderOverlay', () => {
let renderOverlay
beforeEach(() => {
renderOverlay = shallow(wrapper.instance().renderOverlay())
})
it('should render a overlay element', () => {
assert(renderOverlay.hasClass('sidebar-overlay'))
})
it('should pass the correct onClick function to the element', () => {
assert.equal(propsMethodSpies.hideSidebar.callCount, 0)
renderOverlay.props().onClick()
assert.equal(propsMethodSpies.hideSidebar.callCount, 1)
})
})
describe('renderSidebarContent', () => {
let renderSidebarContent
beforeEach(() => {
wrapper.setProps({ type: 'wallet-view' })
renderSidebarContent = wrapper.instance().renderSidebarContent()
})
it('should render sidebar content with the correct props', () => {
wrapper.setProps({ type: 'wallet-view' })
renderSidebarContent = wrapper.instance().renderSidebarContent()
assert.equal(renderSidebarContent.props.responsiveDisplayClassname, 'sidebar-right')
})
it('should not render with an unrecognized type', () => {
wrapper.setProps({ type: 'foobar' })
renderSidebarContent = wrapper.instance().renderSidebarContent()
assert.equal(renderSidebarContent, undefined)
})
})
describe('render', () => {
it('should render a div with one child', () => {
assert(wrapper.is('div'))
assert.equal(wrapper.children().length, 1)
})
it('should render the ReactCSSTransitionGroup without any children', () => {
assert(wrapper.children().at(0).is(ReactCSSTransitionGroup))
assert.equal(wrapper.children().at(0).children().length, 0)
})
it('should render sidebar content and the overlay if sidebarOpen is true', () => {
wrapper.setProps({ sidebarOpen: true })
assert.equal(wrapper.children().length, 2)
assert(wrapper.children().at(1).hasClass('sidebar-overlay'))
assert.equal(wrapper.children().at(0).children().length, 1)
assert(wrapper.children().at(0).children().at(0).is(WalletView))
})
})
})

View File

@ -18,7 +18,7 @@ function mapStateToProps (state) {
userAddress: selectors.getSelectedAddress(state),
contractExchangeRates: state.metamask.contractExchangeRates,
conversionRate: state.metamask.conversionRate,
sidebarOpen: state.appState.sidebarOpen,
sidebarOpen: state.appState.sidebar.isOpen,
}
}

View File

@ -34,7 +34,7 @@ function mapStateToProps (state) {
return {
network: state.metamask.network,
sidebarOpen: state.appState.sidebarOpen,
sidebarOpen: state.appState.sidebar.isOpen,
identities: state.metamask.identities,
accounts: state.metamask.accounts,
tokens: state.metamask.tokens,

View File

@ -148,7 +148,7 @@ $wallet-view-bg: $alabaster;
}
}
.wallet-view.sidebar {
.wallet-view.sidebar-right {
flex: 1 0 230px;
background: rgb(250, 250, 250);
z-index: $sidebar-z-index;
@ -166,20 +166,6 @@ $wallet-view-bg: $alabaster;
height: calc(100% - 56px);
}
.sidebar-overlay {
z-index: $sidebar-overlay-z-index;
position: fixed;
// top: 41px;
height: 100%;
width: 100%;
left: 0;
right: 0;
bottom: 0;
opacity: 1;
visibility: visible;
background-color: rgba(0, 0, 0, .3);
}
// main-container media queries
@media screen and (min-width: 576px) {

View File

@ -48,7 +48,11 @@ function reduceApp (state, action) {
name: null,
},
},
sidebarOpen: false,
sidebar: {
isOpen: false,
transitionName: '',
type: '',
},
alertOpen: false,
alertMessage: null,
qrCodeData: null,
@ -88,12 +92,18 @@ function reduceApp (state, action) {
// sidebar methods
case actions.SIDEBAR_OPEN:
return extend(appState, {
sidebarOpen: true,
sidebar: {
...action.value,
isOpen: true,
},
})
case actions.SIDEBAR_CLOSE:
return extend(appState, {
sidebarOpen: false,
sidebar: {
...appState.sidebar,
isOpen: false,
},
})
// alert methods