diff --git a/app/components/UI/DataRow.js b/app/components/UI/DataRow.js index 77c62f14..ae02f481 100644 --- a/app/components/UI/DataRow.js +++ b/app/components/UI/DataRow.js @@ -3,12 +3,12 @@ import PropTypes from 'prop-types' import { Flex } from 'rebass' import { Text } from 'components/UI' -const DataRow = ({ left, right }) => ( - - +const DataRow = ({ left, right, ...rest }) => ( + + {left} - + {right} diff --git a/app/components/UI/GlobalStyle.js b/app/components/UI/GlobalStyle.js index 8e27fc07..6081ddb0 100644 --- a/app/components/UI/GlobalStyle.js +++ b/app/components/UI/GlobalStyle.js @@ -41,6 +41,15 @@ const GlobalStyle = createGlobalStyle` font-size: 13px; } + a { + text-decoration: none; + color: inherit; + } + + pre { + font-family: "Lucida Console", Monaco, monospace; + } + #root { height: 100%; } diff --git a/app/components/UI/Header.js b/app/components/UI/Header.js index 535e6016..e53f4068 100644 --- a/app/components/UI/Header.js +++ b/app/components/UI/Header.js @@ -3,23 +3,29 @@ import PropTypes from 'prop-types' import { Flex } from 'rebass' import { Heading, Text } from 'components/UI' -const Header = ({ title, subtitle, logo }) => ( - +const Header = ({ title, subtitle, align, logo }) => ( + {logo && ( - - - {logo} - - + + {logo} + )} - {title && {title}} - {subtitle && {subtitle}} + {title && ( + + {title} + + )} + {subtitle && {subtitle}} ) Header.propTypes = { title: PropTypes.node, subtitle: PropTypes.node, - logo: PropTypes.node + logo: PropTypes.node, + align: PropTypes.string +} +Header.defaultProps = { + align: 'center' } export default Header diff --git a/app/components/UI/Heading.js b/app/components/UI/Heading.js index cf82c2ce..0d235008 100644 --- a/app/components/UI/Heading.js +++ b/app/components/UI/Heading.js @@ -14,7 +14,7 @@ class Heading extends React.PureComponent { return ( { + if ([...Object.keys(styles.space.propTypes), 'width'].includes(key)) { + spaceProps[key] = rest[key] + delete rest[key] + } + }) + const { readOnly } = this.props const { hasFocus } = this.state const { setValue, setTouched } = fieldApi @@ -66,15 +100,28 @@ class Input extends React.Component { let borderColor if (readOnly) { borderColor = theme.colors.gray - } else if (fieldState.error) { + } else if (fieldState.error || fieldState.asyncError) { borderColor = theme.colors.superRed - } else if (value && !fieldState.error) { + } else if (value && (!fieldState.error && !fieldState.asyncError)) { borderColor = theme.colors.superGreen } return ( - + + {label && ( + + )} { @@ -117,16 +165,51 @@ class Input extends React.Component { onFocus(e) } }} - error={fieldState.error} + required={required} /> - {fieldState.error && ( - - {fieldState.error} - + {description && ( + + {description} + )} + {showMessage && + (fieldState.error || fieldState.asyncError) && ( + + {fieldState.error || fieldState.asyncError} + + )} ) } } -export default asField(withTheme(Input)) +const InputAsField = asField(Input) + +class WrappedInputAsField extends React.Component { + validate = value => { + const { disabled, required } = this.props + if (disabled) { + return + } + try { + if (required) { + const validator = yup.string().required() + validator.validateSync(value) + } + } catch (error) { + return error.message + } + + // Run any additional validation provided by the caller. + const { validate } = this.props + if (validate) { + return validate(value) + } + } + + render() { + return + } +} + +export default withTheme(WrappedInputAsField) diff --git a/app/components/UI/Label.js b/app/components/UI/Label.js index a375a24e..66885bd6 100644 --- a/app/components/UI/Label.js +++ b/app/components/UI/Label.js @@ -10,7 +10,6 @@ const SystemLabel = system( color: 'primaryText', fontSize: 'm', fontWeight: 'normal', - width: 1, mb: 1 }, ...Object.keys(styles) diff --git a/app/components/UI/MainContent.js b/app/components/UI/MainContent.js index d7ec0fa5..c8f92cf0 100644 --- a/app/components/UI/MainContent.js +++ b/app/components/UI/MainContent.js @@ -7,6 +7,19 @@ import { BackgroundPrimary } from 'components/UI' * @example * Some content */ -const MainContent = props => +const MainContent = ({ css, ...rest }) => ( + +) export default MainContent diff --git a/app/components/UI/Message.js b/app/components/UI/Message.js index ce4189d9..28204d35 100644 --- a/app/components/UI/Message.js +++ b/app/components/UI/Message.js @@ -25,19 +25,26 @@ class Message extends React.Component { children: PropTypes.node } + renderIcon = () => { + const { variant } = this.props + return ( + + {variant === 'success' && } + {variant === 'warning' && } + {variant === 'error' && } + + ) + } + render() { const { children, variant, ...rest } = this.props return ( - - - {variant === 'success' && } - {variant === 'warning' && } - {variant === 'error' && } - - + + + {this.renderIcon()} {children} - - + + ) } } diff --git a/app/components/UI/Modal.js b/app/components/UI/Modal.js index 770eed1c..40ae7fab 100644 --- a/app/components/UI/Modal.js +++ b/app/components/UI/Modal.js @@ -2,6 +2,8 @@ import React from 'react' import PropTypes from 'prop-types' import { Box, Flex } from 'rebass' import X from 'components/Icon/X' +import ZapLogo from 'components/Icon/ZapLogo' +import { Panel, Text } from 'components/UI' /** * @render react @@ -14,42 +16,50 @@ class Modal extends React.Component { static propTypes = { children: PropTypes.node, - onClose: PropTypes.func + onClose: PropTypes.func, + withClose: PropTypes.bool, + withHeader: PropTypes.bool } - state = { - hover: false - } - - hoverOn = () => { - this.setState({ hover: true }) - } - - hoverOff = () => { - this.setState({ hover: false }) + static defaultProps = { + withClose: true, + withHeader: false } render() { - const { hover } = this.state - const { children, onClose } = this.props + const { children, onClose, withClose, withHeader, ...rest } = this.props return ( - - - - - - - + + + + + {withClose && } + + + {withHeader && ( + + + + + + Need Help? + + + )} + + + {' '} {children} - - + + ) } } diff --git a/app/components/UI/Page.js b/app/components/UI/Page.js index e686645a..9880f8ae 100644 --- a/app/components/UI/Page.js +++ b/app/components/UI/Page.js @@ -14,9 +14,9 @@ const Page = ({ css, ...rest }) => ( css={Object.assign( { height: '100%', + overflow: 'hidden', 'min-width': '950px', 'min-height': '600px', - 'overflow-y': 'hidden', 'box-shadow': '0 20px 70px rgba(0, 0, 0, 0.55)' }, css diff --git a/app/components/UI/Panel.js b/app/components/UI/Panel.js index fbe6c9be..ae292e98 100644 --- a/app/components/UI/Panel.js +++ b/app/components/UI/Panel.js @@ -9,8 +9,17 @@ const PanelHeader = ({ children, ...rest }) => ( ) PanelHeader.propTypes = { children: PropTypes.node } -const PanelBody = ({ children, ...rest }) => ( - +const PanelBody = ({ children, css, ...rest }) => ( + {children} ) diff --git a/app/components/UI/PasswordInput.js b/app/components/UI/PasswordInput.js new file mode 100644 index 00000000..35288730 --- /dev/null +++ b/app/components/UI/PasswordInput.js @@ -0,0 +1,48 @@ +/* eslint-disable react/no-multi-comp */ + +import React from 'react' +import { asField } from 'informed' +import * as yup from 'yup' +import Input from 'components/UI/Input' + +/** + * @render react + * @name PasswordInput + */ +class PasswordInput extends React.Component { + render() { + return + } +} + +const PasswordInputAsField = asField(PasswordInput) + +class WrappedPasswordInputAsField extends React.Component { + validate = value => { + const { disabled, required } = this.props + if (disabled) { + return + } + try { + let validator = yup.string().min(8) + if (required) { + validator = validator.required() + } + validator.validateSync(value) + } catch (error) { + return error.message + } + + // Run any additional validation provided by the caller. + const { validate } = this.props + if (validate) { + return validate(value) + } + } + + render() { + return + } +} + +export default WrappedPasswordInputAsField diff --git a/app/components/UI/Sidebar.js b/app/components/UI/Sidebar.js index 0a6be03d..c498949e 100644 --- a/app/components/UI/Sidebar.js +++ b/app/components/UI/Sidebar.js @@ -7,7 +7,20 @@ import { BackgroundTertiary } from 'components/UI' * @example * Some content */ -const Sidebar = props => +const Sidebar = ({ css, ...rest }) => ( + +) Sidebar.small = props => Sidebar.medium = props => diff --git a/app/components/UI/Span.js b/app/components/UI/Span.js new file mode 100644 index 00000000..8b02ffaa --- /dev/null +++ b/app/components/UI/Span.js @@ -0,0 +1,13 @@ +import system from '@rebass/components' + +const Span = system( + { + as: 'span' + }, + 'space', + 'color', + 'fontSize', + 'fontWeight' +) + +export default Span diff --git a/app/components/UI/TextArea.js b/app/components/UI/TextArea.js index 88319eba..0ed050ef 100644 --- a/app/components/UI/TextArea.js +++ b/app/components/UI/TextArea.js @@ -4,7 +4,7 @@ import system from '@rebass/components' import { styles } from 'styled-system' import { withTheme } from 'styled-components' import { Flex } from 'rebass' -import { Message } from 'components/UI' +import { Message, Label, Span, Text } from 'components/UI' // Create an html textarea element that accepts all style props from styled-system. const SystemTextArea = system( @@ -34,6 +34,12 @@ const SystemTextArea = system( class TextArea extends React.PureComponent { static displayName = 'TextArea' + static defaultProps = { + description: null, + label: null, + showMessage: true + } + state = { hasFocus: false } @@ -47,14 +53,19 @@ class TextArea extends React.PureComponent { render() { const { css, + description, onChange, onBlur, onFocus, forwardedRef, + label, + required, theme, + field, fieldApi, fieldState, justifyContent, + showMessage, ...rest } = this.props const { readOnly } = this.props @@ -67,14 +78,34 @@ class TextArea extends React.PureComponent { let borderColor if (readOnly) { borderColor = theme.colors.gray - } else if (fieldState.error) { + } else if (fieldState.error || fieldState.asyncError) { borderColor = theme.colors.superRed - } else if (value && !fieldState.error) { + } else if (value && (!fieldState.error && !fieldState.asyncError)) { borderColor = theme.colors.superGreen } + // Extract any styled-system space props so that we can apply them directly to the wrapper. + const spaceProps = {} + Object.keys(rest).forEach(key => { + if ([...Object.keys(styles.space.propTypes), 'width'].includes(key)) { + spaceProps[key] = rest[key] + delete rest[key] + } + }) + return ( - + + {label && ( + + )} { @@ -119,13 +151,27 @@ class TextArea extends React.PureComponent { onFocus(e) } }} + required={required} error={fieldState.error} /> - {fieldState.error && ( - - {fieldState.error} - - )} + + {description && ( + + {description} + + )} + {showMessage && + (fieldState.error || fieldState.asyncError) && ( + + {fieldState.error || fieldState.asyncError} + + )} + ) } diff --git a/app/components/UI/Truncate.js b/app/components/UI/Truncate.js index 645d8615..d01ef267 100644 --- a/app/components/UI/Truncate.js +++ b/app/components/UI/Truncate.js @@ -1,20 +1,22 @@ import PropTypes from 'prop-types' const Truncate = ({ text, maxlen = 12 }) => { - if (!text) { + if (text === null || typeof text === 'undefined' || text === '') { return null } + const textString = text.toString() + const truncatedText = - text.length < maxlen - ? text - : text.substr(0, maxlen / 2) + '...' + text.substr(text.length - maxlen / 2) + textString.length < maxlen + ? textString + : textString.substr(0, maxlen / 2) + '...' + textString.substr(textString.length - maxlen / 2) return truncatedText } Truncate.propTypes = { - text: PropTypes.string.isRequired, + text: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), maxlen: PropTypes.number } diff --git a/app/components/UI/index.js b/app/components/UI/index.js index 8b2960d7..f626e5a8 100644 --- a/app/components/UI/index.js +++ b/app/components/UI/index.js @@ -25,12 +25,14 @@ export Modal from './Modal' export Notification from './Notification' export Panel, { PanelHeader, PanelBody, PanelFooter } from './Panel' export Page from './Page' +export PasswordInput from './PasswordInput' export QRCode from './QRCode' export Radio from './Radio' export RadioGroup from './RadioGroup' export Range from './Range' export Select from './Select' export Sidebar from './Sidebar' +export Span from './Span' export Spinner from './Spinner' export Text from './Text' export TextArea from './TextArea' diff --git a/stories/forms/form.stories.js b/stories/forms/form.stories.js index 89388543..3b4848d9 100644 --- a/stories/forms/form.stories.js +++ b/stories/forms/form.stories.js @@ -2,12 +2,12 @@ import React from 'react' import { storiesOf } from '@storybook/react' import { action } from '@storybook/addon-actions' import { Box } from 'rebass' -import { Form } from 'informed' import { CryptoAmountInput, FiatAmountInput, Page, MainContent, + Form, Input, Label, LightningInvoiceInput, @@ -35,9 +35,22 @@ const selectItems = [ storiesOf('Forms', module) .add('Input', () => ( -
- -
+ <> +
+ +
+
+ +
+
+ +
+ )) .add('Label', () => (
@@ -52,64 +65,48 @@ storiesOf('Forms', module) .add('CryptoAmountInput', () => ( - - - - + - - - - + - - - - +
)) .add('FiatAmountInput', () => (
- - - - +
)) .add('Lightning Invoice Textarea', () => ( - - -
- - -
@@ -148,56 +145,44 @@ storiesOf('Forms', module) {({ formState }) => ( - - - - - - + - - - - -