diff --git a/.storybook/config.js b/.storybook/config.js
index 42040678..8f23abf6 100644
--- a/.storybook/config.js
+++ b/.storybook/config.js
@@ -12,8 +12,7 @@ import { setIntlConfig, withIntl } from 'storybook-addon-intl'
import StoryRouter from 'storybook-react-router'
import { dark, light } from 'themes'
import { getDefaultLocale, locales } from 'lib/i18n'
-import BackgroundDark from 'components/UI/BackgroundDark'
-import GlobalStyle from 'components/UI/GlobalStyle'
+import { BackgroundDark, GlobalStyle } from 'components/UI'
// Register supported locales.
import '../app/lib/i18n/locale'
diff --git a/app/components/Activity/Activity.js b/app/components/Activity/Activity.js
index 8690434a..f1eac664 100644
--- a/app/components/Activity/Activity.js
+++ b/app/components/Activity/Activity.js
@@ -6,7 +6,7 @@ import FaRepeat from 'react-icons/lib/fa/repeat'
import { FormattedMessage, injectIntl } from 'react-intl'
import { Flex } from 'rebass'
-import Button from 'components/UI/Button'
+import { Button } from 'components/UI'
import Wallet from 'components/Wallet'
import Invoice from './Invoice'
import Payment from './Payment'
diff --git a/app/components/Activity/InvoiceModal/InvoiceModal.js b/app/components/Activity/InvoiceModal/InvoiceModal.js
index 354aa276..35a162ec 100644
--- a/app/components/Activity/InvoiceModal/InvoiceModal.js
+++ b/app/components/Activity/InvoiceModal/InvoiceModal.js
@@ -4,7 +4,7 @@ import QRCode from 'qrcode.react'
import copy from 'copy-to-clipboard'
import { showNotification } from 'lib/utils/notifications'
import Value from 'components/Value'
-import Dropdown from 'components/UI/Dropdown'
+import { Dropdown } from 'components/UI'
import { FormattedDate, FormattedTime, FormattedMessage } from 'react-intl'
import Countdown from '../Countdown'
import messages from './messages'
diff --git a/app/components/Activity/PaymentModal/PaymentModal.js b/app/components/Activity/PaymentModal/PaymentModal.js
index 3f214056..072a8a9b 100644
--- a/app/components/Activity/PaymentModal/PaymentModal.js
+++ b/app/components/Activity/PaymentModal/PaymentModal.js
@@ -1,8 +1,8 @@
import React from 'react'
import PropTypes from 'prop-types'
-import Dropdown from 'components/UI/Dropdown'
import PaperPlane from 'components/Icon/PaperPlane'
import Zap from 'components/Icon/Zap'
+import { Dropdown } from 'components/UI'
import Value from 'components/Value'
import { FormattedDate, FormattedTime, FormattedMessage } from 'react-intl'
import messages from './messages'
diff --git a/app/components/Activity/TransactionModal/TransactionModal.js b/app/components/Activity/TransactionModal/TransactionModal.js
index 70f10274..71bd0c93 100644
--- a/app/components/Activity/TransactionModal/TransactionModal.js
+++ b/app/components/Activity/TransactionModal/TransactionModal.js
@@ -1,16 +1,13 @@
import React from 'react'
import PropTypes from 'prop-types'
-import Dropdown from 'components/UI/Dropdown'
import PaperPlane from 'components/Icon/PaperPlane'
import Hand from 'components/Icon/Hand'
import ChainLink from 'components/Icon/ChainLink'
+import { Dropdown } from 'components/UI'
import { blockExplorer } from 'lib/utils'
-
import Value from 'components/Value'
-
import { FormattedDate, FormattedTime, FormattedMessage } from 'react-intl'
import messages from './messages'
-
import styles from './TransactionModal.scss'
const TransactionModal = ({
diff --git a/app/components/Contacts/AddChannel/AddChannel.js b/app/components/Contacts/AddChannel/AddChannel.js
index 18b7ab31..c8a7fd63 100644
--- a/app/components/Contacts/AddChannel/AddChannel.js
+++ b/app/components/Contacts/AddChannel/AddChannel.js
@@ -4,7 +4,7 @@ import PropTypes from 'prop-types'
import X from 'components/Icon/X'
import { FormattedMessage } from 'react-intl'
-import Button from 'components/UI/Button'
+import { Button } from 'components/UI'
import messages from './messages'
import styles from './AddChannel.scss'
diff --git a/app/components/Contacts/ConnectManually/ConnectManually.js b/app/components/Contacts/ConnectManually/ConnectManually.js
index 1352ca49..76bed320 100644
--- a/app/components/Contacts/ConnectManually/ConnectManually.js
+++ b/app/components/Contacts/ConnectManually/ConnectManually.js
@@ -2,7 +2,7 @@ import React from 'react'
import PropTypes from 'prop-types'
import { FormattedMessage, injectIntl } from 'react-intl'
-import Button from 'components/UI/Button'
+import { Button } from 'components/UI'
import messages from './messages'
import styles from './ConnectManually.scss'
diff --git a/app/components/Contacts/SubmitChannelForm/SubmitChannelForm.js b/app/components/Contacts/SubmitChannelForm/SubmitChannelForm.js
index c35fabb2..a3bc7da4 100644
--- a/app/components/Contacts/SubmitChannelForm/SubmitChannelForm.js
+++ b/app/components/Contacts/SubmitChannelForm/SubmitChannelForm.js
@@ -1,15 +1,10 @@
import React from 'react'
import PropTypes from 'prop-types'
-
import FaExclamationCircle from 'react-icons/lib/fa/exclamation-circle'
-
import AmountInput from 'components/AmountInput'
-import Button from 'components/UI/Button'
-import Dropdown from 'components/UI/Dropdown'
-
+import { Button, Dropdown } from 'components/UI'
import { FormattedNumber, FormattedMessage } from 'react-intl'
import messages from './messages'
-
import styles from './SubmitChannelForm.scss'
class SubmitChannelForm extends React.Component {
diff --git a/app/components/Form/Pay/Pay.js b/app/components/Form/Pay/Pay.js
index fca63e4f..cf3013b6 100644
--- a/app/components/Form/Pay/Pay.js
+++ b/app/components/Form/Pay/Pay.js
@@ -1,14 +1,10 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
-
+import { btc } from 'lib/utils'
import PaperPlane from 'components/Icon/PaperPlane'
import ChainLink from 'components/Icon/ChainLink'
-
-import { btc } from 'lib/utils'
import AmountInput from 'components/AmountInput'
-import Button from 'components/UI/Button'
-import Dropdown from 'components/UI/Dropdown'
-
+import { Button, Dropdown } from 'components/UI'
import { FormattedNumber, FormattedMessage, injectIntl } from 'react-intl'
import messages from './messages'
diff --git a/app/components/Form/Request/Request.js b/app/components/Form/Request/Request.js
index 71c1fc06..93501d7a 100644
--- a/app/components/Form/Request/Request.js
+++ b/app/components/Form/Request/Request.js
@@ -1,16 +1,11 @@
import React from 'react'
import PropTypes from 'prop-types'
-
-import Hand from 'components/Icon/Hand'
-
import { btc } from 'lib/utils'
+import Hand from 'components/Icon/Hand'
import AmountInput from 'components/AmountInput'
-import Button from 'components/UI/Button'
-import Dropdown from 'components/UI/Dropdown'
-
+import { Button, Dropdown } from 'components/UI'
import { FormattedNumber, FormattedMessage, injectIntl } from 'react-intl'
import messages from './messages'
-
import styles from './Request.scss'
const Request = ({
diff --git a/app/components/GlobalError/GlobalError.js b/app/components/GlobalError/GlobalError.js
index 185d9eac..e49c8a9e 100644
--- a/app/components/GlobalError/GlobalError.js
+++ b/app/components/GlobalError/GlobalError.js
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types'
import { animated, Transition } from 'react-spring'
import { Box } from 'rebass'
import errorToUserFriendly from 'lib/utils/userFriendlyErrors'
-import Notification from 'components/UI/Notification'
+import { Notification } from 'components/UI'
class GlobalError extends React.Component {
static propTypes = {
diff --git a/app/components/Onboarding/FormContainer/FormContainer.js b/app/components/Onboarding/FormContainer/FormContainer.js
index baf89a95..f37c672f 100644
--- a/app/components/Onboarding/FormContainer/FormContainer.js
+++ b/app/components/Onboarding/FormContainer/FormContainer.js
@@ -6,7 +6,7 @@ import FaAngleLeft from 'react-icons/lib/fa/angle-left'
import FaAngleRight from 'react-icons/lib/fa/angle-right'
import ZapLogo from 'components/Icon/ZapLogo'
import ZapLogoBlack from 'components/Icon/ZapLogoBlack'
-import Button from 'components/UI/Button'
+import { Button } from 'components/UI'
import messages from './messages'
import styles from './FormContainer.scss'
diff --git a/app/components/Onboarding/Login/Login.js b/app/components/Onboarding/Login/Login.js
index 4461064f..db1c13b6 100644
--- a/app/components/Onboarding/Login/Login.js
+++ b/app/components/Onboarding/Login/Login.js
@@ -1,7 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
import { FormattedMessage, injectIntl } from 'react-intl'
-import Button from 'components/UI/Button'
+import { Button } from 'components/UI'
import messages from './messages'
import styles from './Login.scss'
diff --git a/app/components/UI/GlobalStyle.js b/app/components/UI/GlobalStyle.js
index 1e67fdd4..e38da917 100644
--- a/app/components/UI/GlobalStyle.js
+++ b/app/components/UI/GlobalStyle.js
@@ -9,7 +9,6 @@ const GlobalStyle = createGlobalStyle`
}
body {
position: relative;
- box-sizing: border-box;
overflow-y: hidden;
-webkit-font-smoothing: antialiased;
-webkit-tap-highlight-color: rgba(255, 255, 255, 0);
diff --git a/app/components/UI/Input.js b/app/components/UI/Input.js
index 003cbc61..8321e282 100644
--- a/app/components/UI/Input.js
+++ b/app/components/UI/Input.js
@@ -1,9 +1,28 @@
import React from 'react'
import { asField } from 'informed'
-import { Input as Base } from 'styled-system-html'
import { withTheme } from 'styled-components'
+import system from '@rebass/components'
+import { styles } from 'styled-system'
import { Flex } from 'rebass'
-import FormFieldMessage from 'components/UI/FormFieldMessage'
+import { FormFieldMessage } from 'components/UI'
+
+// Create an html input element that accepts all style props from styled-system.
+const SystemInput = system(
+ {
+ as: 'input',
+ border: 1,
+ borderColor: 'gray',
+ borderRadius: 5,
+ bg: 'transparent',
+ color: 'primaryText',
+ fontFamily: 'sans',
+ fontSize: 'm',
+ fontWeight: 'light',
+ p: 3,
+ width: 1
+ },
+ ...Object.keys(styles)
+)
/**
* @render react
@@ -11,15 +30,26 @@ import FormFieldMessage from 'components/UI/FormFieldMessage'
* @example
*
*/
-class Input extends React.PureComponent {
+class Input extends React.Component {
static displayName = 'Input'
+ state = {
+ hasFocus: false
+ }
+
+ constructor(props) {
+ super(props)
+ const { forwardedRef } = this.props
+ this.inputRef = forwardedRef || React.createRef()
+ }
+
render() {
const {
+ css,
onChange,
onBlur,
+ onFocus,
initialValue,
- field,
forwardedRef,
theme,
fieldApi,
@@ -27,42 +57,40 @@ class Input extends React.PureComponent {
justifyContent,
...rest
} = this.props
+ const { readOnly } = this.props
+ const { hasFocus } = this.state
const { setValue, setTouched } = fieldApi
const { value } = fieldState
+ const isValid = value && !fieldState.error
+ // Calculate the border color based on the current field state.
let borderColor
-
- if (fieldState.touched) {
- if (fieldState.error) {
- borderColor = theme.colors.superRed
- } else if (value && !fieldState.error) {
- borderColor = theme.colors.superGreen
- }
+ if (readOnly) {
+ borderColor = theme.colors.gray
+ } else if (fieldState.error) {
+ borderColor = theme.colors.superRed
+ } else if (value && !fieldState.error) {
+ borderColor = theme.colors.superGreen
}
return (
- {
setValue(e.target.value)
if (onChange) {
@@ -71,14 +99,33 @@ class Input extends React.PureComponent {
}}
onBlur={e => {
setTouched()
+ // Make the state aware that the element is now focused.
+ const newHasFocus = document.activeElement === this.inputRef.current
+ if (hasFocus !== newHasFocus) {
+ this.setState({ hasFocus: newHasFocus })
+ }
if (onBlur) {
onBlur(e)
}
}}
+ onFocus={e => {
+ // Make the state aware that the element is no longer focused.
+ const newHasFocus = document.activeElement === this.inputRef.current
+ if (hasFocus !== newHasFocus) {
+ this.setState({ hasFocus: newHasFocus })
+ }
+ if (onFocus) {
+ onFocus(e)
+ }
+ }}
error={fieldState.error}
/>
{fieldState.error && (
-
+
{fieldState.error}
)}
diff --git a/app/components/UI/Label.js b/app/components/UI/Label.js
index 33f8c999..a375a24e 100644
--- a/app/components/UI/Label.js
+++ b/app/components/UI/Label.js
@@ -1,5 +1,20 @@
import React from 'react'
-import { Label as Base } from 'styled-system-html'
+import system from '@rebass/components'
+import { styles } from 'styled-system'
+
+// Create an html input element that accepts all style props from styled-system.
+const SystemLabel = system(
+ {
+ as: 'label',
+ display: 'block',
+ color: 'primaryText',
+ fontSize: 'm',
+ fontWeight: 'normal',
+ width: 1,
+ mb: 1
+ },
+ ...Object.keys(styles)
+)
/**
* @render react
@@ -7,13 +22,12 @@ import { Label as Base } from 'styled-system-html'
* @example
*
*/
-class Label extends React.Component {
+class Label extends React.PureComponent {
static displayName = 'Label'
render() {
- return (
-
- )
+ const { readOnly } = this.props
+ return
}
}
diff --git a/app/components/UI/LightningInvoiceInput.js b/app/components/UI/LightningInvoiceInput.js
new file mode 100644
index 00000000..a5057ff1
--- /dev/null
+++ b/app/components/UI/LightningInvoiceInput.js
@@ -0,0 +1,71 @@
+import React from 'react'
+import PropTypes from 'prop-types'
+import { asField } from 'informed'
+import { isOnchain, isLn } from 'lib/utils/crypto'
+import TextArea from 'components/UI/TextArea'
+import FormFieldMessage from 'components/UI/FormFieldMessage'
+
+/**
+ * @render react
+ * @name LightningInvoiceInput
+ * @example
+ *
+ */
+class LightningInvoiceInput extends React.Component {
+ static displayName = 'LightningInvoiceInput'
+
+ static propTypes = {
+ required: PropTypes.bool,
+ chain: PropTypes.oneOf(['bitcoin', 'litecoin']),
+ network: PropTypes.oneOf(['mainnet', 'testnet', 'regtest'])
+ }
+
+ static defaultProps = {
+ required: false
+ }
+
+ validate = value => {
+ const { network, chain, required } = this.props
+ if (required && (!value || value.trim() === '')) {
+ return 'This is a required field'
+ }
+ if (value && !isLn(value, chain, network) && !isOnchain(value, chain, network)) {
+ return 'Not a valid address.'
+ }
+ }
+
+ render() {
+ return (
+
+ )
+ }
+}
+
+const InformedTextArea = asField(({ fieldState, fieldApi, ...props }) => {
+ const { value } = fieldState
+ const { chain, network, ...rest } = props
+ return (
+
+
+ {value &&
+ !fieldState.error && (
+
+ Valid {isLn(value, chain, network) ? 'lightning' : chain} address{' '}
+ {network !== 'mainnet' && `(${network})`}
+
+ )}
+
+ )
+})
+
+export default LightningInvoiceInput
diff --git a/app/components/UI/MainContent.js b/app/components/UI/MainContent.js
index 10734d72..4350c57f 100644
--- a/app/components/UI/MainContent.js
+++ b/app/components/UI/MainContent.js
@@ -1,5 +1,5 @@
import React from 'react'
-import BackgroundDark from 'components/UI/BackgroundDark'
+import { BackgroundDark } from 'components/UI'
/**
* @render react
diff --git a/app/components/UI/Modal.js b/app/components/UI/Modal.js
index 7726c312..16de8f6b 100644
--- a/app/components/UI/Modal.js
+++ b/app/components/UI/Modal.js
@@ -1,9 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
-
import { Box, Flex } from 'rebass'
-
-import BackgroundDark from 'components/UI/BackgroundDark'
+import { BackgroundDark } from 'components/UI'
import X from 'components/Icon/X'
/**
diff --git a/app/components/UI/Select.js b/app/components/UI/Select.js
index 7dcdeefd..c0f8f577 100644
--- a/app/components/UI/Select.js
+++ b/app/components/UI/Select.js
@@ -1,7 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
import { asField } from 'informed'
-import Input from 'components/UI/Input'
+import { Input } from 'components/UI'
import styled, { withTheme } from 'styled-components'
import Downshift from 'downshift'
import { Box } from 'rebass'
diff --git a/app/components/UI/Sidebar.js b/app/components/UI/Sidebar.js
index 737730f3..7abe9278 100644
--- a/app/components/UI/Sidebar.js
+++ b/app/components/UI/Sidebar.js
@@ -1,5 +1,5 @@
import React from 'react'
-import BackgroundLightest from 'components/UI/BackgroundLightest'
+import { BackgroundLightest } from 'components/UI'
/**
* @render react
diff --git a/app/components/UI/TextArea.js b/app/components/UI/TextArea.js
index 4b1686d7..0675026d 100644
--- a/app/components/UI/TextArea.js
+++ b/app/components/UI/TextArea.js
@@ -1,9 +1,29 @@
import React from 'react'
import { asField } from 'informed'
-import { TextArea as Base } from 'styled-system-html'
+import system from '@rebass/components'
+import { styles } from 'styled-system'
import { withTheme } from 'styled-components'
import { Flex } from 'rebass'
-import FormFieldMessage from 'components/UI/FormFieldMessage'
+import { FormFieldMessage } from 'components/UI'
+
+// Create an html textarea element that accepts all style props from styled-system.
+const SystemTextArea = system(
+ {
+ as: 'textarea',
+ border: 1,
+ borderColor: 'gray',
+ borderRadius: '5px',
+ bg: 'transparent',
+ color: 'primaryText',
+ fontFamily: 'sans',
+ fontSize: 'm',
+ fontWeight: 'light',
+ p: 3,
+ width: 1,
+ rows: 5
+ },
+ ...Object.keys(styles)
+)
/**
* @render react
@@ -14,10 +34,22 @@ import FormFieldMessage from 'components/UI/FormFieldMessage'
class TextArea extends React.PureComponent {
static displayName = 'TextArea'
+ state = {
+ hasFocus: false
+ }
+
+ constructor(props) {
+ super(props)
+ const { forwardedRef } = this.props
+ this.inputRef = forwardedRef || React.createRef()
+ }
+
render() {
const {
+ css,
onChange,
onBlur,
+ onFocus,
initialValue,
forwardedRef,
theme,
@@ -26,11 +58,17 @@ class TextArea extends React.PureComponent {
justifyContent,
...rest
} = this.props
+ const { readOnly } = this.props
+ const { hasFocus } = this.state
const { setValue, setTouched } = fieldApi
const { value } = fieldState
+ const isValid = value && !fieldState.error
+ // Calculate the border color based on the current field state.
let borderColor
- if (fieldState.error) {
+ if (readOnly) {
+ borderColor = theme.colors.gray
+ } else if (fieldState.error) {
borderColor = theme.colors.superRed
} else if (value && !fieldState.error) {
borderColor = theme.colors.superGreen
@@ -38,27 +76,23 @@ class TextArea extends React.PureComponent {
return (
- {
setValue(e.target.value)
if (onChange) {
@@ -67,14 +101,33 @@ class TextArea extends React.PureComponent {
}}
onBlur={e => {
setTouched()
+ // Make the state aware that the element is now focused.
+ const newHasFocus = document.activeElement === this.inputRef.current
+ if (hasFocus !== newHasFocus) {
+ this.setState({ hasFocus: newHasFocus })
+ }
if (onBlur) {
onBlur(e)
}
}}
+ onFocus={e => {
+ // Make the state aware that the element is no longer focused.
+ const newHasFocus = document.activeElement === this.inputRef.current
+ if (hasFocus !== newHasFocus) {
+ this.setState({ hasFocus: newHasFocus })
+ }
+ if (onFocus) {
+ onFocus(e)
+ }
+ }}
error={fieldState.error}
/>
{fieldState.error && (
-
+
{fieldState.error}
)}
diff --git a/app/components/UI/index.js b/app/components/UI/index.js
new file mode 100644
index 00000000..c1539a35
--- /dev/null
+++ b/app/components/UI/index.js
@@ -0,0 +1,27 @@
+export BackgroundDark from './BackgroundDark'
+export BackgroundLight from './BackgroundLight'
+export BackgroundLightest from './BackgroundLightest'
+export Bar from './Bar'
+export Button from './Button'
+export Dropdown from './Dropdown'
+export FormFieldMessage from './FormFieldMessage'
+export GlobalStyle from './GlobalStyle'
+export Heading from './Heading'
+export Input from './Input'
+export Label from './Label'
+export LightningInvoiceInput from './LightningInvoiceInput'
+export MainContent from './MainContent'
+export Menu from './Menu'
+export MenuItem from './MenuItem'
+export MenuItemGroup from './MenuItemGroup'
+export Modal from './Modal'
+export Notification from './Notification'
+export Page from './Page'
+export Range from './Range'
+export Select from './Select'
+export Sidebar from './Sidebar'
+export Spinner from './Spinner'
+export Text from './Text'
+export TextArea from './TextArea'
+export Titlebar from './Titlebar'
+export Toggle from './Toggle'
diff --git a/app/components/Wallet/Wallet.js b/app/components/Wallet/Wallet.js
index 37ad0d8f..f5aa3402 100644
--- a/app/components/Wallet/Wallet.js
+++ b/app/components/Wallet/Wallet.js
@@ -6,10 +6,7 @@ import { Box, Flex } from 'rebass'
import { btc, blockExplorer } from 'lib/utils'
import Value from 'components/Value'
import Settings from 'components/Settings'
-import Button from 'components/UI/Button'
-import Text from 'components/UI/Text'
-import Dropdown from 'components/UI/Dropdown'
-
+import { Button, Dropdown, Text } from 'components/UI'
import CheckAnimated from 'components/Icon/CheckAnimated'
import ZapLogo from 'components/Icon/ZapLogo'
import ZapLogoBlack from 'components/Icon/ZapLogoBlack'
diff --git a/app/containers/Root.js b/app/containers/Root.js
index 3429d789..f33fad03 100644
--- a/app/containers/Root.js
+++ b/app/containers/Root.js
@@ -10,12 +10,9 @@ import { loadingSelectors, setLoading, setMounted } from 'reducers/loading'
import { initCurrency, initLocale } from 'reducers/locale'
import { initTheme, themeSelectors } from 'reducers/theme'
-import Page from 'components/UI/Page'
-import Titlebar from 'components/UI/Titlebar'
+import { Page, Titlebar, GlobalStyle } from 'components/UI'
import GlobalError from 'components/GlobalError'
-import GlobalStyle from 'components/UI/GlobalStyle'
import withLoading from 'components/withLoading'
-
import Onboarding from './Onboarding'
import Syncing from './Syncing'
import App from './App'
diff --git a/app/lib/utils/crypto.js b/app/lib/utils/crypto.js
new file mode 100644
index 00000000..63e16532
--- /dev/null
+++ b/app/lib/utils/crypto.js
@@ -0,0 +1,73 @@
+import bitcoin from 'bitcoinjs-lib'
+import bech32 from 'lib/utils/bech32'
+
+/**
+ * Test to see if a string is a valid on-chain address.
+ * @param {String} input string to check.
+ * @param {String} [network='mainnet'] network to check (mainnet, testnet).
+ * @return {Boolean} boolean indicating wether the address is a valid on-chain address.
+ */
+export const isOnchain = (input, chain = 'bitcoin', network = 'mainnet') => {
+ if (chain !== 'bitcoin') {
+ // TODO: Implement address checking for litecoin.
+ return true
+ }
+ try {
+ bitcoin.address.toOutputScript(
+ input,
+ network === 'mainnet' ? bitcoin.networks.bitcoin : bitcoin.networks.testnet
+ )
+ return true
+ } catch (e) {
+ return false
+ }
+}
+
+/**
+ * Test to see if a string is a valid lightning address.
+ * @param {String} input string to check.
+ * @param {String} [network='bitcoin'] chain to check (bitcoin, litecoin).
+ * @param {String} [network='mainnet'] network to check (mainnet, testnet, regtest).
+ * @return {Boolean} boolean indicating wether the address is a lightning address.
+ */
+export const isLn = (input, chain = 'bitcoin', network = 'mainnet') => {
+ let prefix = 'ln'
+ // Prefixes come from SLIP-0173
+ // See https://github.com/satoshilabs/slips/blob/master/slip-0173.md
+ if (chain === 'bitcoin') {
+ switch (network) {
+ case 'mainnet':
+ prefix = 'lnbc'
+ break
+ case 'testnet':
+ prefix = 'lntb'
+ break
+ case 'regtest':
+ prefix = 'lnbcrt'
+ break
+ }
+ } else if (chain === 'litecoin') {
+ switch (network) {
+ case 'mainnet':
+ prefix = 'lnltc'
+ break
+ case 'testnet':
+ prefix = 'lntltc'
+ break
+ case 'regtest':
+ prefix = 'lnrltc'
+ break
+ }
+ }
+
+ if (!input.startsWith(prefix)) {
+ return false
+ }
+
+ try {
+ bech32.decode(input)
+ return true
+ } catch (e) {
+ return false
+ }
+}
diff --git a/package.json b/package.json
index a1989584..ff3cd880 100644
--- a/package.json
+++ b/package.json
@@ -349,7 +349,6 @@
"source-map-support": "^0.5.9",
"split2": "^3.0.0",
"styled-components": "^4.0.3",
- "styled-system-html": "^2.0.2",
"tildify": "^1.2.0",
"untildify": "^3.0.3",
"validator": "^10.8.0"
diff --git a/stories/components/background.stories.js b/stories/components/background.stories.js
index edc940a9..283e03dd 100644
--- a/stories/components/background.stories.js
+++ b/stories/components/background.stories.js
@@ -1,8 +1,6 @@
import React from 'react'
import { storiesOf } from '@storybook/react'
-import BackgroundDark from 'components/UI/BackgroundDark'
-import BackgroundLight from 'components/UI/BackgroundLight'
-import BackgroundLightest from 'components/UI/BackgroundLightest'
+import { BackgroundDark, BackgroundLight, BackgroundLightest } from 'components/UI'
storiesOf('Components.Background', module)
.add('dark', () => (
diff --git a/stories/components/bar.stories.js b/stories/components/bar.stories.js
index 07f240d9..2563d543 100644
--- a/stories/components/bar.stories.js
+++ b/stories/components/bar.stories.js
@@ -1,5 +1,5 @@
import React from 'react'
import { storiesOf } from '@storybook/react'
-import Bar from 'components/UI/Bar'
+import { Bar } from 'components/UI'
storiesOf('Components.Bar', module).add('bar', () => )
diff --git a/stories/components/button.stories.js b/stories/components/button.stories.js
index e2d4ba8d..01b262e5 100644
--- a/stories/components/button.stories.js
+++ b/stories/components/button.stories.js
@@ -1,7 +1,7 @@
import React from 'react'
import { storiesOf } from '@storybook/react'
import { action } from '@storybook/addon-actions'
-import Button from 'components/UI/Button'
+import { Button } from 'components/UI'
import SystemNavPrevious from 'components/Icon/SystemNavPrevious'
import SystemNavNext from 'components/Icon/SystemNavNext'
diff --git a/stories/components/dropdown.stories.js b/stories/components/dropdown.stories.js
index e30c0ce9..86b76f27 100644
--- a/stories/components/dropdown.stories.js
+++ b/stories/components/dropdown.stories.js
@@ -1,7 +1,7 @@
import React from 'react'
import { storiesOf } from '@storybook/react'
import { StateDecorator, Store } from '@sambego/storybook-state'
-import Dropdown from 'components/UI/Dropdown'
+import { Dropdown } from 'components/UI'
const store = new Store({
crypto: 'btc',
diff --git a/stories/components/form.stories.js b/stories/components/form.stories.js
index 325299dd..ad7a3573 100644
--- a/stories/components/form.stories.js
+++ b/stories/components/form.stories.js
@@ -2,16 +2,19 @@ import React from 'react'
import { storiesOf } from '@storybook/react'
import { action } from '@storybook/addon-actions'
import { Box } from 'rebass'
-import Page from 'components/UI/Page'
-import MainContent from 'components/UI/MainContent'
-import Input from 'components/UI/Input'
-import Label from 'components/UI/Label'
-import Select from 'components/UI/Select'
-import TextArea from 'components/UI/TextArea'
-import Button from 'components/UI/Button'
-import Toggle from 'components/UI/Toggle'
-import Range from 'components/UI/Range'
import { Form } from 'informed'
+import {
+ Page,
+ MainContent,
+ Input,
+ Label,
+ LightningInvoiceInput,
+ Select,
+ TextArea,
+ Button,
+ Toggle,
+ Range
+} from 'components/UI'
const validate = value => {
return !value || value.length < 5 ? 'Field must be at least five characters' : null
@@ -42,6 +45,40 @@ storiesOf('Components.Form', module)
))
+ .add('Lightning Invoice Textarea', () => (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ))
.add('Select', () => (