Add SelectionList component to DRY up (#4639)
* Added SelectionList component for selections * Use SelectionList in DappPermisions * AddDapps uses SelectionList * Fix AccountCard to consistent height * Convert Signer defaults to SelectionList * Subtle selection border * Convert VaultAccounts to SelectionList * Add tests for SectionList component * Apply scroll fixes from lates commit in #4621 * Remove unneeded logs * Remove extra div, fixing ParityBar overflow
This commit is contained in:
parent
5817cfdf41
commit
a6ed3dc5dc
@ -19,10 +19,6 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.description {
|
.description {
|
||||||
margin-top: .5em !important;
|
margin-top: .5em !important;
|
||||||
}
|
}
|
||||||
@ -49,26 +45,3 @@
|
|||||||
opacity: 0.75;
|
opacity: 0.75;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.selectIcon {
|
|
||||||
position: absolute;
|
|
||||||
right: 0.5em;
|
|
||||||
top: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected,
|
|
||||||
.unselected {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.unselected {
|
|
||||||
background: rgba(0, 0, 0, 0.4) !important;
|
|
||||||
|
|
||||||
.selectIcon {
|
|
||||||
opacity: 0.15;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected {
|
|
||||||
background: rgba(255, 255, 255, 0.15) !important;
|
|
||||||
}
|
|
||||||
|
@ -18,8 +18,7 @@ import { observer } from 'mobx-react';
|
|||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
import { DappCard, Portal, SectionList } from '~/ui';
|
import { DappCard, Portal, SelectionList } from '~/ui';
|
||||||
import { CheckIcon } from '~/ui/Icons';
|
|
||||||
|
|
||||||
import styles from './addDapps.css';
|
import styles from './addDapps.css';
|
||||||
|
|
||||||
@ -48,7 +47,6 @@ export default class AddDapps extends Component {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className={ styles.container }>
|
|
||||||
<div className={ styles.warning } />
|
<div className={ styles.warning } />
|
||||||
{
|
{
|
||||||
this.renderList(store.sortedLocal, store.displayApps,
|
this.renderList(store.sortedLocal, store.displayApps,
|
||||||
@ -86,7 +84,6 @@ export default class AddDapps extends Component {
|
|||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</div>
|
|
||||||
</Portal>
|
</Portal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -102,9 +99,11 @@ export default class AddDapps extends Component {
|
|||||||
<div className={ styles.header }>{ header }</div>
|
<div className={ styles.header }>{ header }</div>
|
||||||
<div className={ styles.byline }>{ byline }</div>
|
<div className={ styles.byline }>{ byline }</div>
|
||||||
</div>
|
</div>
|
||||||
<SectionList
|
<SelectionList
|
||||||
|
isChecked={ this.isVisible }
|
||||||
items={ items }
|
items={ items }
|
||||||
noStretch
|
noStretch
|
||||||
|
onSelectClick={ this.onSelect }
|
||||||
renderItem={ this.renderApp }
|
renderItem={ this.renderApp }
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -112,30 +111,27 @@ export default class AddDapps extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
renderApp = (app) => {
|
renderApp = (app) => {
|
||||||
const { store } = this.props;
|
return (
|
||||||
const isVisible = store.displayApps[app.id].visible;
|
<DappCard
|
||||||
|
app={ app }
|
||||||
|
key={ app.id }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const onClick = () => {
|
isVisible = (app) => {
|
||||||
if (isVisible) {
|
const { store } = this.props;
|
||||||
|
|
||||||
|
return store.displayApps[app.id].visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelect = (app) => {
|
||||||
|
const { store } = this.props;
|
||||||
|
|
||||||
|
if (this.isVisible(app)) {
|
||||||
store.hideApp(app.id);
|
store.hideApp(app.id);
|
||||||
} else {
|
} else {
|
||||||
store.showApp(app.id);
|
store.showApp(app.id);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DappCard
|
|
||||||
app={ app }
|
|
||||||
className={
|
|
||||||
isVisible
|
|
||||||
? styles.selected
|
|
||||||
: styles.unselected
|
|
||||||
}
|
|
||||||
key={ app.id }
|
|
||||||
onClick={ onClick }
|
|
||||||
>
|
|
||||||
<CheckIcon className={ styles.selectIcon } />
|
|
||||||
</DappCard>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,51 +15,6 @@
|
|||||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
.container {
|
|
||||||
overflow-y: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item {
|
|
||||||
display: flex;
|
|
||||||
flex: 1;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
.overlay {
|
|
||||||
position: absolute;
|
|
||||||
right: 0.5em;
|
|
||||||
top: 0.5em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected,
|
|
||||||
.unselected {
|
|
||||||
margin-bottom: 0.25em;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.unselected {
|
|
||||||
background: rgba(0, 0, 0, 0.4) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected {
|
|
||||||
background: rgba(255, 255, 255, 0.15) !important;
|
|
||||||
|
|
||||||
&.default {
|
|
||||||
background: rgba(255, 255, 255, 0.35) !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.unselected {
|
|
||||||
}
|
|
||||||
|
|
||||||
.iconDisabled {
|
|
||||||
opacity: 0.15;
|
|
||||||
}
|
|
||||||
|
|
||||||
.legend {
|
.legend {
|
||||||
opacity: 0.75;
|
opacity: 0.75;
|
||||||
|
|
||||||
|
@ -19,8 +19,8 @@ import React, { Component, PropTypes } from 'react';
|
|||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
import { AccountCard, Portal, SectionList } from '~/ui';
|
import { AccountCard, Portal, SelectionList } from '~/ui';
|
||||||
import { CheckIcon, StarIcon, StarOutlineIcon } from '~/ui/Icons';
|
import { CheckIcon, StarIcon } from '~/ui/Icons';
|
||||||
|
|
||||||
import styles from './dappPermissions.css';
|
import styles from './dappPermissions.css';
|
||||||
|
|
||||||
@ -61,60 +61,34 @@ class DappPermissions extends Component {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className={ styles.container }>
|
<SelectionList
|
||||||
<SectionList
|
|
||||||
items={ permissionStore.accounts }
|
items={ permissionStore.accounts }
|
||||||
noStretch
|
noStretch
|
||||||
|
onDefaultClick={ this.onMakeDefault }
|
||||||
|
onSelectClick={ this.onSelect }
|
||||||
renderItem={ this.renderAccount }
|
renderItem={ this.renderAccount }
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
</Portal>
|
</Portal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderAccount = (account) => {
|
onMakeDefault = (account) => {
|
||||||
const { balances, permissionStore } = this.props;
|
this.props.permissionStore.setDefaultAccount(account.address);
|
||||||
const balance = balances[account.address];
|
|
||||||
|
|
||||||
const onMakeDefault = () => {
|
|
||||||
permissionStore.setDefaultAccount(account.address);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onSelect = () => {
|
|
||||||
permissionStore.selectAccount(account.address);
|
|
||||||
};
|
|
||||||
|
|
||||||
let className;
|
|
||||||
|
|
||||||
if (account.checked) {
|
|
||||||
className = account.default
|
|
||||||
? `${styles.selected} ${styles.default}`
|
|
||||||
: styles.selected;
|
|
||||||
} else {
|
|
||||||
className = styles.unselected;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onSelect = (account) => {
|
||||||
|
this.props.permissionStore.selectAccount(account.address);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderAccount = (account) => {
|
||||||
|
const { balances } = this.props;
|
||||||
|
const balance = balances[account.address];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ styles.item }>
|
|
||||||
<AccountCard
|
<AccountCard
|
||||||
account={ account }
|
account={ account }
|
||||||
balance={ balance }
|
balance={ balance }
|
||||||
className={ className }
|
|
||||||
onClick={ onSelect }
|
|
||||||
/>
|
/>
|
||||||
<div className={ styles.overlay }>
|
|
||||||
{
|
|
||||||
account.checked && account.default
|
|
||||||
? <StarIcon />
|
|
||||||
: <StarOutlineIcon className={ styles.iconDisabled } onClick={ onMakeDefault } />
|
|
||||||
}
|
|
||||||
{
|
|
||||||
account.checked
|
|
||||||
? <CheckIcon onClick={ onSelect } />
|
|
||||||
: <CheckIcon className={ styles.iconDisabled } onClick={ onSelect } />
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,11 +22,9 @@ import { bindActionCreators } from 'redux';
|
|||||||
|
|
||||||
import { newError } from '~/redux/actions';
|
import { newError } from '~/redux/actions';
|
||||||
import { personalAccountsInfo } from '~/redux/providers/personalActions';
|
import { personalAccountsInfo } from '~/redux/providers/personalActions';
|
||||||
import { AccountCard, Button, Portal, SectionList } from '~/ui';
|
import { AccountCard, Button, Portal, SelectionList } from '~/ui';
|
||||||
import { CancelIcon, CheckIcon } from '~/ui/Icons';
|
import { CancelIcon, CheckIcon } from '~/ui/Icons';
|
||||||
|
|
||||||
import styles from './vaultAccounts.css';
|
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
class VaultAccounts extends Component {
|
class VaultAccounts extends Component {
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
@ -92,55 +90,47 @@ class VaultAccounts extends Component {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<SectionList
|
{ this.renderList(vaultAccounts, selectedAccounts) }
|
||||||
items={ vaultAccounts }
|
|
||||||
noStretch
|
|
||||||
renderItem={ this.renderAccount }
|
|
||||||
selectedAccounts={ selectedAccounts }
|
|
||||||
/>
|
|
||||||
</Portal>
|
</Portal>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: There are a lot of similarities between the dapp permissions selector
|
renderList (vaultAccounts) {
|
||||||
// (although that has defaults) and this one. A genrerix multi-select component
|
return (
|
||||||
// would be applicable going forward. (Originals passed in, new selections back)
|
<SelectionList
|
||||||
|
isChecked={ this.isSelected }
|
||||||
|
items={ vaultAccounts }
|
||||||
|
noStretch
|
||||||
|
onSelectClick={ this.onSelect }
|
||||||
|
renderItem={ this.renderAccount }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
renderAccount = (account) => {
|
renderAccount = (account) => {
|
||||||
const { balances } = this.props;
|
const { balances } = this.props;
|
||||||
const { vaultName, selectedAccounts } = this.props.vaultStore;
|
|
||||||
const balance = balances[account.address];
|
const balance = balances[account.address];
|
||||||
const isInVault = account.meta.vault === vaultName;
|
|
||||||
const isSelected = isInVault
|
|
||||||
? !selectedAccounts[account.address]
|
|
||||||
: selectedAccounts[account.address];
|
|
||||||
|
|
||||||
const onSelect = () => {
|
|
||||||
this.props.vaultStore.toggleSelectedAccount(account.address);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={ styles.item }>
|
|
||||||
<AccountCard
|
<AccountCard
|
||||||
account={ account }
|
account={ account }
|
||||||
balance={ balance }
|
balance={ balance }
|
||||||
className={
|
|
||||||
isSelected
|
|
||||||
? styles.selected
|
|
||||||
: styles.unselected
|
|
||||||
}
|
|
||||||
onClick={ onSelect }
|
|
||||||
/>
|
/>
|
||||||
<div className={ styles.overlay }>
|
|
||||||
{
|
|
||||||
isSelected
|
|
||||||
? <CheckIcon onClick={ onSelect } />
|
|
||||||
: <CheckIcon className={ styles.iconDisabled } onClick={ onSelect } />
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isSelected = (account) => {
|
||||||
|
const { vaultName, selectedAccounts } = this.props.vaultStore;
|
||||||
|
|
||||||
|
return account.meta.vault === vaultName
|
||||||
|
? !selectedAccounts[account.address]
|
||||||
|
: selectedAccounts[account.address];
|
||||||
|
}
|
||||||
|
|
||||||
|
onSelect = (account) => {
|
||||||
|
this.props.vaultStore.toggleSelectedAccount(account.address);
|
||||||
|
}
|
||||||
|
|
||||||
onClose = () => {
|
onClose = () => {
|
||||||
this.props.vaultStore.closeAccountsModal();
|
this.props.vaultStore.closeAccountsModal();
|
||||||
}
|
}
|
||||||
|
@ -130,11 +130,11 @@ describe('modals/VaultAccounts', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('components', () => {
|
describe('components', () => {
|
||||||
describe('SectionList', () => {
|
describe('SelectionList', () => {
|
||||||
let sectionList;
|
let sectionList;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
sectionList = component.find('SectionList');
|
sectionList = component.find('SelectionList');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('has the filtered accounts', () => {
|
it('has the filtered accounts', () => {
|
||||||
|
@ -20,7 +20,7 @@
|
|||||||
background-color: rgba(0, 0, 0, 0.8);
|
background-color: rgba(0, 0, 0, 0.8);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
margin: 0.5em 0;
|
height: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
transition: transform ease-out 0.1s;
|
transition: transform ease-out 0.1s;
|
||||||
transform: scale(1);
|
transform: scale(1);
|
||||||
|
@ -39,7 +39,7 @@ export default class AccountCard extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { account, balance, className } = this.props;
|
const { account, balance, className, onFocus } = this.props;
|
||||||
const { copied } = this.state;
|
const { copied } = this.state;
|
||||||
const { address, description, meta = {}, name } = account;
|
const { address, description, meta = {}, name } = account;
|
||||||
const { tags = [] } = meta;
|
const { tags = [] } = meta;
|
||||||
@ -49,14 +49,18 @@ export default class AccountCard extends Component {
|
|||||||
classes.push(styles.copied);
|
classes.push(styles.copied);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const props = onFocus
|
||||||
|
? { tabIndex: 0 }
|
||||||
|
: {};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={ address }
|
key={ address }
|
||||||
tabIndex={ 0 }
|
|
||||||
className={ classes.join(' ') }
|
className={ classes.join(' ') }
|
||||||
onClick={ this.onClick }
|
onClick={ this.onClick }
|
||||||
onFocus={ this.onFocus }
|
onFocus={ this.onFocus }
|
||||||
onKeyDown={ this.handleKeyDown }
|
onKeyDown={ this.handleKeyDown }
|
||||||
|
{ ...props }
|
||||||
>
|
>
|
||||||
<div className={ styles.mainContainer }>
|
<div className={ styles.mainContainer }>
|
||||||
<div className={ styles.infoContainer }>
|
<div className={ styles.infoContainer }>
|
||||||
|
@ -146,4 +146,8 @@
|
|||||||
|
|
||||||
margin: 1em 0;
|
margin: 1em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.account {
|
||||||
|
margin: 0.5em 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -346,6 +346,7 @@ class AddressSelect extends Component {
|
|||||||
<AccountCard
|
<AccountCard
|
||||||
account={ account }
|
account={ account }
|
||||||
balance={ balance }
|
balance={ balance }
|
||||||
|
className={ styles.account }
|
||||||
key={ `account_${index}` }
|
key={ `account_${index}` }
|
||||||
onClick={ this.handleClick }
|
onClick={ this.handleClick }
|
||||||
onFocus={ this.focusItem }
|
onFocus={ this.focusItem }
|
||||||
|
17
js/src/ui/SelectionList/index.js
Normal file
17
js/src/ui/SelectionList/index.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
export default from './selectionList';
|
@ -15,16 +15,25 @@
|
|||||||
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/* TODO: These overlap with DappPermissions now, make DRY */
|
|
||||||
/* (selection component or just styles?) */
|
|
||||||
.iconDisabled {
|
|
||||||
opacity: 0.15;
|
|
||||||
}
|
|
||||||
|
|
||||||
.item {
|
.item {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: inset 0 0 0 2px rgb(255, 255, 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.25);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.overlay {
|
.overlay {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@ -33,16 +42,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected,
|
|
||||||
.unselected {
|
|
||||||
margin-bottom: 0.25em;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
&:focus {
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected {
|
.selected {
|
||||||
background: rgba(255, 255, 255, 0.15) !important;
|
box-shadow: inset 0 0 0 2px rgb(255, 255, 255);
|
||||||
|
filter: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unselected {
|
||||||
|
filter: grayscale(100%);
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.iconDisabled {
|
||||||
|
opacity: 0.15;
|
||||||
}
|
}
|
93
js/src/ui/SelectionList/selectionList.js
Normal file
93
js/src/ui/SelectionList/selectionList.js
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import React, { Component, PropTypes } from 'react';
|
||||||
|
|
||||||
|
import { CheckIcon, StarIcon, StarOutlineIcon } from '~/ui/Icons';
|
||||||
|
import SectionList from '~/ui/SectionList';
|
||||||
|
import { arrayOrObjectProptype } from '~/util/proptypes';
|
||||||
|
|
||||||
|
import styles from './selectionList.css';
|
||||||
|
|
||||||
|
export default class SelectionList extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
isChecked: PropTypes.func,
|
||||||
|
items: arrayOrObjectProptype().isRequired,
|
||||||
|
noStretch: PropTypes.bool,
|
||||||
|
onDefaultClick: PropTypes.func,
|
||||||
|
onSelectClick: PropTypes.func.isRequired,
|
||||||
|
renderItem: PropTypes.func.isRequired
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { items, noStretch } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SectionList
|
||||||
|
items={ items }
|
||||||
|
noStretch={ noStretch }
|
||||||
|
renderItem={ this.renderItem }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderItem = (item, index) => {
|
||||||
|
const { isChecked, onDefaultClick, onSelectClick, renderItem } = this.props;
|
||||||
|
const isSelected = isChecked
|
||||||
|
? isChecked(item)
|
||||||
|
: item.checked;
|
||||||
|
|
||||||
|
const makeDefault = () => {
|
||||||
|
onDefaultClick(item);
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
const selectItem = () => {
|
||||||
|
onSelectClick(item);
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
let defaultIcon = null;
|
||||||
|
|
||||||
|
if (onDefaultClick) {
|
||||||
|
defaultIcon = isSelected && item.default
|
||||||
|
? <StarIcon />
|
||||||
|
: <StarOutlineIcon className={ styles.iconDisabled } onClick={ makeDefault } />;
|
||||||
|
}
|
||||||
|
|
||||||
|
const classes = isSelected
|
||||||
|
? [styles.item, styles.selected]
|
||||||
|
: [styles.item, styles.unselected];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={ classes.join(' ') }>
|
||||||
|
<div
|
||||||
|
className={ styles.content }
|
||||||
|
onClick={ selectItem }
|
||||||
|
>
|
||||||
|
{ renderItem(item, index) }
|
||||||
|
</div>
|
||||||
|
<div className={ styles.overlay }>
|
||||||
|
{ defaultIcon }
|
||||||
|
{
|
||||||
|
isSelected
|
||||||
|
? <CheckIcon onClick={ selectItem } />
|
||||||
|
: <CheckIcon className={ styles.iconDisabled } onClick={ selectItem } />
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
100
js/src/ui/SelectionList/selectionList.spec.js
Normal file
100
js/src/ui/SelectionList/selectionList.spec.js
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
// Copyright 2015-2017 Parity Technologies (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import { shallow } from 'enzyme';
|
||||||
|
import React from 'react';
|
||||||
|
import sinon from 'sinon';
|
||||||
|
|
||||||
|
import SelectionList from './';
|
||||||
|
|
||||||
|
const ITEMS = ['A', 'B', 'C'];
|
||||||
|
|
||||||
|
let component;
|
||||||
|
let instance;
|
||||||
|
let renderItem;
|
||||||
|
let onDefaultClick;
|
||||||
|
let onSelectClick;
|
||||||
|
|
||||||
|
function render (props = {}) {
|
||||||
|
renderItem = sinon.stub();
|
||||||
|
onDefaultClick = sinon.stub();
|
||||||
|
onSelectClick = sinon.stub();
|
||||||
|
|
||||||
|
component = shallow(
|
||||||
|
<SelectionList
|
||||||
|
items={ ITEMS }
|
||||||
|
noStretch='testNoStretch'
|
||||||
|
onDefaultClick={ onDefaultClick }
|
||||||
|
onSelectClick={ onSelectClick }
|
||||||
|
renderItem={ renderItem }
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
instance = component.instance();
|
||||||
|
|
||||||
|
return component;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('ui/SelectionList', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
render();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders defaults', () => {
|
||||||
|
expect(component).to.be.ok;
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('SectionList', () => {
|
||||||
|
let section;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
section = component.find('SectionList');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders the SectionList', () => {
|
||||||
|
expect(section.get(0)).to.be.ok;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes the items through', () => {
|
||||||
|
expect(section.props().items).to.deep.equal(ITEMS);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes internal render method', () => {
|
||||||
|
expect(section.props().renderItem).to.equal(instance.renderItem);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('passes noStretch prop through', () => {
|
||||||
|
expect(section.props().noStretch).to.equal('testNoStretch');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('instance methods', () => {
|
||||||
|
describe('renderItem', () => {
|
||||||
|
let result;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
result = instance.renderItem('testItem', 'testIndex');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('renders', () => {
|
||||||
|
expect(result).to.be.ok;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('calls into parent renderItem', () => {
|
||||||
|
expect(renderItem).to.have.been.calledWith('testItem', 'testIndex');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -46,6 +46,7 @@ export ParityBackground from './ParityBackground';
|
|||||||
export Portal from './Portal';
|
export Portal from './Portal';
|
||||||
export QrCode from './QrCode';
|
export QrCode from './QrCode';
|
||||||
export SectionList from './SectionList';
|
export SectionList from './SectionList';
|
||||||
|
export SelectionList from './SelectionList';
|
||||||
export ShortenedHash from './ShortenedHash';
|
export ShortenedHash from './ShortenedHash';
|
||||||
export SignerIcon from './SignerIcon';
|
export SignerIcon from './SignerIcon';
|
||||||
export Tags from './Tags';
|
export Tags from './Tags';
|
||||||
|
@ -37,7 +37,7 @@ export default class AccountStore {
|
|||||||
@action setDefaultAccount = (defaultAccount) => {
|
@action setDefaultAccount = (defaultAccount) => {
|
||||||
transaction(() => {
|
transaction(() => {
|
||||||
this.accounts = this.accounts.map((account) => {
|
this.accounts = this.accounts.map((account) => {
|
||||||
account.default = account.address === defaultAccount;
|
account.checked = account.address === defaultAccount;
|
||||||
|
|
||||||
return account;
|
return account;
|
||||||
});
|
});
|
||||||
@ -90,7 +90,7 @@ export default class AccountStore {
|
|||||||
const account = accounts[address];
|
const account = accounts[address];
|
||||||
|
|
||||||
account.address = address;
|
account.address = address;
|
||||||
account.default = address === this.defaultAccount;
|
account.checked = address === this.defaultAccount;
|
||||||
|
|
||||||
return account;
|
return account;
|
||||||
})
|
})
|
||||||
|
@ -24,7 +24,7 @@ import { connect } from 'react-redux';
|
|||||||
import store from 'store';
|
import store from 'store';
|
||||||
|
|
||||||
import imagesEthcoreBlock from '~/../assets/images/parity-logo-white-no-text.svg';
|
import imagesEthcoreBlock from '~/../assets/images/parity-logo-white-no-text.svg';
|
||||||
import { AccountCard, Badge, Button, ContainerTitle, IdentityIcon, ParityBackground, SectionList } from '~/ui';
|
import { AccountCard, Badge, Button, ContainerTitle, IdentityIcon, ParityBackground, SelectionList } from '~/ui';
|
||||||
import { CancelIcon, FingerprintIcon } from '~/ui/Icons';
|
import { CancelIcon, FingerprintIcon } from '~/ui/Icons';
|
||||||
import DappsStore from '~/views/Dapps/dappsStore';
|
import DappsStore from '~/views/Dapps/dappsStore';
|
||||||
import { Embedded as Signer } from '~/views/Signer';
|
import { Embedded as Signer } from '~/views/Signer';
|
||||||
@ -328,10 +328,11 @@ class ParityBar extends Component {
|
|||||||
{
|
{
|
||||||
displayType === DISPLAY_ACCOUNTS
|
displayType === DISPLAY_ACCOUNTS
|
||||||
? (
|
? (
|
||||||
<SectionList
|
<SelectionList
|
||||||
className={ styles.accountsSection }
|
className={ styles.accountsSection }
|
||||||
items={ this.accountStore.accounts }
|
items={ this.accountStore.accounts }
|
||||||
noStretch
|
noStretch
|
||||||
|
onSelectClick={ this.onMakeDefault }
|
||||||
renderItem={ this.renderAccount }
|
renderItem={ this.renderAccount }
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@ -344,31 +345,23 @@ class ParityBar extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderAccount = (account) => {
|
onMakeDefault = (account) => {
|
||||||
const { balances } = this.props;
|
|
||||||
const balance = balances[account.address];
|
|
||||||
const makeDefaultAccount = () => {
|
|
||||||
this.toggleAccountsDisplay();
|
this.toggleAccountsDisplay();
|
||||||
|
|
||||||
return this.accountStore
|
return this.accountStore
|
||||||
.makeDefaultAccount(account.address)
|
.makeDefaultAccount(account.address)
|
||||||
.then(() => this.accountStore.loadAccounts());
|
.then(() => this.accountStore.loadAccounts());
|
||||||
};
|
}
|
||||||
|
|
||||||
|
renderAccount = (account) => {
|
||||||
|
const { balances } = this.props;
|
||||||
|
const balance = balances[account.address];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
|
||||||
className={ styles.account }
|
|
||||||
onClick={ makeDefaultAccount }
|
|
||||||
>
|
|
||||||
<AccountCard
|
<AccountCard
|
||||||
account={ account }
|
account={ account }
|
||||||
balance={ balance }
|
balance={ balance }
|
||||||
className={
|
|
||||||
account.default
|
|
||||||
? styles.selected
|
|
||||||
: styles.unselected
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user