Align list displays with SectionList (UI consistency) (#4621)

* Render Dapps via SectionList

* Initial rendering of accounts via SectionList

* Width vars

* Allow classNames in certifications & tags

* Overlay of info on hover

* Adjust hover balances

* Large owner icons (align with vaults)

* Consistent block mined at message

* Attach ParityBackground to html

* Adjust page padding to align

* Lint fixes

* Link to different types of addresses

* Make content parts clickable only (a within a)

* Force Chrome hardware acceleration

* Trust the vendors... don't go crazy with transform :)

* Use faster & default transitions

* Remove extra container (double scrolling)

* Remove unused container style

* Make dapp iframe background white

* Stop event propgation on tag click
This commit is contained in:
Jaco Greeff 2017-02-24 15:21:36 +01:00 committed by GitHub
parent afecf5b148
commit f670b180d7
24 changed files with 372 additions and 235 deletions

View File

@ -6,12 +6,15 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title><%= htmlWebpackPlugin.options.title %></title>
<style>
html {
background: white;
}
html, body, #container {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
background: white;
font-family: 'Roboto', sans-serif;
font-size: 16px;
font-weight: 300;

View File

@ -14,6 +14,7 @@
/* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.balances {
display: flex;
flex-wrap: wrap;
@ -21,16 +22,22 @@
vertical-align: top;
}
.balance,
.empty {
margin: 0.75em 0.5em 0 0;
}
.empty {
line-height: 24px;
margin: 0 0.5em 0 0;
opacity: 0.25;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.balance {
background: rgba(255, 255, 255, 0.07);
border-radius: 16px;
margin: 0.75em 0.5em 0 0;
max-height: 24px;
max-width: 100%;
display: flex;

View File

@ -27,18 +27,19 @@ class Certifications extends Component {
static propTypes = {
address: PropTypes.string.isRequired,
certifications: PropTypes.array.isRequired,
className: PropTypes.string,
dappsUrl: PropTypes.string.isRequired
}
render () {
const { certifications } = this.props;
const { certifications, className } = this.props;
if (certifications.length === 0) {
return null;
}
return (
<div className={ styles.certifications }>
<div className={ [styles.certifications, className].join(' ') }>
{ certifications.map(this.renderCertification) }
</div>
);

View File

@ -25,21 +25,24 @@ $transitionAll: all 0.75s cubic-bezier(0.23, 1, 0.32, 1);
height: 100%;
padding: 0em;
position: relative;
transition: $transitionAll;
/*transform: translateZ(0);
transition: $transitionAll;*/
width: 100%;
.hoverOverlay {
background: $background;
left: 0;
margin-top: -1.5em;
margin-bottom: 3em;
opacity: inherit;
padding: 0 1.5em 1.5em 1.5em;
position: absolute;
right: 0;
top: 100%;
transition: $transitionAll;
transform: scale(0.5, 0);
transform-origin: top center;
/*transition: $transitionAll;*/
opacity: 0;
/*transform: scale(0.5, 0);
transform-origin: top center;*/
z-index: 100;
}
@ -48,7 +51,8 @@ $transitionAll: all 0.75s cubic-bezier(0.23, 1, 0.32, 1);
.hoverOverlay {
background: $backgroundHover;
transform: scale(1, 1);
/*transform: scale(1, 1);*/
opacity: 1;
}
}
}
@ -74,3 +78,8 @@ $transitionAll: all 0.75s cubic-bezier(0.23, 1, 0.32, 1);
.light .padded {
background: rgba(0, 0, 0, 0.5) !important;
}
.link {
width: 100%;
height: 100%;
}

View File

