Skip to content

Commit

Permalink
feat(select): Initial commit of Select
Browse files Browse the repository at this point in the history
Refactor FormField to be generic enough for both Textarea and Select
  • Loading branch information
ryanoglesby08 committed Nov 27, 2017
1 parent 2a5c533 commit eeb5e69
Show file tree
Hide file tree
Showing 13 changed files with 332 additions and 185 deletions.
1 change: 1 addition & 0 deletions config/styleguide.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,7 @@ module.exports = {
path.resolve('src/components/Button/Button.jsx'),
path.resolve('src/components/Input/Input.jsx'),
toggleByEnv('Textarea', path.resolve('src/components/Textarea/Textarea.jsx')),
toggleByEnv('Select', path.resolve('src/components/Select/Select.jsx')),
path.resolve('src/components/Tooltip/Tooltip.jsx'),
path.resolve('src/old-components/SelectorCounter/SelectorCounter.jsx'),
])
Expand Down
40 changes: 40 additions & 0 deletions src/components/FormField/FeedbackIcon.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from 'react'
import PropTypes from 'prop-types'

import Fade from '../Animation/Fade'
import StandaloneIcon from '../Icons/StandaloneIcon/StandaloneIcon'

import iconWrapperStyles from '../Icons/IconWrapper.modules.scss'

const FeedbackIcon = ({ showIcon, feedback }) => (
<Fade timeout={100} in={showIcon} mountOnEnter={true} unmountOnExit={true}>
{() => (
<div className={iconWrapperStyles.fixLineHeight}>
{feedback === 'success' ? (
<StandaloneIcon
symbol="checkmark"
variant="primary"
size={16}
a11yText="The value of this input field is valid."
/>
) : (
<StandaloneIcon
symbol="exclamationPointCircle"
variant="error"
size={16}
a11yText="The value of this input field is invalid."
/>
)}
</div>
)}
</Fade>
)
FeedbackIcon.propTypes = {
showIcon: PropTypes.bool.isRequired,
feedback: PropTypes.oneOf(['success', 'error']),
}
FeedbackIcon.defaultProps = {
feedback: undefined
}

export default FeedbackIcon
177 changes: 97 additions & 80 deletions src/components/FormField/FormField.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,20 @@ import React from 'react'
import PropTypes from 'prop-types'
import { childrenOfType } from 'airbnb-prop-types'

import safeRest from '../../utils/safeRest'
import generateId from '../../utils/generateId'
import joinClassNames from '../../utils/joinClassNames'

import Box from '../Box/Box'
import Flexbox from '../Flexbox/Flexbox'
import Fade from '../Animation/Fade'
import Text from '../Typography/Text/Text'
import Paragraph from '../Typography/Paragraph/Paragraph'
import Helper from '../Input/Helper/Helper'
import StandaloneIcon from '../Icons/StandaloneIcon/StandaloneIcon'
import Tooltip from '../Tooltip/Tooltip'

import styles from './FormField.modules.scss'
import positionStyles from '../Position.modules.scss'
import iconWrapperStyles from '../Icons/IconWrapper.modules.scss'

