styles: UI improvements and theme support on send view and loading screen

styles: support for scrolling without scrollbar shown
feature: new CopyToClipboard component
This commit is contained in:
Andre Neves 2019-02-18 18:40:15 -05:00
parent ca040ee99f
commit 1ffcf59da2
13 changed files with 305 additions and 63 deletions

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M296 48H176.5C154.4 48 136 65.4 136 87.5V96h-7.5C106.4 96 88 113.4 88 135.5v288c0 22.1 18.4 40.5 40.5 40.5h208c22.1 0 39.5-18.4 39.5-40.5V416h8.5c22.1 0 39.5-18.4 39.5-40.5V176L296 48zm0 44.6l83.4 83.4H296V92.6zm48 330.9c0 4.7-3.4 8.5-7.5 8.5h-208c-4.4 0-8.5-4.1-8.5-8.5v-288c0-4.1 3.8-7.5 8.5-7.5h7.5v255.5c0 22.1 10.4 32.5 32.5 32.5H344v7.5zm48-48c0 4.7-3.4 8.5-7.5 8.5h-208c-4.4 0-8.5-4.1-8.5-8.5v-288c0-4.1 3.8-7.5 8.5-7.5H264v128h128v167.5z"/></svg>

Before

Width:  |  Height:  |  Size: 525 B

View File

@ -0,0 +1,61 @@
// @flow
import React, { PureComponent } from 'react';
import copy from 'copy-to-clipboard';
type Props = {
text: string,
children: any,
onCopy?: Function,
options?: Object,
}
export class CopyToClipboard extends PureComponent<Props> {
static defaultProps = {
onCopy: () => {},
options: {},
};
onClick = (event: Object) => {
const {
text,
onCopy,
children,
options,
} = this.props;
const elem = React.Children.only(children);
const result = copy(text, options);
if (onCopy) {
onCopy(text, result);
}
// Bypass onClick if it was present
if (
elem
&& elem.props
&& typeof elem.props.onClick === 'function'
) {
elem.props.onClick(event);
}
};
render() {
const {
text: _text,
onCopy: _onCopy,
options: _options,
children,
...props
} = this.props;
const elem = React.Children.only(children);
return React.cloneElement(
elem,
{ ...props, onClick: this.onClick },
);
}
}

View File

