From 009b632110c2857d8b04f5cf35bb73256a0b2cd9 Mon Sep 17 00:00:00 2001 From: Byron Mitchell Date: Wed, 11 Oct 2017 15:03:37 -0400 Subject: [PATCH] FormField: Initial commit of the FormField component and story --- .storybook/FormField.js | 264 ++++++++++++++++++++++++++++++++++++++++ src/FormField.js | 163 +++++++++++++++++++++++++ src/index.js | 1 + 3 files changed, 428 insertions(+) create mode 100644 .storybook/FormField.js create mode 100644 src/FormField.js diff --git a/.storybook/FormField.js b/.storybook/FormField.js new file mode 100644 index 0000000000..e314b766e8 --- /dev/null +++ b/.storybook/FormField.js @@ -0,0 +1,264 @@ +import React from 'react' +import { storiesOf } from '@storybook/react' +import styled from 'styled-components' +import { Box, FormField, theme, Flex } from '../src' + + +class InputController extends React.PureComponent { + state = { + value: '', + isValid: false, + isDirty: false, + isFocused: false + } + + onChange = (event) => { + const value = event.target.value + const isValid = value.length === 5 + this.setState({ + isDirty: true, + value: value, + isValid: isValid + }) + } + + onBlur = () => { + this.setState({ + isFocused: false + }) + } + + onFocus = () => { + this.setState({ + isFocused: true + }) + } + + getBorderColor = () => { + const { isValid, isFocused, isDirty } = this.state + if (isValid && isDirty) { + return theme.colors.darkGreen + } else if (isFocused) { + return theme.colors.blue + } else if (!isValid && isDirty) { + return theme.colors.red + } + } + + isThickBorder = () => (this.state.isFocused || this.state.isDirty) + + render () { + return ( + React.cloneElement(React.Children.only(this.props.children), { + onChange: this.onChange, + onBlur: this.onBlur, + onFocus: this.onFocus, + onClick: this.onClick, + borderColor: this.getBorderColor(), + thickBorder: this.isThickBorder(), + ...this.state + }) + ) + } +} + +storiesOf('FormField', module) + .add('FormField', () => ( +
+ + +
Placeholder
+ +
+ +
Text entered
+ +
+
+ + + +
Credit Card
+ +
+ +
Credit Card Number
+ +
+
+ + + +
First Name
+ +
+ +
Last name
+ +
+
+ + + +
Invalid Email Address
+ +
+ +
Valid Email Address
+ +
+
+ + + +
Poor Password
+ +
+ +
Weak Password
+ +
+
+ + + +
Good Password
+ +
+ +
Strong Password
+ +
+
+
+ )) + + +const ColoredMarkContainer = styled.div` + width: 36px; +` +const ColoredMark = styled.div` + background-color: ${props => props.color}; + border-color: ${props => props.color}; + display: inline-block; + margin-left: 3px; + border-radius: 2px; + width: 4px; + height: 12px; +` + +const poorIconRenderer = () => + + + + + + + +const weakIconRenderer = () => + + + + + + + +const goodIconRenderer = () => + + + + + + + +const strongIconRenderer = () => + + + + + + \ No newline at end of file diff --git a/src/FormField.js b/src/FormField.js new file mode 100644 index 0000000000..75085963ac --- /dev/null +++ b/src/FormField.js @@ -0,0 +1,163 @@ +import React from 'react' +import PropTypes from 'prop-types' +import styled from 'styled-components' +import { theme, Flex, Box, Icon, Text } from '../' + +const propTypes = { + id: PropTypes.string.isRequired, + borderColor: PropTypes.string, + labelColor: PropTypes.string, + color: PropTypes.string, + icon: PropTypes.string, + iconRenderer: PropTypes.func, + iconColor: PropTypes.string, + thickBorder: PropTypes.bool, + error: PropTypes.string, + errorColor: PropTypes.string, + label: PropTypes.string, + accessibleLabel: PropTypes.string, + placeholder: PropTypes.string, + placeholderColor: PropTypes.string, + value: PropTypes.string, + name: PropTypes.string +} + +const defaultProps = { + borderColor: theme.colors.lightGray, + placeholderColor: theme.colors.lightGray, + errorColor: theme.colors.red, + labelColor: theme.colors.text, + color: theme.colors.text +} + +const errors = props => props.error && { + boxShadow: `0 26px ${props.errorColor}` +} + +const InputContainer = styled.div` + display: flex; + align-items: center; + border-color: ${(props) => props.borderColor}; + border-width: ${(props) => (props.thickBorder) ? '2px' : '1px'}; + border-style: solid; + border-radius: 2px; + width: 100%; + height: 48px; + padding-left: 12px; // ${props => props.theme.space[3]}px; or maybe use a combo of padding on elements + padding-right: 12px; // ${props => props.theme.space[3]}px; + + ${errors} +` + +const MiniLabel = Text.extend` + font-weight: 500; + font-size: 10px; // ${({ theme }) => theme.fontSizes[2]}px; no matching font size +` + +const StrippedInput = styled.input` + display: block; + width: 100%; + outline: 0; + font-size: ${({ theme }) => theme.fontSizes[2]}px; + background-color: transparent; + border: transparent; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + vertical-align: top; + + ::placeholder { + color: ${({ placeholderColor }) => placeholderColor}; + } + + ::-ms-clear { + display: none; + } +` + +const VerticalFlex = styled(Flex) ` // TODO add direction to Flex component + flex-direction: column; +` +const ErrorText = Text.extend` + position: absolute; +` + +const FormField = ({ + id, + accessibleLabel, + borderColor, + labelColor, + color, + icon, + iconRenderer, + error, + errorColor, + iconColor, + thickBorder, + label, + placeholder, + placeholderColor, + value, + name, + ...otherProps +}) => { + + let textInput = null + + const getIcon = () => { + if (iconRenderer) { + return iconRenderer() + } else if (icon) { + return + } + return null + } + + const handleInputFocus = () => { + textInput.focus() + } + + return ( + + + + + {label && + + + {label} + + + } + + { textInput = input }} + {...otherProps} + /> + + + {getIcon()} + + + {error && + + + {error} + + + } + + ) +} + +FormField.propTypes = propTypes +FormField.defaultProps = defaultProps + +export default FormField diff --git a/src/index.js b/src/index.js index a0f2824b78..7b9c2ad448 100644 --- a/src/index.js +++ b/src/index.js @@ -7,6 +7,7 @@ export { default as Divider } from './Divider' export { default as GreenButton } from './GreenButton' export { default as RedButton } from './RedButton' export { default as OutlineButton } from './OutlineButton' +export { default as FormField } from './FormField' export { default as Flex } from './Flex' export { default as Heading } from './Heading' export { default as Hide } from './Hide'