Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Form): make error prop shorthand in Form.Field #3603

Merged
merged 9 commits into from
Jun 28, 2019
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from 'react'
import { Form } from 'semantic-ui-react'

const FormExampleFieldErrorLabel = () => (
<Form>
<Form.Input
error={{ content: 'Please enter your first name', pointing: 'below' }}
fluid
label='First name'
placeholder='First name'
/>
<Form.Input
error='Please enter your last name'
fluid
label='Last name'
placeholder='Last name'
/>
<Form.Checkbox
label='I agree to the Terms and Conditions'
error={{
content: 'You must agree to the terms and conditions',
pointing: 'left',
}}
/>
</Form>
)

export default FormExampleFieldErrorLabel
1 change: 1 addition & 0 deletions docs/src/examples/collections/Form/States/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const FormStatesExamples = () => (
description='Individual fields may display an error state.'
examplePath='collections/Form/States/FormExampleFieldError'
/>
<ComponentExample examplePath='collections/Form/States/FormExampleFieldErrorLabel' />
<ComponentExample
title='Disabled Field'
description='Individual fields may be disabled.'
Expand Down
3 changes: 3 additions & 0 deletions src/collections/Form/FormDropdown.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ export interface StrictFormDropdownProps extends StrictFormFieldProps, StrictDro

/** A FormField control prop. */
control?: any

/** Individual fields may display an error state along with a message. */
error?: any
}

declare const FormDropdown: React.StatelessComponent<FormDropdownProps>
Expand Down
6 changes: 4 additions & 2 deletions src/collections/Form/FormField.d.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import * as React from 'react'

import {
HtmlLabelProps,
SemanticShorthandContent,
SemanticShorthandItem,
SemanticWIDTHS,
} from '../../generic'
import { LabelProps } from '../../elements/Label'

export interface FormFieldProps extends StrictFormFieldProps {
[key: string]: any
Expand Down Expand Up @@ -33,8 +35,8 @@ export interface StrictFormFieldProps {
/** Individual fields may be disabled. */
disabled?: boolean

/** Individual fields may display an error state. */
error?: boolean
/** Individual fields may display an error state along with a message. */
error?: boolean | SemanticShorthandItem<LabelProps>

/** A field can have its label next to instead of above it. */
inline?: boolean
Expand Down
22 changes: 20 additions & 2 deletions src/collections/Form/FormField.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
useKeyOnly,
useWidthProp,
} from '../../lib'
import Label from '../../elements/Label'
import Checkbox from '../../modules/Checkbox'
import Radio from '../../addons/Radio'

Expand Down Expand Up @@ -54,6 +55,15 @@ function FormField(props) {
const rest = getUnhandledProps(FormField, props)
const ElementType = getElementType(FormField, props)

const errorPointing = _.get(error, 'pointing', 'above')
const errorLabel = Label.create(error, {
autoGenerateKey: false,
defaultProps: { prompt: true, pointing: errorPointing },
})

const errorLabelBefore = (errorPointing === 'below' || errorPointing === 'right') && errorLabel
const errorLabelAfter = (errorPointing === 'above' || errorPointing === 'left') && errorLabel

// ----------------------------------------
// No Control
// ----------------------------------------
Expand All @@ -69,7 +79,9 @@ function FormField(props) {

return (
<ElementType {...rest} className={classes}>
{errorLabelBefore}
{createHTMLLabel(label, { autoGenerateKey: false })}
{errorLabelAfter}
</ElementType>
)
}
Expand All @@ -84,7 +96,9 @@ function FormField(props) {
return (
<ElementType className={classes}>
<label>
{errorLabelBefore}
{createElement(control, controlProps)} {label}
{errorLabelAfter}
</label>
</ElementType>
)
Expand All @@ -94,7 +108,9 @@ function FormField(props) {
if (control === Checkbox || control === Radio) {
return (
<ElementType className={classes}>
{errorLabelBefore}
{createElement(control, { ...controlProps, label })}
{errorLabelAfter}
</ElementType>
)
}
Expand All @@ -109,7 +125,9 @@ function FormField(props) {
defaultProps: { htmlFor: _.get(controlProps, 'id') },
autoGenerateKey: false,
})}
{errorLabelBefore}
{createElement(control, controlProps)}
{errorLabelAfter}
</ElementType>
)
}
Expand Down Expand Up @@ -140,8 +158,8 @@ FormField.propTypes = {
/** Individual fields may be disabled. */
disabled: PropTypes.bool,

/** Individual fields may display an error state. */
error: PropTypes.bool,
/** Individual fields may display an error state along with a message. */
error: PropTypes.oneOfType([PropTypes.bool, customPropTypes.itemShorthand]),

/** A field can have its label next to instead of above it. */
inline: PropTypes.bool,
Expand Down
3 changes: 3 additions & 0 deletions src/collections/Form/FormInput.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ export interface StrictFormInputProps extends StrictFormFieldProps, StrictInputP
/** A FormField control prop. */
control?: any

/** Individual fields may display an error state along with a message. */
error?: any

/** Shorthand for a Label. */
label?: SemanticShorthandItem<LabelProps>
}
Expand Down
5 changes: 5 additions & 0 deletions src/collections/Form/FormSelect.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import * as React from 'react'
import { StrictSelectProps } from '../../addons/Select'
import { DropdownItemProps } from '../../modules/Dropdown/DropdownItem'
import { StrictFormFieldProps } from './FormField'
import { SemanticShorthandItem } from '../../generic'
import { LabelProps } from '../../elements/Label'

