Skip to content

Commit

Permalink
feat(input): Add error message
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanoglesby08 committed Sep 11, 2017
1 parent 3485b5d commit f0b3ce6
Show file tree
Hide file tree
Showing 8 changed files with 78 additions and 16 deletions.
20 changes: 16 additions & 4 deletions src/components/Input/Input.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import React from 'react'
import PropTypes from 'prop-types'

import Fade from './Fade'
import Icon from '../../old-components/Icon/Icon'
import ColoredTextProvider from '../Typography/ColoredTextProvider/ColoredTextProvider'
import Paragraph from '../Typography/Paragraph/Paragraph'
import Fade from './Fade'
import safeRest from '../../safeRest'

import styles from './Input.modules.scss'
Expand Down Expand Up @@ -82,7 +84,7 @@ class Input extends React.Component {
}

render() {
const { type, label, feedback, ...rest } = this.props
const { type, label, feedback, error, ...rest } = this.props

const id = rest.id || rest.name || textToId(label)
const wrapperClassName = getWrapperClassName(feedback, this.state.focused, rest.disabled)
Expand All @@ -92,14 +94,22 @@ class Input extends React.Component {
<div>
<label htmlFor={id} className={styles.label}>{label}</label>

<div className={wrapperClassName} data-inputwrapper>
{ error &&
<div className={styles.errorMessage}>
<ColoredTextProvider colorClassName={styles.errorText}>
<Paragraph>{error}</Paragraph>
</ColoredTextProvider>
</div>
}

<div className={wrapperClassName} data-testID="inputWrapper">
<input
{...safeRest(rest)} id={id} type={type} className={styles.input}
value={this.state.value}
onChange={this.onChange} onFocus={this.onFocus} onBlur={this.onBlur}
/>

<Fade timeout={150} in={showIcon} mountOnEnter={true} unmountOnExit={true}>
<Fade timeout={100} in={showIcon} mountOnEnter={true} unmountOnExit={true}>
{ () => (
<span className={styles.icon}>
<Icon glyph={iconByFeedbackState[feedback]} aria-hidden="true" />
Expand All @@ -120,6 +130,7 @@ Input.propTypes = {
PropTypes.number
]),
feedback: PropTypes.oneOf(['success', 'error']),
error: PropTypes.string,
onChange: PropTypes.func,
onFocus: PropTypes.func,
onBlur: PropTypes.func
Expand All @@ -129,6 +140,7 @@ Input.defaultProps = {
type: 'text',
value: '',
feedback: undefined,
error: undefined,
onChange: undefined,
onFocus: undefined,
onBlur: undefined
Expand Down
18 changes: 13 additions & 5 deletions src/components/Input/Input.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
```
<div>
<Input label="First name" />
<Input label="First name" value="Ryan Ogl" />
<Input label="First name" autoFocus />
<Input label="First name" disabled />
<Input label="First name" feedback="success" />
<Input label="First name" feedback="error" />
<Input type="password" label="Password" value="123abc" />
<Input type="number" label="Age" value="32" />
<Input label="First name 2" autoFocus />
<Input label="First name 3" disabled />
<Input label="First name 4" feedback="success" />
<Input label="First name 5" feedback="error" />
<Input label="First name 6" feedback="error" error="First name is required" />
</div>
```
18 changes: 18 additions & 0 deletions src/components/Input/Input.modules.scss
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

$border-radius: 4px;

// TODO: Bring over the responsive styles
// TODO: Bring over/globalize the browser specific styles for the pseudo elements/placeholders

.label {
composes: medium boldFont from '../Typography/Text/Text.modules.scss';

Expand Down Expand Up @@ -94,3 +97,18 @@ input.input {
.icon {
padding: 0 $spacing-base;
}

.errorMessage {
padding: $spacing-base;
margin-bottom: $spacing-tight;

background-color: $color-lavender-blush;
border-radius: $border-radius;

// transition: background-color .1s linear; TODO: Why?
}

// TODO: This is duplicated in Notification...
.errorText {
color: $color-cardinal;
}
28 changes: 26 additions & 2 deletions src/components/Input/__tests__/Input.spec.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { shallow, render } from 'enzyme'
import toJson from 'enzyme-to-json'

import Icon from '../../../old-components/Icon/Icon'
import ColoredTextProvider from '../../Typography/ColoredTextProvider/ColoredTextProvider'
import Paragraph from '../../Typography/Paragraph/Paragraph'
import Fade from '../Fade'
import Input from '../Input'

Expand All @@ -14,7 +16,7 @@ describe('Input', () => {
const doRender = (overrides = {}) => render(<Input {...defaultProps} {...overrides} />)

const findInputElement = input => input.find('input')
const findWrapperElement = input => input.find('[data-inputwrapper]')
const findWrapperElement = input => input.find('[data-testID="inputWrapper"]')

it('renders', () => {
const input = doRender()
Expand Down Expand Up @@ -169,6 +171,15 @@ describe('Input', () => {
findInputElement(input).simulate('blur')
expect(findWrapperElement(input)).toHaveClassName('success')
})

it('fades the feedback icon in on focus lost and out on focus gained', () => {
const input = doShallow({ feedback: 'success' })

expect(input.find(Fade)).toHaveProp('in', true)

findInputElement(input).simulate('focus')
expect(input.find(Fade)).toHaveProp('in', false)
})
})

it('can be disabled', () => {
Expand All @@ -181,7 +192,15 @@ describe('Input', () => {
expect(findInputElement(input)).toBeDisabled()
})

// TODO: it('can have a field helper')
it('can have an error message', () => {
const input = doShallow({ error: 'Oh no a terrible error!' })

expect(input).toContainReact(
<ColoredTextProvider colorClassName="errorText">
<Paragraph>Oh no a terrible error!</Paragraph>
</ColoredTextProvider>
)
})

it('passes additional attributes to the input element', () => {
const input = doShallow({ name: 'a name', placeholder: 'a placeholder' })
Expand All @@ -196,4 +215,9 @@ describe('Input', () => {
expect(findInputElement(input)).not.toHaveProp('className', 'my-custom-class')
expect(findInputElement(input)).not.toHaveProp('style')
})

describe('accessibility', () => {
it('marks the input as invalid when in the error feedback state')
it('connects the error message to the input field for screen readers')
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ exports[`Input renders 1`] = `
</label>
<div
class="default"
data-inputwrapper="true"
data-testid="inputWrapper"
>
<input
class="input"
Expand All @@ -32,7 +32,7 @@ exports[`Input renders with a feedback state and icon 1`] = `
</label>
<div
class="error"
data-inputwrapper="true"
data-testid="inputWrapper"
>
<input
class="input"
Expand All @@ -41,7 +41,7 @@ exports[`Input renders with a feedback state and icon 1`] = `
value=""
/>
<div
style="transition:opacity 150ms ease-in-out;opacity:1;"
style="transition:opacity 100ms ease-in-out;opacity:1;"
>
<span
class="icon"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import Icon from '../../../../old-components/Icon/Icon'
import styles from '../UnorderedList.modules.scss'

const UnorderedItem = ({ variant, children, ...rest }) => {

const iconClasses = `
${styles.fixedWidth}
${styles.icon}
Expand Down
2 changes: 1 addition & 1 deletion src/components/Lists/UnorderedList/UnorderedList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import styles from './UnorderedList.modules.scss'

const UnorderedList = ({ variant, children, ...rest }) => {
const classes = `
${styles['base']}
${styles.base}
${styles[variant]}
`
return (
Expand Down
1 change: 1 addition & 0 deletions src/scss/settings/_spacing.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
$spacing-base: 1rem;

$spacing-x-tight: $spacing-base * .25;
$spacing-tight: $spacing-base * .5;
$spacing-wide: $spacing-base * 2;

0 comments on commit f0b3ce6

Please sign in to comment.