@ -1,10 +1,25 @@ |
|||
<style> |
|||
.story-title, |
|||
.story-subtitle, |
|||
.chapter-header, |
|||
.section-header, |
|||
.section-subsection, |
|||
.section-button-container { |
|||
filter: invert(100%); |
|||
.dark .story-title, |
|||
.dark .story-subtitle, |
|||
.dark .chapter-header, |
|||
.dark .section-header, |
|||
.dark .section-subsection, |
|||
.dark .section-button-container { |
|||
color: white !important; |
|||
display: block; |
|||
} |
|||
|
|||
.light .story-title, |
|||
.light .story-subtitle, |
|||
.light .chapter-header, |
|||
.light .section-header, |
|||
.light .section-subsection, |
|||
.light .section-button-container { |
|||
color: black !important; |
|||
display: block; |
|||
} |
|||
|
|||
.story-header p { |
|||
margin-bottom: 1em; |
|||
} |
|||
</style> |
|||
|
@ -1,95 +0,0 @@ |
|||
import React from 'react' |
|||
import PropTypes from 'prop-types' |
|||
|
|||
import { FormattedMessage } from 'react-intl' |
|||
import messages from './messages' |
|||
|
|||
import styles from './Countdown.scss' |
|||
|
|||
class Countdown extends React.Component { |
|||
constructor(props) { |
|||
super(props) |
|||
|
|||
this.state = { |
|||
days: null, |
|||
hours: null, |
|||
minutes: null, |
|||
seconds: null, |
|||
expired: false, |
|||
interval: null |
|||
} |
|||
|
|||
this.timerInterval = this.timerInterval.bind(this) |
|||
} |
|||
|
|||
componentDidMount() { |
|||
const interval = setInterval(this.timerInterval, 1000) |
|||
// store interval in the state so it can be accessed later
|
|||
this.setState({ interval }) |
|||
} |
|||
|
|||
componentWillUnmount() { |
|||
const { interval } = this.state |
|||
// use interval from the state to clear the interval
|
|||
clearInterval(interval) |
|||
} |
|||
|
|||
timerInterval() { |
|||
const convertTwoDigits = n => (n > 9 ? n : `0${n}`.slice(-2)) |
|||
|
|||
const now = new Date().getTime() |
|||
const { countDownDate } = this.props |
|||
const countDownSeconds = countDownDate * 1000 |
|||
const distance = countDownSeconds - now |
|||
|
|||
if (distance <= 0) { |
|||
this.setState({ expired: true }) |
|||
const { interval } = this.state |
|||
clearInterval(interval) |
|||
return |
|||
} |
|||
|
|||
const days = convertTwoDigits(Math.floor(distance / (1000 * 60 * 60 * 24))) |
|||
const hours = convertTwoDigits( |
|||
Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)) |
|||
) |
|||
const minutes = convertTwoDigits(Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60))) |
|||
const seconds = convertTwoDigits(Math.floor((distance % (1000 * 60)) / 1000)) |
|||
|
|||
this.setState({ |
|||
days, |
|||
hours, |
|||
minutes, |
|||
seconds |
|||
}) |
|||
} |
|||
|
|||
render() { |
|||
const { days, hours, minutes, seconds, expired } = this.state |
|||
|
|||
if (expired) { |
|||
return <span className={`${styles.container} ${styles.expired}`}>Expired</span> |
|||
} |
|||
if (!days && !hours && !minutes && !seconds) { |
|||
return <span className={styles.container} /> |
|||
} |
|||
|
|||
return ( |
|||
<span className={styles.container}> |
|||
<i className={styles.caption}> |
|||
<FormattedMessage {...messages.expires} /> |
|||
</i> |
|||
<i>{days > 0 && `${days}:`}</i> |
|||
<i>{hours > 0 && `${hours}:`}</i> |
|||
<i>{minutes > 0 && `${minutes}:`}</i> |
|||
<i>{seconds >= 0 && `${seconds}`}</i> |
|||
</span> |
|||
) |
|||
} |
|||
} |
|||
|
|||
Countdown.propTypes = { |
|||
countDownDate: PropTypes.number.isRequired |
|||
} |
|||
|
|||
export default Countdown |
@ -1,16 +0,0 @@ |
|||
@import 'styles/variables.scss'; |
|||
|
|||
.container { |
|||
display: block; |
|||
text-align: center; |
|||
font-size: 12px; |
|||
min-height: 12px; |
|||
|
|||
&.expired { |
|||
color: $red; |
|||
} |
|||
} |
|||
|
|||
.caption { |
|||
margin-right: 4px; |
|||
} |
@ -1,3 +0,0 @@ |
|||
import Countdown from './Countdown' |
|||
|
|||
export default Countdown |
@ -1,6 +0,0 @@ |
|||
import { defineMessages } from 'react-intl' |
|||
|
|||
/* eslint-disable max-len */ |
|||
export default defineMessages({ |
|||
expires: 'Expires in' |
|||
}) |
@ -1,3 +0,0 @@ |
|||
import AmountInput from './AmountInput' |
|||
|
|||
export default AmountInput |
@ -1,23 +0,0 @@ |
|||
import React from 'react' |
|||
import PropTypes from 'prop-types' |
|||
|
|||
import SkinnyBitcoin from 'components/Icon/SkinnyBitcoin' |
|||
import Litecoin from 'components/Icon/Litecoin' |
|||
|
|||
const CryptoIcon = ({ currency, styles }) => { |
|||
switch (currency) { |
|||
case 'btc': |
|||
return <SkinnyBitcoin style={styles} /> |
|||
case 'ltc': |
|||
return <Litecoin style={styles} /> |
|||
default: |
|||
return <span /> |
|||
} |
|||
} |
|||
|
|||
CryptoIcon.propTypes = { |
|||
currency: PropTypes.string.isRequired, |
|||
styles: PropTypes.object |
|||
} |
|||
|
|||
export default CryptoIcon |
@ -1,3 +0,0 @@ |
|||
import CryptoIcon from './CryptoIcon' |
|||
|
|||
export default CryptoIcon |
@ -1,20 +0,0 @@ |
|||
import React from 'react' |
|||
import PropTypes from 'prop-types' |
|||
import FaDollar from 'react-icons/lib/fa/dollar' |
|||
import CryptoIcon from '../CryptoIcon' |
|||
|
|||
const CurrencyIcon = ({ currency, crypto, styles }) => { |
|||
return currency === 'usd' ? ( |
|||
<FaDollar style={styles} /> |
|||
) : ( |
|||
<CryptoIcon styles={styles} currency={crypto} /> |
|||
) |
|||
} |
|||
|
|||
CurrencyIcon.propTypes = { |
|||
currency: PropTypes.string.isRequired, |
|||
crypto: PropTypes.string.isRequired, |
|||
styles: PropTypes.object |
|||
} |
|||
|
|||
export default CurrencyIcon |
@ -1,3 +0,0 @@ |
|||
import CurrencyIcon from './CurrencyIcon' |
|||
|
|||
export default CurrencyIcon |
@ -1,24 +0,0 @@ |
|||
import React from 'react' |
|||
import PropTypes from 'prop-types' |
|||
import { Box, Flex } from 'rebass' |
|||
import { Text } from 'components/UI' |
|||
|
|||
const PaySummaryRow = ({ left, right }) => ( |
|||
<Box py={3}> |
|||
<Flex alignItems="center"> |
|||
<Box width={1 / 2}> |
|||
<Text fontWeight="normal">{left}</Text> |
|||
</Box> |
|||
<Box width={1 / 2}> |
|||
<Text textAlign="right">{right}</Text> |
|||
</Box> |
|||
</Flex> |
|||
</Box> |
|||
) |
|||
|
|||
PaySummaryRow.propTypes = { |
|||
left: PropTypes.any, |
|||
right: PropTypes.any |
|||
} |
|||
|
|||
export default PaySummaryRow |
@ -0,0 +1,85 @@ |
|||
import React from 'react' |
|||
import PropTypes from 'prop-types' |
|||
|
|||
import { FormattedMessage, FormattedRelative } from 'react-intl' |
|||
import { Text } from 'components/UI' |
|||
import messages from './messages' |
|||
|
|||
class Countdown extends React.Component { |
|||
state = { |
|||
isExpired: null, |
|||
timer: null |
|||
} |
|||
|
|||
static propTypes = { |
|||
colorActive: PropTypes.string, |
|||
colorExpired: PropTypes.string, |
|||
date: PropTypes.oneOfType([PropTypes.number, PropTypes.instanceOf(Date)]).isRequired, |
|||
countdownStyle: PropTypes.string, |
|||
countUpAfterExpire: PropTypes.bool, |
|||
updateInterval: PropTypes.number |
|||
} |
|||
|
|||
static defaultProps = { |
|||
colorActive: 'superGreen', |
|||
colorExpired: 'superRed', |
|||
countdownStyle: 'best fit', |
|||
countUpAfterExpire: true, |
|||
updateInterval: 1000 |
|||
} |
|||
|
|||
componentDidMount() { |
|||
let { date } = this.props |
|||
if (date instanceof Date) { |
|||
date = new Date(date) |
|||
} |
|||
|
|||
const expiresIn = date - Date.now() |
|||
if (expiresIn >= 0) { |
|||
this.setState({ isExpired: false }) |
|||
const timer = setInterval(() => this.setState({ isExpired: true }), expiresIn) |
|||
this.setState({ timer }) |
|||
} else { |
|||
this.setState({ isExpired: true }) |
|||
} |
|||
} |
|||
|
|||
componentWillUnmount() { |
|||
const { timer } = this.state |
|||
clearInterval(timer) |
|||
} |
|||
|
|||
render() { |
|||
const { |
|||
colorActive, |
|||
colorExpired, |
|||
countdownStyle, |
|||
countUpAfterExpire, |
|||
date, |
|||
updateInterval, |
|||
...rest |
|||
} = this.props |
|||
const { isExpired } = this.state |
|||
return ( |
|||
<Text color={isExpired ? colorExpired : colorActive} {...rest}> |
|||
{isExpired ? ( |
|||
<FormattedMessage {...messages.expired} /> |
|||
) : ( |
|||
<FormattedMessage {...messages.expires} /> |
|||
)} |
|||
{(!isExpired || (isExpired && countUpAfterExpire)) && ( |
|||
<React.Fragment> |
|||
{` `} |
|||
<FormattedRelative |
|||
value={new Date(date)} |
|||
updateInterval={updateInterval} |
|||
style={countdownStyle} |
|||
/> |
|||
</React.Fragment> |
|||
)} |
|||
</Text> |
|||
) |
|||
} |
|||
} |
|||
|
|||
export default Countdown |
@ -0,0 +1,22 @@ |
|||
import React from 'react' |
|||
import PropTypes from 'prop-types' |
|||
import { Flex } from 'rebass' |
|||
import { Text } from 'components/UI' |
|||
|
|||
const DataRow = ({ left, right }) => ( |
|||
<Flex alignItems="center" py={3}> |
|||
<Text width={1 / 2} fontWeight="normal"> |
|||
{left} |
|||
</Text> |
|||
<Text width={1 / 2} textAlign="right"> |
|||
{right} |
|||
</Text> |
|||
</Flex> |
|||
) |
|||
|
|||
DataRow.propTypes = { |
|||
left: PropTypes.any, |
|||
right: PropTypes.any |
|||
} |
|||
|
|||
export default DataRow |
@ -1,3 +0,0 @@ |
|||
import Value from './Value' |
|||
|
|||
export default Value |
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 337 B After Width: | Height: | Size: 342 B |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 4.9 KiB After Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.1 KiB |
@ -1,5 +1,186 @@ |
|||
import React from 'react' |
|||
import { storiesOf } from '@storybook/react' |
|||
import { StateDecorator, Store } from '@sambego/storybook-state' |
|||
import { Flex } from 'rebass' |
|||
import { |
|||
Button, |
|||
Dropdown, |
|||
Form, |
|||
Input, |
|||
Label, |
|||
LightningInvoiceInput, |
|||
Message, |
|||
Notification, |
|||
Range, |
|||
TextArea, |
|||
Toggle |
|||
} from 'components/UI' |
|||
import { Column, Group, Element } from './helpers' |
|||
|
|||
storiesOf('Welcome', module).addWithChapters('Zap Style Guide', { |
|||
subtitle: 'Reusable components for Zap Desktop.' |
|||
const store = new Store({ |
|||
crypto: 'btc', |
|||
fiat: 'usd', |
|||
cryptoCurrencies: [ |
|||
{ |
|||
key: 'btc', |
|||
name: 'BTC' |
|||
}, |
|||
{ |
|||
key: 'bits', |
|||
name: 'bits' |
|||
}, |
|||
{ |
|||
key: 'sats', |
|||
name: 'satoshis' |
|||
} |
|||
], |
|||
fiatCurrencies: [ |
|||
{ |
|||
key: 'usd', |
|||
name: 'USD' |
|||
}, |
|||
{ |
|||
key: 'eur', |
|||
name: 'EUR' |
|||
}, |
|||
{ |
|||
key: 'gbp', |
|||
name: 'GBP' |
|||
} |
|||
] |
|||
}) |
|||
|
|||
storiesOf('Welcome', module) |
|||
.addDecorator(StateDecorator(store)) |
|||
.addWithChapters('Zap Style Guide', { |
|||
subtitle: 'Reusable components for Zap Desktop.', |
|||
info: `The Zap style guide showcases and documents our library of reusable React components. Below is a sample of
|
|||
these components. You can see more examples and full documentation of each component using the navigation on the |
|||
left. Use the Theme Picker in the bottom panel to view components in one of our alternate themes.`,
|
|||
chapters: [ |
|||
{ |
|||
sections: [ |
|||
{ |
|||
options: { |
|||
showSource: false, |
|||
allowSourceToggling: false, |
|||
showPropTables: false, |
|||
allowPropTablesToggling: false |
|||
}, |
|||
sectionFn: () => ( |
|||
<Flex> |
|||
<Column> |
|||
<Group title="Message"> |
|||
<Element> |
|||
<Message variant="error">Error message</Message> |
|||
</Element> |
|||
<Element> |
|||
<Message variant="success">Success message</Message> |
|||
</Element> |
|||
<Element> |
|||
<Message variant="warning">Warning message</Message> |
|||
</Element> |
|||
</Group> |
|||
|
|||
<Group title="Notification"> |
|||
<Element> |
|||
<Notification variant="error">Error message</Notification> |
|||
</Element> |
|||
<Element> |
|||
<Notification variant="success">Success message</Notification> |
|||
</Element> |
|||
<Element> |
|||
<Notification variant="warning">Warning message</Notification> |
|||
</Element> |
|||
</Group> |
|||
</Column> |
|||
|
|||
<Column> |
|||
<Group title="Input with Label"> |
|||
<Form> |
|||
<Label htmlFor="input">Input Label</Label> |
|||
<Input field="input" /> |
|||
</Form> |
|||
</Group> |
|||
|
|||
<Group title="TextArea with Label"> |
|||
<Form> |
|||
<Label htmlFor="input">Input Label</Label> |
|||
<TextArea field="input" /> |
|||
</Form> |
|||
</Group> |
|||
|
|||
<Group title="Lightning Invoice Input"> |
|||
<Form> |
|||
<LightningInvoiceInput |
|||
field="input" |
|||
chain="bitcoin" |
|||
network="testnet" |
|||
rows={7} |
|||
validateOnBlur |
|||
validateOnChange |
|||
/* eslint-disable max-len */ |
|||
initialValue="lntb100u1pdaxza7pp5x73t3j7xgvkzgcdvzgpdg74k4pn0uhwuxlxu9qssytjn77x7zs4qdqqcqzysxqyz5vqd20eaq5uferzgzwasu5te3pla7gv8tzk8gcdxlj7lpkygvfdwndhwtl3ezn9ltjejl3hsp36ps3z3e5pp4rzp2hgqjqql80ec3hyzucq4d9axl" |
|||
/> |
|||
</Form> |
|||
</Group> |
|||
</Column> |
|||
|
|||
<Column> |
|||
<Group title="Button"> |
|||
<Element> |
|||
<Button>Text</Button> |
|||
</Element> |
|||
<Element> |
|||
<Button size="small">Text</Button> |
|||
</Element> |
|||
</Group> |
|||
|
|||
<Group title="Secondary Button"> |
|||
<Element> |
|||
<Button variant="secondary">Text</Button> |
|||
</Element> |
|||
</Group> |
|||
|
|||
<Group title="Loading Button"> |
|||
<Element> |
|||
<Button>Send 0.1235 BTC</Button> |
|||
</Element> |
|||
<Element> |
|||
<Button processing disabled> |
|||
Send 0.1235 BTC |
|||
</Button> |
|||
</Element> |
|||
</Group> |
|||
</Column> |
|||
|
|||
<Column> |
|||
<Group title="Dropdown"> |
|||
<Element> |
|||
<Dropdown |
|||
activeKey={store.get('fiat')} |
|||
items={store.get('fiatCurrencies')} |
|||
onChange={fiat => store.set({ fiat })} |
|||
/> |
|||
</Element> |
|||
</Group> |
|||
|
|||
<Group title="Toggle"> |
|||
<Form> |
|||
<Toggle field="input" /> |
|||
</Form> |
|||
</Group> |
|||
|
|||
<Group title="Range"> |
|||
<Form> |
|||
<Range field="input" /> |
|||
</Form> |
|||
</Group> |
|||
</Column> |
|||
</Flex> |
|||
) |
|||
} |
|||
] |
|||
} |
|||
] |
|||
}) |
|||
|
@ -0,0 +1,83 @@ |
|||
import React from 'react' |
|||
import PropTypes from 'prop-types' |
|||
import { storiesOf } from '@storybook/react' |
|||
import { ThemeProvider, withTheme } from 'styled-components' |
|||
import { Box, Card, Flex } from 'rebass' |
|||
import { BackgroundDark, Text } from 'components/UI' |
|||
import { dark, light } from 'themes' |
|||
import { Column, Group, Element } from './helpers' |
|||
|
|||
const Swatch = ({ name, color }) => ( |
|||
<Element> |
|||
<Flex mb={2}> |
|||
<Card |
|||
bg={color} |
|||
width={50} |
|||
css={{ height: '50px' }} |
|||
mr={21} |
|||
borderRadius={8} |
|||
borderColor="primaryText" |
|||
border="solid 1px" |
|||
/> |
|||
<Box> |
|||
<Text fontWeight="normal">{name}</Text> |
|||
<Text>{color}</Text> |
|||
</Box> |
|||
</Flex> |
|||
</Element> |
|||
) |
|||
Swatch.propTypes = { |
|||
name: PropTypes.string, |
|||
color: PropTypes.string |
|||
} |
|||
|
|||
const Palette = withTheme(({ theme, ...rest }) => ( |
|||
<Box {...rest}> |
|||
{Object.keys(theme.palette).map(key => ( |
|||
<Swatch key={key} name={key} color={theme.palette[key]} /> |
|||
))} |
|||
</Box> |
|||
)) |
|||
|
|||
storiesOf('Welcome', module).addWithChapters('Color palette', { |
|||
subtitle: 'Colors that we use throughout the app.', |
|||
info: `This page shows our two primary colour palettes. These are used as "themes" that users can switch between
|
|||
within the app.`,
|
|||
chapters: [ |
|||
{ |
|||
sections: [ |
|||
{ |
|||
options: { |
|||
showSource: false, |
|||
allowSourceToggling: false, |
|||
showPropTables: false, |
|||
allowPropTablesToggling: false |
|||
}, |
|||
sectionFn: () => ( |
|||
<Flex> |
|||
<Column> |
|||
<Group title="Dark"> |
|||
<ThemeProvider theme={dark}> |
|||
<BackgroundDark p={3}> |
|||
<Palette /> |
|||
</BackgroundDark> |
|||
</ThemeProvider> |
|||
</Group> |
|||
</Column> |
|||
|
|||
<Column> |
|||
<Group title="Light"> |
|||
<ThemeProvider theme={light}> |
|||
<BackgroundDark p={3}> |
|||
<Palette /> |
|||
</BackgroundDark> |
|||
</ThemeProvider> |
|||
</Group> |
|||
</Column> |
|||
</Flex> |
|||
) |
|||
} |
|||
] |
|||
} |
|||
] |
|||
}) |
@ -0,0 +1,30 @@ |
|||
import React from 'react' |
|||
import { storiesOf } from '@storybook/react' |
|||
import { Countdown } from 'components/UI' |
|||
|
|||
storiesOf('Components.Countdown', module).addWithChapters('Countdown', { |
|||
chapters: [ |
|||
{ |
|||
sections: [ |
|||
{ |
|||
title: 'Default', |
|||
sectionFn: () => <Countdown date={Date.now() + 5000} /> |
|||
}, |
|||
{ |
|||
title: 'Stop after expire', |
|||
sectionFn: () => <Countdown date={Date.now() + 5000} countUpAfterExpire={false} /> |
|||
}, |
|||
{ |
|||
title: 'Custom colors', |
|||
sectionFn: () => ( |
|||
<Countdown |
|||
date={Date.now() + 5000} |
|||
colorActive="lightningOrange" |
|||
colorExpired="yellow" |
|||
/> |
|||
) |
|||
} |
|||
] |
|||
} |
|||
] |
|||
}) |
@ -0,0 +1,20 @@ |
|||
import React from 'react' |
|||
import PropTypes from 'prop-types' |
|||
import { Box } from 'rebass' |
|||
import { Bar, Heading } from 'components/UI' |
|||
|
|||
export const Column = props => <Box width={1 / 2} mr={5} {...props} /> |
|||
export const Group = ({ title, children }) => ( |
|||
<Box mb={4}> |
|||
<Heading.h3 mb={2} fontWeight="normal"> |
|||
{title} |
|||
</Heading.h3> |
|||
<Bar mb={3} /> |
|||
{children} |
|||
</Box> |
|||
) |
|||
Group.propTypes = { |
|||
title: PropTypes.string, |
|||
children: PropTypes.node |
|||
} |
|||
export const Element = props => <Box py={1} {...props} /> |
@ -1,39 +0,0 @@ |
|||
import React from 'react' |
|||
import { configure, shallow } from 'enzyme' |
|||
import Adapter from 'enzyme-adapter-react-16' |
|||
import CryptoIcon from 'components/CryptoIcon' |
|||
import SkinnyBitcoin from 'components/Icon/SkinnyBitcoin' |
|||
import Litecoin from 'components/Icon/Litecoin' |
|||
|
|||
configure({ adapter: new Adapter() }) |
|||
|
|||
const defaultProps = { |
|||
currency: 'bch', |
|||
styles: {} |
|||
} |
|||
|
|||
describe('component.CryptoIcon', () => { |
|||
describe('currency is "unknown"', () => { |
|||
const props = { ...defaultProps } |
|||
const el = shallow(<CryptoIcon {...props} />) |
|||
it('should show empty span', () => { |
|||
expect(el.html()).toEqual('<span></span>') |
|||
}) |
|||
}) |
|||
|
|||
describe('currency is "btc"', () => { |
|||
const props = { ...defaultProps, currency: 'btc' } |
|||
const el = shallow(<CryptoIcon {...props} />) |
|||
it('should show btc symbol', () => { |
|||
expect(el.find(SkinnyBitcoin)).toHaveLength(1) |
|||
}) |
|||
}) |
|||
|
|||
describe('currency is "ltc"', () => { |
|||
const props = { ...defaultProps, currency: 'ltc' } |
|||
const el = shallow(<CryptoIcon {...props} />) |
|||
it('should show ltc symbol', () => { |
|||
expect(el.find(Litecoin)).toHaveLength(1) |
|||
}) |
|||
}) |
|||
}) |