@ -14,6 +14,10 @@ const Layout = styled.div`
padding-left: ${props => props.theme.layoutPaddingLeft};
padding-right: ${props => props.theme.layoutPaddingRight};
overflow: auto;
::-webkit-scrollbar {
display: none;
}
`;
type Props = {

View File

@ -18,7 +18,19 @@ const Wrapper = styled.div`
flex-direction: column;
align-items: center;
justify-content: center;
background-color: ${props => props.theme.colors.cardBackgroundColor};
background-color: ${props => props.theme.colors.loadingScreenBg};
`;
const LoadingCard = styled.div`
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: #000000;
padding: 60px;
min-width: 300px;
min-height: 200px;
border-radius: 3px;
`;
const CircleWrapper = styled.div`
@ -37,6 +49,10 @@ const Logo = styled.img`
left: calc(50% - 25px);
`;
const LoadingText = styled(TextComponent)`
color: ${props => props.theme.colors.loadingScreenText};
`;
type Props = {
progress: number,
message: string,
@ -86,18 +102,19 @@ export class LoadingScreen extends PureComponent<Props, State> {
justifyContent: 'center',
}}
>
<CircleWrapper>
<Logo src={zcashLogo} alt='Zcash Logo' />
<CircleProgressComponent
progress={progress}
s // TODO: check if this has any effect
responsive
showPercentage={false}
progressColor={appTheme.colors.activeItem}
bgColor={appTheme.colors.inactiveItem}
/>
</CircleWrapper>
<TextComponent value={message} />
<LoadingCard>
<CircleWrapper>
<Logo src={zcashLogo} alt='Zcash Logo' />
<CircleProgressComponent
progress={progress}
responsive
showPercentage={false}
progressColor={appTheme.colors.activeItem}
bgColor={appTheme.colors.inactiveItem}
/>
</CircleWrapper>
<LoadingText value={message} />
</LoadingCard>
</animated.div>
)}
</Transition>

View File

@ -15,6 +15,8 @@ export type Props = {
className?: string,
size?: string | number,
align?: string,
onClick?: Function,
onDoubleClick?: Function,
};
const Text = styled.p`
@ -28,7 +30,8 @@ const Text = styled.p`
`;
export const TextComponent = ({
value, isBold, color, className, size, align, id,
value, isBold, color, className, size,
align, id, onClick, onDoubleClick,
}: Props) => (
<Text
id={id}
@ -37,6 +40,8 @@ export const TextComponent = ({
color={color}
size={`${String(size)}em`}
align={align}
onClick={onClick}
onDoubleClick={onDoubleClick}
>
{value}
</Text>
@ -48,4 +53,6 @@ TextComponent.defaultProps = {
color: appTheme.colors.text,
size: appTheme.fontSize.regular,
align: 'left',
onClick: () => {},
onDoubleClick: () => {},
};

View File

@ -4,7 +4,9 @@ import React, { PureComponent } from 'react';
import styled, { withTheme } from 'styled-components';
import { ColumnComponent } from './column';
import { TextComponent } from './text';
import { QRCode } from './qrcode';
import { CopyToClipboard } from './copy-to-clipboard';
import CopyIconDark from '../assets/images/copy_icon_dark.svg';
import CopyIconLight from '../assets/images/copy_icon_light.svg';
@ -23,21 +25,29 @@ const AddressWrapper = styled.div`
border: 1px solid ${props => props.theme.colors.walletAddressBorder};
`;
const Input = styled.input`
const Address = styled(TextComponent)`
border-radius: ${props => props.theme.boxBorderRadius};
border: none;
background-color: ${props => props.theme.colors.inputBg};
padding: 15px;
width: 100%;
width: 92%;
outline: none;
font-family: ${props => props.theme.fontFamily};
font-size: 13px;
color: #666666;
color: ${props => props.theme.colors.walletAddressInput};
line-height: 1;
letter-spacing: 0.5px;
overflow-x: scroll;
cursor: pointer;
user-select: text;
::-webkit-scrollbar {
display: none;
}
/* // todo: make this theme supported */
${AddressWrapper}:hover & {
color: #fff;
color: ${props => props.theme.colors.walletAddressInputHovered};
}
::placeholder {
@ -45,7 +55,7 @@ const Input = styled.input`
}
`;
const QRCodeWrapper = styled.div`
const QRCodeContainer = styled.div`
align-items: center;
display: flex;
background-color: ${props => props.theme.colors.qrCodeWrapperBg}
@ -56,23 +66,44 @@ const QRCodeWrapper = styled.div`
width: 100%;
`;
const QRCodeWrapper = styled.div`
background-color: #FFFFFF;
padding: 10px;
`;
const IconButton = styled.button`
background: transparent;
cursor: pointer;
outline: none;
border: none;
display: flex;
opacity: 0.4;
width: 28px;
margin-left: 3px;
&:hover {
opacity: 1;
}
position: relative;
`;
const IconImage = styled.img`
max-width: 23px;
opacity: 0.4;
${IconButton}:hover & {
opacity: 1;
}
`;
const CopyTooltip = styled.div`
background: ${props => props.theme.colors.walletAddressTooltipBg};
position: absolute;
top: -27px;
left: -8px;
padding: 6px 10px;
border-radius: 3px;
`;
const TooltipText = styled(TextComponent)`
color: ${props => props.theme.colors.walletAddressTooltip};
font-size: 10px;
font-weight: 700;
`;
type Props = {
@ -81,22 +112,32 @@ type Props = {
};
type State = {
showCopiedTooltip: boolean,
showQRCode: boolean,
};
class Component extends PureComponent<Props, State> {
state = {
showQRCode: false,
showCopiedTooltip: false,
};
show = () => this.setState(() => ({ showQRCode: true }));
showMoreInfo = () => this.setState(() => ({ showQRCode: true }));
hide = () => this.setState(() => ({ showQRCode: false }));
toggleMoreInfo = () => this.setState(prevState => ({
showQRCode: !prevState.showQRCode,
}));
hideTooltip = () => this.setState(() => ({ showCopiedTooltip: false }));
copyAddress = () => this.setState(
() => ({ showCopiedTooltip: true }),
() => setTimeout(() => this.hideTooltip(), 1500),
);
render() {
const { address, theme } = this.props;
const { showQRCode } = this.state;
const toggleVisibility = showQRCode ? this.hide : this.show;
const { showQRCode, showCopiedTooltip } = this.state;
const qrCodeIcon = theme.mode === DARK
? ScanIconDark
@ -109,18 +150,28 @@ class Component extends PureComponent<Props, State> {
return (
<ColumnComponent width='100%'>
<AddressWrapper>
<Input
<Address
value={address}
onChange={() => {}}
onFocus={event => event.currentTarget.select()}
onClick={this.toggleMoreInfo}
onDoubleClick={this.showMoreInfo}
/>
<IconButton onClick={() => {}}>
<IconImage
src={copyIcon}
alt='Copy Address'
/>
</IconButton>
<IconButton onClick={toggleVisibility}>
<CopyToClipboard
text={address}
onCopy={this.copyAddress}
>
<IconButton onClick={() => {}}>
{!showCopiedTooltip ? null : (
<CopyTooltip>
<TooltipText value='Copied!' />
</CopyTooltip>
)}
<IconImage
src={copyIcon}
alt='Copy Address'
/>
</IconButton>
</CopyToClipboard>
<IconButton onClick={this.toggleMoreInfo}>
<IconImage
src={qrCodeIcon}
alt='See QRCode'
@ -128,9 +179,11 @@ class Component extends PureComponent<Props, State> {
</IconButton>
</AddressWrapper>
{!showQRCode ? null : (
<QRCodeWrapper>
<QRCode value={address} />
</QRCodeWrapper>
<QRCodeContainer>
<QRCodeWrapper>
<QRCode value={address} />
</QRCodeWrapper>
</QRCodeContainer>
)}
</ColumnComponent>
);

View File

@ -97,6 +97,10 @@ export const DARK_COLORS = {
// Wallet Address
walletAddressBg: black,
walletAddressBorder: black,
walletAddressInput: '#828282',
walletAddressInputHovered: white,
walletAddressTooltip: black,
walletAddressTooltipBg: white,
// Console
consoleBg: black,
@ -106,4 +110,8 @@ export const DARK_COLORS = {
settingsCardBg: black,
settingsLearnMore: '#AAAAAA',
settingsLearnMoreHovered: '#fff',
// Loading
loadingScreenBg: black,
loadingScreenText: white,
};

View File

@ -102,6 +102,10 @@ export const LIGHT_COLORS = {
// Wallet Address
walletAddressBg: white,
walletAddressBorder: border,
walletAddressInput: '#666',
walletAddressInputHovered: black,
walletAddressTooltip: white,
walletAddressTooltipBg: black,
// Forms
inputBg: white,
@ -119,4 +123,8 @@ export const LIGHT_COLORS = {
settingsCardBg: white,
settingsLearnMore: '#a0a0a0',
settingsLearnMoreHovered: '#000',
// Loading
loadingScreenBg: offWhite,
loadingScreenText: white,
};

View File

@ -230,6 +230,14 @@ export const appTheme: AppTheme = {
[LIGHT]: LIGHT_COLORS.walletAddressBorder,
[DARK]: DARK_COLORS.walletAddressBorder,
}),
walletAddressInput: theme('mode', {
[LIGHT]: LIGHT_COLORS.walletAddressInput,
[DARK]: DARK_COLORS.walletAddressInput,
}),
walletAddressInputHovered: theme('mode', {
[LIGHT]: LIGHT_COLORS.walletAddressInputHovered,
[DARK]: DARK_COLORS.walletAddressInputHovered,
}),
dropdownBg: theme('mode', {
[LIGHT]: LIGHT_COLORS.dropdownBg,
[DARK]: DARK_COLORS.dropdownBg,
@ -298,9 +306,25 @@ export const appTheme: AppTheme = {
[LIGHT]: LIGHT_COLORS.inputBorderActive,
[DARK]: DARK_COLORS.inputBorderActive,
}),
walletAddressTooltipBg: theme('mode', {
[LIGHT]: LIGHT_COLORS.walletAddressTooltipBg,
[DARK]: DARK_COLORS.walletAddressTooltipBg,
}),
qrCodeWrapperBorder: theme('mode', {
[LIGHT]: LIGHT_COLORS.qrCodeWrapperBorder,
[DARK]: DARK_COLORS.qrCodeWrapperBorder,
}),
walletAddressTooltip: theme('mode', {
[LIGHT]: LIGHT_COLORS.walletAddressTooltip,
[DARK]: DARK_COLORS.walletAddressTooltip,
}),
loadingScreenBg: theme('mode', {
[LIGHT]: LIGHT_COLORS.loadingScreenBg,
[DARK]: DARK_COLORS.loadingScreenBg,
}),
loadingScreenText: theme('mode', {
[LIGHT]: LIGHT_COLORS.loadingScreenText,
[DARK]: DARK_COLORS.loadingScreenText,
}),
},
};

View File

@ -59,6 +59,13 @@ const SendWrapper = styled(ColumnComponent)`
margin-top: 45px;
`;
const Label = styled(InputLabelComponent)`
text-transform: uppercase;
color: ${props => props.theme.colors.transactionsDate};
font-size: ${props => `${props.theme.fontSize.regular * 0.9}em`};
font-weight: ${props => String(props.theme.fontWeight.bold)};
`;
type AmountProps =
| {
isEmpty: boolean,
@ -95,7 +102,6 @@ const ShowFeeButton = styled.button`
outline: none;
display: flex;
align-items: center;
margin: 30px 0;
opacity: 0.7;
&:hover {
@ -251,6 +257,28 @@ const ValidateWrapper = styled(RowComponent)`
margin-top: 3px;
`;
const ActionsWrapper = styled(RowComponent)`
padding: 30px 0;
align-items: center;
flex-direction: row;
justify-content: space-between;
`;
const HexadecimalWrapper = styled.div`
display: flex;
opacity: 0.7;
cursor: pointer;
&:hover {
opacity: 1;
}
`;
const HexadecimalText = styled(TextComponent)`
white-space: nowrap;
`;
type Props = {
...SendState,
balance: number,
@ -504,7 +532,7 @@ class Component extends PureComponent<Props, State> {
addresses, balance, zecPrice, isSending, error, operationId, theme,
} = this.props;
const {
showFee, from, amount, to, memo, fee, feeType,
showFee, from, amount, to, memo, fee, feeType, isHexMemo,
} = this.state;
const isEmpty = amount === '';
@ -532,7 +560,7 @@ class Component extends PureComponent<Props, State> {
return (
<RowComponent id='send-wrapper' justifyContent='space-between'>
<FormWrapper>
<InputLabelComponent value='From an address' />
<Label value='From an address' />
<SelectComponent
onChange={this.handleChange('from')}
value={from}
@ -540,7 +568,7 @@ class Component extends PureComponent<Props, State> {
options={addresses.map(addr => ({ value: addr, label: addr }))}
capitalize={false}
/>
<InputLabelComponent value='Amount' />
<Label value='Amount' />
<AmountWrapper isEmpty={isEmpty}>
<AmountInput
renderRight={() => (
@ -561,7 +589,7 @@ class Component extends PureComponent<Props, State> {
disabled={!from}
/>
</AmountWrapper>
<InputLabelComponent value='To' />
<Label value='To' />
<InputComponent
onChange={this.handleChange('to')}
value={to}
@ -569,7 +597,7 @@ class Component extends PureComponent<Props, State> {
renderRight={to ? this.renderValidationStatus : () => null}
name='to'
/>
<InputLabelComponent value='Memo' />
<Label value='Memo' />
<InputComponent
onChange={this.handleChange('memo')}
value={memo}
@ -577,20 +605,28 @@ class Component extends PureComponent<Props, State> {
placeholder='Enter a text here'
name='memo'
/>
<RowComponent justifyContent='flex-end'>
<Checkbox onChange={event => this.setState({ isHexMemo: event.target.checked })} />
<TextComponent value='Hexadecimal memo' />
</RowComponent>
<ShowFeeButton
id='send-show-additional-options-button'
onClick={() => this.setState(state => ({
showFee: !state.showFee,
}))
}
>
<SeeMoreIcon src={seeMoreIcon} alt='Show more icon' />
<TextComponent value={`${showFee ? 'Hide' : 'Show'} Additional Options`} />
</ShowFeeButton>
<ActionsWrapper>
<ShowFeeButton
id='send-show-additional-options-button'
onClick={() => this.setState(state => ({
showFee: !state.showFee,
}))
}
>
<SeeMoreIcon src={seeMoreIcon} alt='Show more icon' />
<TextComponent value={`${showFee ? 'Hide' : 'Show'} Additional Options`} />
</ShowFeeButton>
<HexadecimalWrapper>
<Checkbox
onChange={event => this.setState({ isHexMemo: event.target.checked })}
checked={isHexMemo}
/>
<HexadecimalText
onClick={() => this.setState(prevState => ({ isHexMemo: !prevState.isHexMemo }))}
value='Hexadecimal Memo'
/>
</HexadecimalWrapper>
</ActionsWrapper>
<RevealsMain>
<Transition
native
@ -616,7 +652,7 @@ class Component extends PureComponent<Props, State> {
<FeeWrapper id='send-fee-wrapper'>
<RowComponent alignItems='flex-end' justifyContent='space-between'>
<ColumnComponent width='74%'>
<InputLabelComponent value='Fee' />
<Label value='Fee' />
<InputComponent
type='number'
onChange={this.handleChange('fee')}

View File

@ -45,6 +45,14 @@ type Colors = {
walletSummaryBg: ThemeSet,
walletSummaryBorder: ThemeSet,
// Wallet Address
walletAddressBg: ThemeSet,
walletAddressBorder: ThemeSet,
walletAddressInput: ThemeSet,
walletAddressInputHovered: ThemeSet,
walletAddressTooltip: ThemeSet,
walletAddressTooltipBg: ThemeSet,
// Console
consoleBg: ThemeSet,
consoleBorder: ThemeSet,
@ -103,6 +111,10 @@ type Colors = {
settingsCardBg: ThemeSet,
settingsLearnMore: ThemeSet,
settingsLearnMoreHovered: ThemeSet,
// Loading
loadingScreenBg: ThemeSet,
loadingScreenText: ThemeSet,
};
type FontSize = {

View File

@ -97,6 +97,7 @@
"autoprefixer": "^9.3.1",
"bignumber.js": "^8.0.1",
"connected-react-router": "^5.0.1",
"copy-to-clipboard": "^3.0.8",
"date-fns": "^1.30.1",
"dotenv": "^6.2.0",
"electron-compile": "^6.4.4",

View File

@ -4045,6 +4045,13 @@ copy-text-to-clipboard@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/copy-text-to-clipboard/-/copy-text-to-clipboard-1.0.4.tgz#2286ff6c53495962c5318d34746d256939069c49"
copy-to-clipboard@^3.0.8:
version "3.0.8"
resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.0.8.tgz#f4e82f4a8830dce4666b7eb8ded0c9bcc313aba9"
integrity sha512-c3GdeY8qxCHGezVb1EFQfHYK/8NZRemgcTIzPq7PuxjHAf/raKibn2QdhHPb/y6q74PMgH6yizaDZlRmw6QyKw==
dependencies:
toggle-selection "^1.0.3"
core-js@^1.0.0:
version "1.2.7"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636"
@ -14115,6 +14122,11 @@ to-vfile@^5.0.1, to-vfile@^5.0.2:
is-buffer "^2.0.0"
vfile "^3.0.0"
toggle-selection@^1.0.3:
version "1.0.6"
resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32"
integrity sha1-bkWxJj8gF/oKzH2J14sVuL932jI=
toidentifier@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"