Merge pull request #18 from andrerfneves/feature/modal

Feature/modal
This commit is contained in:
George Lima 2018-12-21 13:45:31 -02:00 committed by GitHub
commit d914f2418c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 465 additions and 50 deletions

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" enable-background="new 0 0 24 24" height="24px" id="Layer_1" version="1.1" viewBox="0 0 24 24" width="24px" xml:space="preserve"><path xmlns="http://www.w3.org/2000/svg" fill="#777777" d="M22.245,4.015c0.313,0.313,0.313,0.826,0,1.139l-6.276,6.27c-0.313,0.312-0.313,0.826,0,1.14l6.273,6.272 c0.313,0.313,0.313,0.826,0,1.14l-2.285,2.277c-0.314,0.312-0.828,0.312-1.142,0l-6.271-6.271c-0.313-0.313-0.828-0.313-1.141,0 l-6.276,6.267c-0.313,0.313-0.828,0.313-1.141,0l-2.282-2.28c-0.313-0.313-0.313-0.826,0-1.14l6.278-6.269 c0.313-0.312,0.313-0.826,0-1.14L1.709,5.147c-0.314-0.313-0.314-0.827,0-1.14l2.284-2.278C4.308,1.417,4.821,1.417,5.135,1.73 L11.405,8c0.314,0.314,0.828,0.314,1.141,0.001l6.276-6.267c0.312-0.312,0.826-0.312,1.141,0L22.245,4.015z"/></svg>

After

Width:  |  Height:  |  Size: 839 B

View File

@ -90,7 +90,7 @@ export class DropdownComponent extends Component<Props, State> {
</MenuItem>
)}
{options.map(({ label: optionLabel, onClick }) => (
<MenuItem onClick={onClick}>
<MenuItem onClick={onClick} key={optionLabel}>
<TextComponent value={optionLabel} />
</MenuItem>
))}

117
app/components/modal.js Normal file
View File

@ -0,0 +1,117 @@
// @flow
import React, { PureComponent, Fragment, type Element } from 'react';
import { createPortal } from 'react-dom';
import styled from 'styled-components';
const ModalWrapper = styled.div`
width: 100vw;
height: 100vh;
position: fixed;
display: flex;
align-items: center;
justify-content: center;
top: 0;
left: 0;
z-index: 10;
background-color: rgba(0, 0, 0, 0.4);
`;
const ChildrenWrapper = styled.div`
z-index: 90;
`;
type Props = {
renderTrigger: (() => void) => Element<*>,
children: (() => void) => Element<*>,
closeOnBackdropClick?: boolean,
closeOnEsc?: boolean,
};
type State = {
isVisible: boolean,
};
const modalRoot = document.getElementById('modal-root');
export class ModalComponent extends PureComponent<Props, State> {
element = document.createElement('div');
static defaultProps = {
closeOnBackdropClick: true,
closeOnEsc: true,
};
state = {
isVisible: false,
};
componentDidMount() {
const { closeOnEsc } = this.props;
if (closeOnEsc) {
window.addEventListener('keydown', this.handleEscPress);
}
}
componentWillUnmount() {
const { closeOnEsc } = this.props;
if (closeOnEsc) {
window.removeEventListener('keydown', this.handleEscPress);
}
}
handleEscPress = (event: Object) => {
const { isVisible } = this.state;
if (event.key === 'Escape' && isVisible) {
this.close();
}
};
open = () => {
this.setState(
() => ({ isVisible: true }),
() => {
if (modalRoot) modalRoot.appendChild(this.element);
},
);
};
close = () => {
this.setState(
() => ({ isVisible: false }),
() => {
if (modalRoot) modalRoot.removeChild(this.element);
},
);
};
render() {
const { renderTrigger, children, closeOnBackdropClick } = this.props;
const { isVisible } = this.state;
const toggleVisibility = isVisible ? this.close : this.open;
return (
<Fragment>
{renderTrigger(toggleVisibility)}
{isVisible
? createPortal(
<ModalWrapper
id='modal-portal-wrapper'
onClick={(event) => {
if (
closeOnBackdropClick
&& event.target.id === 'modal-portal-wrapper'
) this.close();
}}
>
<ChildrenWrapper>{children(toggleVisibility)}</ChildrenWrapper>
</ModalWrapper>,
this.element,
)
: null}
</Fragment>
);
}
}

