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 }
{
link
? (
<Link
className={ styles.link }
to={ link }
>
{ this.renderTitle() }
{ children }
</Card>
{ 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'
getLink (app) {
const { showLink } = this.props;
if (!showLink) {
return null;
}
return app.url === 'web'
? '/web'
: `/app/${app.id}`
}
>
{ app.name }
</Link>
);
: `/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();
return addresses.map((address, idx) => {
const addresses = this
.getAddresses()
.map((address, idx) => {
const account = accounts[address] || {};
const balance = balances[address] || {};
const owners = account.owners || null;
return (
<div
className={ styles.item }
key={ address }
>
{ this.renderSummary(account, balance, owners) }
</div>
);
return {
account,
balance,
owners
};
});
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 (
<div className={ styles.blockDescription }>
<FormattedMessage
id='accounts.summary.minedBlock'
defaultMessage='Mined at block #{blockNumber}'
values={ {
blockNumber: formattedBlockNumber
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}` }>
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}` }
>
<div
data-tip
data-for={ `owner_${owner.address}` }
data-effect='solid'
>
<IdentityIcon address={ owner.address } button />
<IdentityIcon
address={ owner.address }
center
/>
</div>
<ReactTooltip id={ `owner_${owner.address}` }>
<strong>{ owner.name } </strong><small> (owner)</small>
</ReactTooltip>
</div>
))
</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 `/${baseLink}/${address}`;
}
return (
<Link to={ viewLink }>
{ content }
</Link>
);
}
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;
}
.account {
position: relative;
.blockDescription {
color: rgba(255, 255, 255, 0.25);
margin-top: 1.5em;
}
.ethBalances {
opacity: 1;
}
.owners {
margin-top: 1em;
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,36 +14,8 @@
/* 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;
.body {
line-height: 1.5em;
margin: 0 auto;
text-align: left;
@ -53,4 +25,3 @@
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,7 +53,6 @@ class Dapps extends Component {
if (this.store.externalOverlayVisible) {
externalOverlay = (
<div className={ styles.overlay }>
<div className={ styles.body }>
<div>
<FormattedMessage
id='dapps.external.warning'
@ -74,7 +73,6 @@ class Dapps extends Component {
/>
</div>
</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 }
key={ app.id }
>
<DappCard
app={ app }
key={ app.id }
showLink
showTags
/>
</div>
);
}

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 }