export interface FormSelectProps extends StrictFormSelectProps {
[key: string]: any
Expand All @@ -15,6 +17,9 @@ export interface StrictFormSelectProps extends StrictFormFieldProps, StrictSelec
/** A FormField control prop. */
control?: any

/** Individual fields may display an error state along with a message. */
error?: any

/** Array of Dropdown.Item props e.g. `{ text: '', value: '' }` */
options: DropdownItemProps[]
}
Expand Down
12 changes: 6 additions & 6 deletions src/lib/factories.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,9 @@ export function createShorthandFactory(Component, mapValueToProps) {
// ============================================================
// HTML Factories
// ============================================================
export const createHTMLDivision = createShorthandFactory('div', val => ({ children: val }))
export const createHTMLIframe = createShorthandFactory('iframe', src => ({ src }))
export const createHTMLImage = createShorthandFactory('img', val => ({ src: val }))
export const createHTMLInput = createShorthandFactory('input', val => ({ type: val }))
export const createHTMLLabel = createShorthandFactory('label', val => ({ children: val }))
export const createHTMLParagraph = createShorthandFactory('p', val => ({ children: val }))
export const createHTMLDivision = createShorthandFactory('div', (val) => ({ children: val }))
export const createHTMLIframe = createShorthandFactory('iframe', (src) => ({ src }))
export const createHTMLImage = createShorthandFactory('img', (val) => ({ src: val }))
export const createHTMLInput = createShorthandFactory('input', (val) => ({ type: val }))
export const createHTMLLabel = createShorthandFactory('label', (val) => ({ children: val }))
export const createHTMLParagraph = createShorthandFactory('p', (val) => ({ children: val }))
2 changes: 1 addition & 1 deletion test/specs/collections/Form/FormDropdown-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import Dropdown from 'src/modules/Dropdown/Dropdown'
import * as common from 'test/specs/commonTests'

describe('FormDropdown', () => {
common.isConformant(FormDropdown)
common.isConformant(FormDropdown, { ignoredTypingsProps: ['error'] })
common.labelImplementsHtmlForProp(FormDropdown)

it('renders a FormField with a Dropdown control', () => {
Expand Down
48 changes: 48 additions & 0 deletions test/specs/collections/Form/FormField-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import faker from 'faker'
import React from 'react'

import Radio from 'src/addons/Radio/Radio'
import Label from 'src/elements/Label/Label'
import FormField from 'src/collections/Form/FormField'
import { SUI } from 'src/lib'
import Button from 'src/elements/Button/Button'
Expand Down Expand Up @@ -35,6 +36,53 @@ describe('FormField', () => {
})
})

describe('error', () => {
common.implementsLabelProp(FormField, {
autoGenerateKey: false,
propKey: 'error',
requiredProps: { label: faker.lorem.word() },
shorthandDefaultProps: { prompt: true, pointing: 'above' },
})
common.implementsLabelProp(FormField, {
autoGenerateKey: false,
propKey: 'error',
requiredProps: { control: 'radio' },
shorthandDefaultProps: { prompt: true, pointing: 'above' },
})
common.implementsLabelProp(FormField, {
autoGenerateKey: false,
propKey: 'error',
requiredProps: { control: Checkbox },
shorthandDefaultProps: { prompt: true, pointing: 'above' },
})
common.implementsLabelProp(FormField, {
autoGenerateKey: false,
propKey: 'error',
requiredProps: { control: 'input' },
shorthandDefaultProps: { prompt: true, pointing: 'above' },
})

it('positioned in DOM according to passed "pointing" prop', () => {
;[
{ pointing: 'below', inDom: 'before' },
{ pointing: 'right', inDom: 'before' },
{ pointing: 'left', inDom: 'after' },
{ pointing: 'above', inDom: 'after' },
].forEach(({ pointing, inDom }) => {
const wrapper = shallow(
<FormField
control='input'
error={{ content: faker.lorem.word(), pointing }}
type='text'
/>,
)

wrapper.childAt(inDom === 'before' ? 0 : 1).should.have.type(Label)
wrapper.childAt(inDom === 'before' ? 1 : 0).should.have.type('input')
})
})
})

describe('label', () => {
it('wraps html checkbox inputs', () => {
const text = faker.hacker.phrase()
Expand Down
2 changes: 1 addition & 1 deletion test/specs/collections/Form/FormInput-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import * as common from 'test/specs/commonTests'

describe('FormInput', () => {
common.isConformant(FormInput, {
ignoredTypingsProps: ['label'],
ignoredTypingsProps: ['label', 'error'],
})
common.labelImplementsHtmlForProp(FormInput)

Expand Down
2 changes: 1 addition & 1 deletion test/specs/collections/Form/FormSelect-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const requiredProps = {
}

describe('FormSelect', () => {
common.isConformant(FormSelect, { requiredProps })
common.isConformant(FormSelect, { requiredProps, ignoredTypingsProps: ['error'] })
common.labelImplementsHtmlForProp(FormSelect, { requiredProps })

it('renders a FormField with a Select control', () => {
Expand Down