57
app/components/modal.mdx Normal file
View File

@ -0,0 +1,57 @@
---
name: Modal
---
import { Playground, PropsTable } from 'docz'
import { ModalComponent } from './modal.js'
# Modal
## Properties
<PropsTable of={ModalComponent} />
## Basic usage
<Playground>
<ModalComponent
renderTrigger={toggleVisibility => (
<button type="button" onClick={toggleVisibility}>
Open Modal
</button>
)}
>
{toggleVisibility => (
<div style={{ padding: '50px', backgroundColor: 'white' }}>
Modal Content
<button type="button" onClick={toggleVisibility}>
Close Modal
</button>
</div>
)}
</ModalComponent>
</Playground>
## Don't close with ESC or backdrop click
<Playground>
<ModalComponent
closeOnEsc={false}
closeOnBackdropClick={false}
renderTrigger={toggleVisibility => (
<button type="button" onClick={toggleVisibility}>
Open Modal
</button>
)}
>
{toggleVisibility => (
<div style={{ padding: '50px', backgroundColor: 'white' }}>
Modal Content
<button type="button" onClick={toggleVisibility}>
Close Modal
</button>
</div>
)}
</ModalComponent>
</Playground>

View File

@ -1,5 +1,5 @@
// @flow
import React from 'react';
import React, { Fragment } from 'react';
import styled from 'styled-components';
import { TransactionItemComponent, type Transaction } from './transaction-item';
import { TextComponent } from './text';
@ -46,20 +46,23 @@ export const TransactionDailyComponent = ({
<Wrapper>
<Day value={transactionsDate} />
<TransactionsWrapper>
{transactions.map(({
date, type, address, amount,
}, idx) => (
<div>
<TransactionItemComponent
type={type}
date={date}
address={address || ''}
amount={amount}
zecPrice={zecPrice}
/>
{idx < transactions.length - 1 && <Divider />}
</div>
))}
{transactions.map(
({
date, type, address, amount, transactionId,
}, idx) => (
<Fragment key={`${address}-${type}-${amount}-${date}`}>
<TransactionItemComponent
transactionId={transactionId}
type={type}
date={date}
address={address || ''}
amount={amount}
zecPrice={zecPrice}
/>
{idx < transactions.length - 1 && <Divider />}
</Fragment>
),
)}
</TransactionsWrapper>
</Wrapper>
);

View File

@ -0,0 +1,176 @@
// @flow
import React from 'react';
import styled from 'styled-components';
import dateFns from 'date-fns';
import SentIcon from '../assets/images/transaction_sent_icon.svg';
import ReceivedIcon from '../assets/images/transaction_received_icon.svg';
import CloseIcon from '../assets/images/close_icon.svg';
import { TextComponent } from './text';
import { RowComponent } from './row';
import { ColumnComponent } from './column';
import theme from '../theme';
import formatNumber from '../utils/formatNumber';
import truncateAddress from '../utils/truncateAddress';
const Wrapper = styled.div`
width: 30vw;
background-color: ${props => props.theme.colors.background};
display: flex;
flex-direction: column;
align-items: center;
border-radius: 6px;
box-shadow: 0px 0px 30px 0px black;
`;
const TitleWrapper = styled.div`
margin-top: 20px;
`;
const Icon = styled.img`
width: 40px;
height: 40px;
margin: 20px 0;
`;
const CloseIconWrapper = styled.div`
display: flex;
width: 100%;
align-items: flex-end;
justify-content: flex-end;
`;
const CloseIconImg = styled.img`
width: 12.5px;
height: 12.5px;
margin-top: 10px;
margin-right: 10px;
cursor: pointer;
`;
const InfoRow = styled(RowComponent)`
justify-content: space-between;
align-items: center;
width: 100%;
height: 80px;
padding: 0 30px;
`;
const Divider = styled.div`
width: 100%;
background-color: ${props => props.theme.colors.transactionsDetailsLabel};
height: 1px;
opacity: 0.5;
`;
const Label = styled(TextComponent)`
font-weight: ${props => props.theme.fontWeight.bold};
color: ${props => props.theme.colors.transactionsDetailsLabel};
margin-bottom: 5px;
`;
const Ellipsis = styled(TextComponent)`
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: calc(30vw - 60px);
`;
type Props = {
amount: number,
type: 'send' | 'receive',
zecPrice: number,
date: string,
transactionId: string,
from: string,
to: string,
handleClose: () => void,
};
export const TransactionDetailsComponent = ({
amount,
type,
zecPrice,
date,
transactionId,
from,
to,
handleClose,
}: Props) => {
const isReceived = type === 'receive';
return (
<Wrapper>
<CloseIconWrapper>
<CloseIconImg src={CloseIcon} onClick={handleClose} />
</CloseIconWrapper>
<TitleWrapper>
<TextComponent value='Transaction Details' align='center' />
</TitleWrapper>
<Icon
src={isReceived ? ReceivedIcon : SentIcon}
alt='Transaction Type Icon'
/>
<TextComponent
value={formatNumber({
append: `${isReceived ? '+' : '-'}ZEC `,
value: amount,
})}
color={
isReceived
? theme.colors.transactionReceived
: theme.colors.transactionSent
}
isBold
size={2.625}
/>
<TextComponent
value={formatNumber({
append: `${isReceived ? '+' : '-'}USD `,
value: amount * zecPrice,
})}
size={1.5}
color={theme.colors.transactionsDetailsLabel}
/>
<InfoRow>
<ColumnComponent>
<Label value='DATE' />
<TextComponent value={dateFns.format(date, 'MMMM D, YYYY HH:MMA')} />
</ColumnComponent>
<ColumnComponent>
<TextComponent
value='FEES'
isBold
color={theme.colors.transactionsDetailsLabel}
/>
<TextComponent
value={formatNumber({ value: amount * 0.1, append: 'ZEC ' })}
/>
</ColumnComponent>
</InfoRow>
<Divider />
<InfoRow>
<ColumnComponent>
<Label value='TRANSACTION ID' />
<Ellipsis value={transactionId} />
</ColumnComponent>
</InfoRow>
<Divider />
<InfoRow>
<ColumnComponent>
<Label value='FROM' />
<TextComponent value={truncateAddress(from)} />
</ColumnComponent>
</InfoRow>
<Divider />
<InfoRow>
<ColumnComponent>
<Label value='TO' />
<TextComponent value={truncateAddress(to)} />
</ColumnComponent>
</InfoRow>
</Wrapper>
);
};

View File

@ -0,0 +1,32 @@
---
name: Transaction Details
---
import { Playground, PropsTable } from 'docz'
import { TransactionDetails } from './transaction-details.js'
import { DoczWrapper } from '../theme.js'
# Transaction Details
## Properties
<PropsTable of={TransactionDetails} />
## Basic Usage
<Playground>
<DoczWrapper>
{() => (
<TransactionDetails
type="receive"
amount={2851}
zecPrice={56}
transactionId="asdh8233uhd89as283dhuashd23-493iskdfdnhdufhs"
from="WKSJniasnxusiuhd898ns13umdoioj93287yhnjknxU"
to="WKSJniasnxusiuhd898ns13umdoioj93287yhnjknxU"
date={new Date().toISOString()}
/>
)}
</DoczWrapper>
</Playground>

View File

@ -9,6 +9,8 @@ import ReceivedIcon from '../assets/images/transaction_received_icon.svg';
import { RowComponent } from './row';
import { ColumnComponent } from './column';
import { TextComponent } from './text';
import { ModalComponent } from './modal';
import { TransactionDetailsComponent } from './transaction-details';
import theme from '../theme';
@ -53,6 +55,7 @@ export type Transaction = {
address: string,
amount: number,
zecPrice: number,
transactionId: string,
};
export const TransactionItemComponent = ({
@ -61,6 +64,7 @@ export const TransactionItemComponent = ({
address,
amount,
zecPrice,
transactionId,
}: Transaction) => {
const isReceived = type === 'receive';
const transactionTime = dateFns.format(new Date(date), 'HH:mm A');
@ -75,34 +79,55 @@ export const TransactionItemComponent = ({
const transactionAddress = truncateAddress(address);
return (
<Wrapper alignItems='center' justifyContent='space-between'>
<RowComponent alignItems='center'>
<RowComponent alignItems='center'>
<Icon
src={isReceived ? ReceivedIcon : SentIcon}
alt='Transaction Type Icon'
/>
<TransactionColumn>
<TransactionTypeLabel isReceived={isReceived} value={type} />
<TransactionTime value={transactionTime} />
</TransactionColumn>
</RowComponent>
<TextComponent value={transactionAddress} align='left' />
</RowComponent>
<ColumnComponent alignItems='flex-end'>
<TextComponent
value={transactionValueInZec}
color={
isReceived
? theme.colors.transactionReceived
: theme.colors.transactionSent
}
<ModalComponent
renderTrigger={toggleVisibility => (
<Wrapper
alignItems='center'
justifyContent='space-between'
onClick={toggleVisibility}
>
<RowComponent alignItems='center'>
<RowComponent alignItems='center'>
<Icon
src={isReceived ? ReceivedIcon : SentIcon}
alt='Transaction Type Icon'
/>
<TransactionColumn>
<TransactionTypeLabel isReceived={isReceived} value={type} />
<TransactionTime value={transactionTime} />
</TransactionColumn>
</RowComponent>
<TextComponent value={transactionAddress} align='left' />
</RowComponent>
<ColumnComponent alignItems='flex-end'>
<TextComponent
value={transactionValueInZec}
color={
isReceived
? theme.colors.transactionReceived
: theme.colors.transactionSent
}
/>
<TextComponent
value={transactionValueInUsd}
color={theme.colors.inactiveItem}
/>
</ColumnComponent>
</Wrapper>
)}
>
{toggleVisibility => (
<TransactionDetailsComponent
amount={amount}
date={date}
from={address}
to=''
transactionId={transactionId}
handleClose={toggleVisibility}
type={type}
zecPrice={zecPrice}
/>
<TextComponent
value={transactionValueInUsd}
color={theme.colors.inactiveItem}
/>
</ColumnComponent>
</Wrapper>
)}
</ModalComponent>
);
};

View File

@ -41,9 +41,7 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({
// eslint-disable-next-line
if (addressesErr) return dispatch(loadWalletSummaryError({ error: addressesErr.message }));
const [transactionsErr, transactions = []] = await eres(
rpc.listtransactions(),
);
const [transactionsErr, transactions] = await eres(rpc.listtransactions());
if (transactionsErr) {
return dispatch(
@ -59,6 +57,7 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({
addresses,
transactions: flow([
arr => arr.map(transaction => ({
transactionId: transaction.txid,
type: transaction.category,
date: new Date(transaction.time * 1000).toISOString(),
address: transaction.address,

View File

@ -42,6 +42,7 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({
loadTransactionsSuccess({
list: flow([
arr => arr.map(transaction => ({
transactionId: transaction.txid,
type: transaction.category,
date: new Date(transaction.time * 1000).toISOString(),
address: transaction.address,

View File

@ -23,6 +23,7 @@ const transactionSent = '#FF6C6C';
const transactionReceived = '#6AEAC0';
const transactionsDate = '#777777';
const transactionsItemHovered = '#222222';
const transactionsDetailsLabel = transactionsDate;
const appTheme = {
mode: DARK,
@ -61,6 +62,7 @@ const appTheme = {
transactionReceived,
transactionsDate,
transactionsItemHovered,
transactionsDetailsLabel,
},
sidebarWidth: '200px',
headerHeight: '60px',

View File

@ -1,3 +1,3 @@
// @flow
export default ({ value, append = '' }: { value: number, append?: string }) => `${append}${(value || 0).toLocaleString('de-DE')}`;
export default ({ value, append = '' }: { value: number, append?: string }) => `${append}${(value || 0).toLocaleString()}`;

View File

@ -14,5 +14,8 @@ export default {
},
],
},
body: {
raw: '<div id="modal-root" />',
},
},
};

View File

@ -13,8 +13,7 @@
<body>
<noscript> You need to enable JavaScript to run this app. </noscript>
<!-- React App Inject -->
<div id="root"></div>
<!-- End React App Inject -->
<div id="modal-root"></div>
</body>
</html>