const getWrapperClassName = (feedback, focus, disabled) => {
const getClassName = (feedback, focus, disabled) => {
if (disabled) {
return styles.disabled
}
Expand Down Expand Up @@ -71,95 +68,113 @@ const renderHelper = (helper, helperId, feedback, value) => {
)
}

const renderIcon = feedback => {
if (feedback === 'success') {
return (
<StandaloneIcon
symbol="checkmark"
variant="primary"
size={16}
a11yText="The value of this input field is valid."
/>
)
class FormField extends React.Component {
constructor(props) {
super(props)

this.state = {
value: props.value,
focus: false,
}
}

return (
<StandaloneIcon
symbol="exclamationPointCircle"
variant="error"
size={16}
a11yText="The value of this input field is invalid."
/>
)
}
componentWillReceiveProps(nextProps) {
if (this.state.value !== nextProps.value) {
this.setState({
value: nextProps.value,
})
}
}

const FormField = ({
value,
label,
hint,
focus,
feedback,
error,
helper,
tooltip,
children,
...rest
}) => {
const fieldId = generateId(rest.id, rest.name, label)
const helperId = helper && fieldId.postfix('helper')
const errorId = error && fieldId.postfix('error-message')

const wrapperClassName = getWrapperClassName(feedback, focus, rest.disabled)

const showIcon = showFeedbackIcon(feedback, focus)

const ariaInvalid = feedback === 'error'
const ariaDescribedBy = errorId || helperId || undefined
onChange = event => {
const { onChange } = this.props

return (
<Box between={2}>
<Flexbox
direction="row"
justifyContent="spaceBetween"
dangerouslyAddClassName={positionStyles.relative}
>
{renderLabel(label, hint, fieldId)}

{tooltip && React.cloneElement(tooltip, { connectedFieldLabel: label })}
</Flexbox>

{helper && renderHelper(helper, helperId, feedback, value)}

{error && renderError(error, errorId)}

<div className={positionStyles.relative}>
{children(fieldId.identity(), ariaInvalid, ariaDescribedBy, wrapperClassName)}

<div
className={joinClassNames(
positionStyles.absolute,
iconWrapperStyles.fixLineHeight,
styles.iconPosition
)}
this.setState({
value: event.target.value,
})

if (onChange) {
onChange(event)
}
}

onFocus = event => {
const { onFocus } = this.props

this.setState({ focus: true })

if (onFocus) {
onFocus(event)
}
}

onBlur = event => {
const { onBlur } = this.props

this.setState({ focus: false })

if (onBlur) {
onBlur(event)
}
}

render() {
const { label, hint, feedback, error, helper, tooltip, children, ...rest } = this.props

const fieldId = generateId(rest.id, rest.name, label)
const helperId = helper && fieldId.postfix('helper')
const errorId = error && fieldId.postfix('error-message')

const showIcon = showFeedbackIcon(feedback, this.state.focus)

return (
<Box between={2}>
<Flexbox
direction="row"
justifyContent="spaceBetween"
dangerouslyAddClassName={positionStyles.relative}
>
<Fade timeout={100} in={showIcon} mountOnEnter={true} unmountOnExit={true}>
{() => renderIcon(feedback)}
</Fade>
</div>
</div>
</Box>
)
{renderLabel(label, hint, fieldId)}

{tooltip && React.cloneElement(tooltip, { connectedFieldLabel: label })}
</Flexbox>

{helper && renderHelper(helper, helperId, feedback, this.state.value)}

{error && renderError(error, errorId)}

{children(
{
...safeRest(rest),
id: fieldId.identity(),
className: getClassName(feedback, this.state.focus, rest.disabled),
value: this.state.value,
onChange: this.onChange,
onFocus: this.onFocus,
onBlur: this.onBlur,
'aria-invalid': feedback === 'error',
'aria-describedby': errorId || helperId || undefined,
'data-no-global-styles': true, // TODO: Remove me when removing the global form styles
},
showIcon,
feedback
)}
</Box>
)
}
}

FormField.propTypes = {
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
label: PropTypes.string.isRequired,
hint: PropTypes.string,
focus: PropTypes.bool.isRequired,
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
feedback: PropTypes.oneOf(['success', 'error']),
error: PropTypes.string,
helper: PropTypes.oneOfType([PropTypes.func, PropTypes.node]),
tooltip: childrenOfType(Tooltip),
onChange: PropTypes.func,
onFocus: PropTypes.func,
onBlur: PropTypes.func,
children: PropTypes.func.isRequired,
}

Expand All @@ -170,6 +185,8 @@ FormField.defaultProps = {
helper: undefined,
tooltip: undefined,
onChange: undefined,
onFocus: undefined,
onBlur: undefined,
}

export default FormField
2 changes: 1 addition & 1 deletion src/components/FormField/FormField.modules.scss
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

composes: noMargin from '../Spacing.modules.scss';
// This vertical padding fixes an IE11 bug that was causing the input value to jump slightly and get cut off.
composes: verticalPadding-3 horizontalPadding-3 from '../Box/Box.modules.scss';
composes: verticalPadding-2 horizontalPadding-3 from '../Box/Box.modules.scss';

composes: thin rounded from '../Borders.modules.scss';

Expand Down
5 changes: 5 additions & 0 deletions src/components/Position.modules.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,8 @@
.absolute {
position: absolute;
}

.centerVertically {
top: 50%;
transform: translateY(-50%);
}
Loading

0 comments on commit eeb5e69

Please sign in to comment.