@ -15,6 +15,7 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import { Link } from 'react-router';
import { Card } from 'material-ui/Card';
import { nodeOrStringProptype } from '~/util/proptypes';
@ -30,6 +31,7 @@ export default class Container extends Component {
compact: PropTypes.bool,
hover: PropTypes.node,
light: PropTypes.bool,
link: PropTypes.string,
onClick: PropTypes.func,
style: PropTypes.object,
tabIndex: PropTypes.number,
@ -37,13 +39,27 @@ export default class Container extends Component {
}
render () {
const { children, className, compact, light, onClick, style, tabIndex } = this.props;
const { children, className, compact, light, link, onClick, style, tabIndex } = this.props;
const props = {};
if (Number.isInteger(tabIndex)) {
props.tabIndex = tabIndex;
}
const card = (
<Card
className={
compact
? styles.compact
: styles.padded
}
onClick={ onClick }
>
{ this.renderTitle() }
{ children }
</Card>
);
return (
<div
className={
@ -58,17 +74,18 @@ export default class Container extends Component {
style={ style }
{ ...props }
>
<Card
className={
compact
? styles.compact
: styles.padded
}
onClick={ onClick }
>
{ this.renderTitle() }
{ children }
</Card>
{
link
? (
<Link
className={ styles.link }
to={ link }
>
{ card }
</Link>
)
: card
}
{ this.renderHover() }
</div>
);

View File

@ -18,6 +18,12 @@
.container {
height: 100%;
position: relative;
&:not(:hover) {
.tags {
display: none;
}
}
}
.image {
@ -26,16 +32,20 @@
top: 1.5em;
}
.author,
.description {
margin-left: 72px;
}
.title {
mragin-bottom: 0.5em;
}
.author, .version {
.titleLink {
color: rgb(0, 151, 167);
}
.author {
font-size: 0.75em;
opacity: 0.5;
margin-top: 0.5em;
margin-top: 1em;
}

View File

@ -15,7 +15,6 @@
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
import React, { Component, PropTypes } from 'react';
import { Link } from 'react-router';
import Container, { Title as ContainerTitle } from '~/ui/Container';
import DappIcon from '~/ui/DappIcon';
@ -50,6 +49,12 @@ export default class DappCard extends Component {
className={
[styles.container, className].join(' ')
}
hover={
<div className={ styles.author }>
{ app.author }, v{ app.version }
</div>
}
link={ this.getLink(app) }
onClick={ onClick }
>
<DappIcon
@ -57,6 +62,7 @@ export default class DappCard extends Component {
className={ styles.image }
/>
<Tags
className={ styles.tags }
tags={
showTags
? [app.type]
@ -65,34 +71,29 @@ export default class DappCard extends Component {
/>
<div className={ styles.description }>
<ContainerTitle
className={ styles.title }
title={
className={
showLink
? this.renderLink(app)
: app.name
? styles.titleLink
: styles.title
}
title={ app.name }
byline={ app.description }
/>
<div className={ styles.author }>
{ app.author }, v{ app.version }
</div>
{ children }
</div>
</Container>
);
}
renderLink (app) {
return (
<Link
to={
app.url === 'web'
? '/web'
: `/app/${app.id}`
}
>
{ app.name }
</Link>
);
getLink (app) {
const { showLink } = this.props;
if (!showLink) {
return null;
}
return app.url === 'web'
? '/web'
: `/app/${app.id}`;
}
}

View File

@ -15,10 +15,17 @@
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.layout {
padding: 0.25em;
.layout,
.layoutPadded {
&>div {
margin-bottom: 0.75em;
margin-bottom: 0.5em;
}
}
.layout {
padding: 0.25em 0.25em 3em 0.25em;
}
.layoutPadded {
padding: 0.5em 0.5em 3em 0.5em;
}

View File

@ -26,11 +26,12 @@ export default class Page extends Component {
buttons: PropTypes.array,
className: PropTypes.string,
children: PropTypes.node,
padded: PropTypes.bool,
title: nodeOrStringProptype()
};
render () {
const { buttons, className, children, title } = this.props;
const { buttons, className, children, padded, title } = this.props;
return (
<div>
@ -44,7 +45,16 @@ export default class Page extends Component {
)
: null
}
<div className={ [styles.layout, className].join(' ') }>
<div
className={
[
padded
? styles.layoutPadded
: styles.layout,
className
].join(' ')
}
>
{ children }
</div>
</div>

View File

@ -23,6 +23,7 @@ class ParityBackground extends Component {
};
static propTypes = {
attachDocument: PropTypes.bool,
backgroundSeed: PropTypes.string,
children: PropTypes.node,
className: PropTypes.string,
@ -70,17 +71,25 @@ class ParityBackground extends Component {
}
render () {
const { children, className, onClick } = this.props;
const { attachDocument, children, className, onClick } = this.props;
const style = {
...this.state.style,
...this.props.style
};
if (attachDocument) {
document.documentElement.style.background = style.background;
}
return (
<div
className={ className }
style={ style }
style={
attachDocument
? {}
: style
}
onTouchTap={ onClick }
>
{ children }

View File

@ -15,6 +15,11 @@
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
$transition: all 0.25s;
$widthNormal: 33.33%;
$widthShrunk: 29%;
$widthExpanded: 42%;
.section {
position: relative;
width: 100%;
@ -38,13 +43,14 @@
/* case where <> 3 columns are required should the need arrise from a UI pov. */
.item {
box-sizing: border-box;
cursor: pointer;
display: flex;
flex: 0 1 33.33%;
max-width: 33.33%;
flex: 0 1 $widthNormal;
max-width: $widthNormal;
opacity: 0.85;
padding: 0.25em;
transition: all 0.75s cubic-bezier(0.23, 1, 0.32, 1);
/* https://www.binarymoon.co.uk/2014/02/fixing-css-transitions-in-google-chrome/ */
transform: translateZ(0);
transition: $transition;
&:hover {
opacity: 1;
@ -55,12 +61,12 @@
&:hover {
.item {
&.stretchOn {
flex: 0 1 29%;
max-width: 29%;
flex: 0 1 $widthShrunk;
max-width: $widthShrunk;
&:hover {
flex: 0 0 42%;
max-width: 42%;
flex: 0 0 $widthExpanded;
max-width: $widthExpanded;
}
}
}

View File

@ -22,6 +22,7 @@ import styles from './tags.css';
export default class Tags extends Component {
static propTypes = {
className: PropTypes.string,
floating: PropTypes.bool,
horizontal: PropTypes.bool,
handleAddSearchToken: PropTypes.func,
@ -35,7 +36,7 @@ export default class Tags extends Component {
};
render () {
const { floating, horizontal, tags } = this.props;
const { className, floating, horizontal, tags } = this.props;
if (!tags || tags.length === 0) {
return null;
@ -51,6 +52,8 @@ export default class Tags extends Component {
classes.push(styles.horizontal);
}
classes.push(className);
return (
<div className={ classes.join(' ') }>
{ this.renderTags() }
@ -73,7 +76,12 @@ export default class Tags extends Component {
.sort()
.map((tag, index) => {
const onClick = handleAddSearchToken
? () => handleAddSearchToken(tag)
? (event) => {
event.stopPropagation();
event.preventDefault();
handleAddSearchToken(tag);
}
: null;
return (

View File

@ -92,7 +92,7 @@ class Account extends Component {
{ this.renderTransferDialog(account, balance) }
{ this.renderVerificationDialog() }
{ this.renderActionbar(balance) }
<Page>
<Page padded>
<Header
account={ account }
balance={ balance }

View File

@ -18,7 +18,7 @@ import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { Container } from '~/ui';
import { Container, SectionList } from '~/ui';
import { fetchCertifiers, fetchCertifications } from '~/redux/providers/certifications/actions';
import Summary from '../Summary';
@ -40,14 +40,6 @@ class List extends Component {
handleAddSearchToken: PropTypes.func
};
render () {
return (
<div className={ styles.list }>
{ this.renderAccounts() }
</div>
);
}
componentWillMount () {
const { accounts, fetchCertifiers, fetchCertifications } = this.props;
@ -57,7 +49,7 @@ class List extends Component {
}
}
renderAccounts () {
render () {
const { accounts, balances, empty } = this.props;
if (empty) {
@ -70,26 +62,30 @@ class List extends Component {
);
}
const addresses = this.getAddresses();
const addresses = this
.getAddresses()
.map((address, idx) => {
const account = accounts[address] || {};
const balance = balances[address] || {};
const owners = account.owners || null;
return addresses.map((address, idx) => {
const account = accounts[address] || {};
const balance = balances[address] || {};
return {
account,
balance,
owners
};
});
const owners = account.owners || null;
return (
<div
className={ styles.item }
key={ address }
>
{ this.renderSummary(account, balance, owners) }
</div>
);
});
return (
<SectionList
items={ addresses }
renderItem={ this.renderSummary }
/>
);
}
renderSummary (account, balance, owners) {
renderSummary = (item) => {
const { account, balance, owners } = item;
const { handleAddSearchToken, link } = this.props;
return (

View File

@ -16,6 +16,7 @@
import BigNumber from 'bignumber.js';
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router';
import { isEqual } from 'lodash';
import ReactTooltip from 'react-tooltip';
@ -27,13 +28,14 @@ import { arrayOrObjectProptype, nullableProptype } from '~/util/proptypes';
import styles from '../accounts.css';
export default class Summary extends Component {
class Summary extends Component {
static contextTypes = {
api: React.PropTypes.object
};
static propTypes = {
account: PropTypes.object.isRequired,
accountsInfo: PropTypes.object.isRequired,
balance: PropTypes.object,
link: PropTypes.string,
name: PropTypes.string,
@ -90,7 +92,7 @@ export default class Summary extends Component {
}
render () {
const { account, handleAddSearchToken } = this.props;
const { account, handleAddSearchToken, noLink } = this.props;
const { tags } = account.meta;
if (!account) {
@ -108,52 +110,71 @@ export default class Summary extends Component {
/>
);
const description = this.getDescription(account.meta);
return (
<Container>
<Tags tags={ tags } handleAddSearchToken={ handleAddSearchToken } />
<Container
className={ styles.account }
hover={
<div className={ styles.overlay }>
{ this.renderBalance(false) }
{ this.renderDescription(account.meta) }
{ this.renderOwners() }
{ this.renderCertifications() }
</div>
}
link={ this.getLink() }
>
<Tags
className={ styles.tags }
tags={ tags }
handleAddSearchToken={ handleAddSearchToken }
/>
<div className={ styles.heading }>
<IdentityIcon
address={ address }
/>
<ContainerTitle
byline={ addressComponent }
className={ styles.main }
description={ description }
title={ this.renderLink() }
className={
noLink
? styles.main
: styles.mainLink
}
title={
<IdentityName
address={ address }
name={ name }
unknown
/>
}
/>
</div>
{ this.renderOwners() }
{ this.renderBalance() }
{ this.renderCertifications() }
{ this.renderBalance(true) }
</Container>
);
}
getDescription (meta = {}) {
renderDescription (meta = {}) {
const { blockNumber } = meta;
if (!blockNumber) {
return null;
}
const formattedBlockNumber = (new BigNumber(blockNumber)).toFormat();
return (
<FormattedMessage
id='accounts.summary.minedBlock'
defaultMessage='Mined at block #{blockNumber}'
values={ {
blockNumber: formattedBlockNumber
} }
/>
<div className={ styles.blockDescription }>
<FormattedMessage
id='accounts.summary.minedBlock'
defaultMessage='Mined at block #{blockNumber}'
values={ {
blockNumber: (new BigNumber(blockNumber)).toFormat()
} }
/>
</div>
);
}
renderOwners () {
const { owners } = this.props;
const { accountsInfo, owners } = this.props;
const ownersValid = (owners || []).filter((owner) => owner.address && new BigNumber(owner.address).gt(0));
if (!ownersValid || ownersValid.length === 0) {
@ -163,55 +184,58 @@ export default class Summary extends Component {
return (
<div className={ styles.owners }>
{
ownersValid.map((owner, index) => (
<div key={ `${index}_${owner.address}` }>
<div
data-tip
data-for={ `owner_${owner.address}` }
data-effect='solid'
ownersValid.map((owner, index) => {
const account = accountsInfo[owner.address];
let ownerLinkType = 'addresses';
if (account) {
if (account.uuid || account.hardware) {
ownerLinkType = 'accounts';
} else if (account.wallet) {
ownerLinkType = 'wallet';
} else if (account.meta.contract) {
ownerLinkType = 'contract';
}
}
return (
<Link
className={ styles.owner }
key={ `${index}_${owner.address}` }
to={ `/${ownerLinkType}/${owner.address}` }
>
<IdentityIcon address={ owner.address } button />
</div>
<ReactTooltip id={ `owner_${owner.address}` }>
<strong>{ owner.name } </strong><small> (owner)</small>
</ReactTooltip>
</div>
))
<div
data-tip
data-for={ `owner_${owner.address}` }
data-effect='solid'
>
<IdentityIcon
address={ owner.address }
center
/>
</div>
<ReactTooltip id={ `owner_${owner.address}` }>
<strong>{ owner.name } </strong><small> (owner)</small>
</ReactTooltip>
</Link>
);
})
}
</div>
);
}
renderLink () {
const { link, noLink, account, name } = this.props;
getLink () {
const { link, account } = this.props;
const { address } = account;
const baseLink = account.wallet
? 'wallet'
: link || 'accounts';
const viewLink = `/${baseLink}/${address}`;
const content = (
<IdentityName
address={ address }
name={ name }
unknown
/>
);
if (noLink) {
return content;
}
return (
<Link to={ viewLink }>
{ content }
</Link>
);
return `/${baseLink}/${address}`;
}
renderBalance () {
renderBalance (onlyEth) {
const { balance } = this.props;
if (!balance) {
@ -219,7 +243,15 @@ export default class Summary extends Component {
}
return (
<Balance balance={ balance } />
<Balance
balance={ balance }
className={
onlyEth
? styles.ethBalances
: styles.allBalances
}
showOnlyEth={ onlyEth }
/>
);
}
@ -231,7 +263,23 @@ export default class Summary extends Component {
}
return (
<Certifications address={ account.address } />
<Certifications
address={ account.address }
className={ styles.Certifications }
/>
);
}
}
function mapStateToProps (state) {
const { accountsInfo } = state.personal;
return {
accountsInfo
};
}
export default connect(
mapStateToProps,
null
)(Summary);

View File

@ -20,10 +20,45 @@
left: 7em;
}
.owners {
margin-top: 1em;
display: flex;
margin-bottom: -0.5em;
.account {
position: relative;
.blockDescription {
color: rgba(255, 255, 255, 0.25);
margin-top: 1.5em;
}
.ethBalances {
opacity: 1;
}
.owners {
display: flex;
justify-content: center;
flex-wrap: wrap;
margin-bottom: -0.5em;
margin-top: 1em;
.owner {
margin: 0.5em;
}
}
.overlay {
margin-top: -3.25em;
}
&:not(:hover) {
.tags {
display: none;
}
}
&:hover {
.ethBalances {
opacity: 0;
}
}
}
.toolbar {
@ -61,7 +96,12 @@
display: flex;
flex-direction: row;
.main {
.main,
.mainLink {
flex: 1;
}
.mainLink h3 {
color: rgb(0, 151, 167);
}
}

View File

@ -90,7 +90,7 @@ class Address extends Component {
{ this.renderEditDialog(contact) }
{ this.renderActionbar(contact) }
{ this.renderDelete(contact) }
<Page>
<Page padded>
<Header
account={ contact || { address, meta: {} } }
balance={ balance }

View File

@ -33,7 +33,10 @@ export default class Container extends Component {
const { children, onCloseFirstRun, showFirstRun, upgradeStore } = this.props;
return (
<ParityBackground className={ styles.container }>
<ParityBackground
attachDocument
className={ styles.container }
>
<FirstRun
onClose={ onCloseFirstRun }
visible={ showFirstRun }

View File

@ -136,7 +136,7 @@ class Contract extends Component {
{ this.renderDeleteDialog(account) }
{ this.renderEditDialog(account) }
{ this.renderExecuteDialog() }
<Page>
<Page padded>
<Header
account={ account }
balance={ balance }

View File

@ -82,7 +82,6 @@ class Contracts extends Component {
<div>
{ this.renderActionbar() }
{ this.renderAddContract() }
{ this.renderAddContract() }
{ this.renderDeployContract() }
<Page>
<List

View File

@ -15,6 +15,7 @@
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.frame {
background: white;
border: 0;
position: absolute;
height: 100%;

View File

@ -14,43 +14,14 @@
/* You should have received a copy of the GNU General Public License
/* along with Parity. If not, see <http://www.gnu.org/licenses/>.
*/
.list {
display: flex;
flex-wrap: wrap;
margin: -0.125em;
position: relative;
.item {
box-sizing: border-box;
flex: 0 1 50%;
opacity: 0.85;
padding: 0.125em;
}
}
.list+.list {
margin-top: -0.25em;
}
.overlay {
background: rgba(0, 0, 0, 0.85);
bottom: 0.5em;
left: -0.125em;
position: absolute;
right: -0.125em;
top: -0.25em;
z-index: 100;
padding: 1em;
line-height: 1.5em;
margin: 0 auto;
text-align: left;
max-width: 980px;
.body {
line-height: 1.5em;
margin: 0 auto;
text-align: left;
max-width: 980px;
&>div:first-child {
padding-bottom: 1em;
}
&>div:first-child {
padding-bottom: 1em;
}
}

View File

@ -23,7 +23,7 @@ import { connect } from 'react-redux';
import { DappPermissions, DappsVisible } from '~/modals';
import PermissionStore from '~/modals/DappPermissions/store';
import { Actionbar, Button, DappCard, Page } from '~/ui';
import { Actionbar, Button, DappCard, Page, SectionList } from '~/ui';
import { LockedIcon, VisibleIcon } from '~/ui/Icons';
import DappsStore from './dappsStore';
@ -53,26 +53,24 @@ class Dapps extends Component {
if (this.store.externalOverlayVisible) {
externalOverlay = (
<div className={ styles.overlay }>
<div className={ styles.body }>
<div>
<FormattedMessage
id='dapps.external.warning'
defaultMessage='Applications made available on the network by 3rd-party authors are not affiliated with Parity nor are they published by Parity. Each remain under the control of their respective authors. Please ensure that you understand the goals for each before interacting.'
/>
</div>
<div>
<Checkbox
className={ styles.accept }
label={
<FormattedMessage
id='dapps.external.accept'
defaultMessage='I understand that these applications are not affiliated with Parity'
/>
}
checked={ false }
onCheck={ this.onClickAcceptExternal }
/>
</div>
<div>
<FormattedMessage
id='dapps.external.warning'
defaultMessage='Applications made available on the network by 3rd-party authors are not affiliated with Parity nor are they published by Parity. Each remain under the control of their respective authors. Please ensure that you understand the goals for each before interacting.'
/>
</div>
<div>
<Checkbox
className={ styles.accept }
label={
<FormattedMessage
id='dapps.external.accept'
defaultMessage='I understand that these applications are not affiliated with Parity'
/>
}
checked={ false }
onCheck={ this.onClickAcceptExternal }
/>
</div>
</div>
);
@ -125,30 +123,23 @@ class Dapps extends Component {
}
renderList (items, overlay) {
if (!items || !items.length) {
return null;
}
return (
<div className={ styles.list }>
{ overlay }
{ items.map(this.renderApp) }
</div>
<SectionList
items={ items }
overlay={ overlay }
renderItem={ this.renderApp }
/>
);
}
renderApp = (app) => {
return (
<div
className={ styles.item }
<DappCard
app={ app }
key={ app.id }
>
<DappCard
app={ app }
showLink
showTags
/>
</div>
showLink
showTags
/>
);
}

View File

@ -120,7 +120,7 @@ class Wallet extends Component {
{ this.renderTransferDialog() }
{ this.renderDeleteDialog(walletAccount) }
{ this.renderActionbar() }
<Page>
<Page padded>
<div className={ styles.info }>
<Header
className={ styles.header }