Skip to content

Commit

Permalink
Allow passing custom error and label components to LabeledField (#330)
Browse files Browse the repository at this point in the history
* Allow passing custom error and label components to LabeledField

* Add example of creating a custom label with existing input label

* Add stories and update example

* minor bump to v3.19.0

* Fix default typos

* minor bump to v3.20.0

* Update example with redux-form

* Cleaning up formatting

* Clean-up documentation
  • Loading branch information
chawes13 authored Apr 4, 2019
1 parent d26f1d1 commit 7974687
Show file tree
Hide file tree
Showing 10 changed files with 206 additions and 49 deletions.
90 changes: 56 additions & 34 deletions docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,16 +122,16 @@
- [compareAtPath][118]
- [Parameters][119]
- [Examples][120]
- [serializeOptions][121]
- [generateInputErrorId][121]
- [Parameters][122]
- [Examples][123]
- [serializeOptionGroups][124]
- [serializeOptions][124]
- [Parameters][125]
- [Examples][126]
- [stripNamespace][127]
- [serializeOptionGroups][127]
- [Parameters][128]
- [Examples][129]
- [generateInputErrorId][130]
- [stripNamespace][130]
- [Parameters][131]
- [Examples][132]

Expand Down Expand Up @@ -1044,7 +1044,8 @@ This component is used within [LabeledField][78], and therefore is incorporated
The text of the label is set using the following rules:

- If the `label` prop is set to `false`, the label is hidden completely
- If the `label` prop is set to a string, the label will display that text
- Else If the component is passed childen, the children will be displayed within the `label`
- Else If the `label` prop is set to a string, the label will display that text
- Otherwise, the label will be set using the `name` prop.

If `name` is used to set the text, it will be stripped of its prefixes and converted to [start case][152].
Expand Down Expand Up @@ -1091,8 +1092,8 @@ function EmailInput ({

## LabeledField

A fieldset wrapper for redux-form controlled inputs. This wrapper adds an [InputLabel][75]
above the wrapped component and an [InputError][72] below. Additionally, it adds the class `"error"`
A fieldset wrapper for redux-form controlled inputs. This wrapper adds a label component (defaults to [InputLabel][75])
above the wrapped component and an error component below (defaults to [InputError][72]). Additionally, it adds the class `"error"`
to the fieldset if the input is touched and invalid.

In order to populate the `InputLabel` and `InputError` correctly, you should pass all the props of the corresponding input
Expand All @@ -1102,11 +1103,13 @@ use the [omitLabelProps][86] helper.
### Parameters

- `hideErrorLabel` **[Boolean][136]?** A boolean determining whether to hide the error label on input error (optional, default `false`)
- `labelComponent` **[Function][135]** A custom label component for the input (optional, default `InputLabel`)
- `errorComponent` **[Function][135]** A custom error component for the input (optional, default `InputError`)

### Examples

```javascript
// A custom input to use with redux-forms
// A custom input to use with redux-form

function LabeledPhoneInput (props) {
const {
Expand All @@ -1127,7 +1130,26 @@ function LabeledPhoneInput (props) {
)
}

*
// A custom label to pass in as a label component (using <InputLabel /> and redux-form)

import LabeledPhoneInput from './LabeledPhoneInput'
import { InputLabel } from 'lp-components'
import { Field } from 'redux-form'

function CustomLabelComponent ({ onClickLabel, ...rest }) {
return (
<InputLabel { ...rest }>
<span>I agree to the <a onClick={ onClickLabel }>Terms and Conditions</a></span>
</InputLabel>
)
}

<Field
name="phoneNumber"
component={ LabeledPhoneInput }
onClickLabel={ () => 'bar' }
labelComponent={ CustomLabelComponent }
/>
```

## blurDirty
Expand Down Expand Up @@ -1544,6 +1566,27 @@ people.sort(ageComparator)

Returns **[Function][135]** Comparison function

## generateInputErrorId

A utility for generating a unique id for an input error label. This logic
is centralized to facilitate reference by multiple input components.

### Parameters

- `name` **[String][134]** The name of the input

### Examples

```javascript
const name = 'cardNumber'

generateInputErrorId(name)

// 'cardNumberError'
```

Returns **[String][134]** String representing error id

## serializeOptions

Function that transforms string options into object options with keys of
Expand Down Expand Up @@ -1626,27 +1669,6 @@ stripNamespace(namespace)

Returns **[String][134]** String with namespace removed

## generateInputErrorId

A utility for generating a unique id for an input error label. This logic
is centralized to facilitate reference by multiple input components.

### Parameters

- `name` **[String][134]** The name of the input

### Examples

```javascript
const name = 'cardNumber'

generateInputErrorId(name)

// 'cardNumberError'
```

Returns **[String][134]** String representing error id

[1]: #colorpicker

[2]: #parameters
Expand Down Expand Up @@ -1887,25 +1909,25 @@ Returns **[String][134]** String representing error id

[120]: #examples-39

[121]: #serializeoptions
[121]: #generateinputerrorid

[122]: #parameters-35

[123]: #examples-40

[124]: #serializeoptiongroups
[124]: #serializeoptions

[125]: #parameters-36

[126]: #examples-41

[127]: #stripnamespace
[127]: #serializeoptiongroups

[128]: #parameters-37

[129]: #examples-42

[130]: #generateinputerrorid
[130]: #stripnamespace

[131]: #parameters-38

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@launchpadlab/lp-components",
"version": "3.19.0",
"version": "3.20.0",
"engines": {
"node": "^8.0.0 || ^10.13.0"
},
Expand Down
2 changes: 2 additions & 0 deletions src/forms/helpers/omit-label-props.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ function omitLabelProps (props) {
'tooltip',
'label',
'requiredIndicator',
'errorComponent',
'labelComponent',
], props)
}

Expand Down
14 changes: 9 additions & 5 deletions src/forms/labels/input-label.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import { toggle, togglePropTypes } from '../../utils'
*
* The text of the label is set using the following rules:
* - If the `label` prop is set to `false`, the label is hidden completely
* - If the `label` prop is set to a string, the label will display that text
* - Else If the component is passed childen, the children will be displayed within the `label`
* - Else If the `label` prop is set to a string, the label will display that text
* - Otherwise, the label will be set using the `name` prop.
*
* If `name` is used to set the text, it will be stripped of its prefixes and converted to [start case](https://en.wikipedia.org/wiki/Letter_case#Stylistic_or_specialised_usage).
Expand Down Expand Up @@ -52,10 +53,11 @@ import { toggle, togglePropTypes } from '../../utils'
* </div>
* )
* }
*
*
**/

const propTypes = {
children: PropTypes.node,
hint: PropTypes.string,
id: PropTypes.string,
label: PropTypes.oneOfType([ PropTypes.string, PropTypes.bool ]),
Expand All @@ -67,6 +69,7 @@ const propTypes = {
}

const defaultProps = {
children: null,
hint: '',
id: '',
label: '',
Expand All @@ -75,6 +78,7 @@ const defaultProps = {
}

function InputLabel ({
children,
hint,
id,
label,
Expand All @@ -85,13 +89,14 @@ function InputLabel ({
required,
requiredIndicator,
}) {
const labelText = label || convertNameToLabel(name)
const labelToDisplay = children || label || convertNameToLabel(name)

return (
<span>
{
label !== false &&
<label htmlFor={ id || name }>
{ labelText }
{ labelToDisplay }
{
required && requiredIndicator &&
<span className="required-indicator" aria-hidden="true">{ requiredIndicator }</span>
Expand All @@ -117,7 +122,6 @@ function InputLabel ({
}

InputLabel.propTypes = propTypes

InputLabel.defaultProps = defaultProps

export default toggle('tooltipShown')(InputLabel)
39 changes: 32 additions & 7 deletions src/forms/labels/labeled-field.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import InputLabel from './input-label'

/**
*
* A fieldset wrapper for redux-form controlled inputs. This wrapper adds an {@link InputLabel}
* above the wrapped component and an {@link InputError} below. Additionally, it adds the class `"error"`
* A fieldset wrapper for redux-form controlled inputs. This wrapper adds a label component (defaults to {@link InputLabel})
* above the wrapped component and an error component below (defaults to {@link InputError}). Additionally, it adds the class `"error"`
* to the fieldset if the input is touched and invalid.
*
* In order to populate the `InputLabel` and `InputError` correctly, you should pass all the props of the corresponding input
Expand All @@ -16,11 +16,13 @@ import InputLabel from './input-label'
*
* @name LabeledField
* @type Function
* @param {Boolean} [hideErrorLabel] A boolean determining whether to hide the error label on input error (optional, default `false`)
* @param {Boolean} [hideErrorLabel] - A boolean determining whether to hide the error label on input error (optional, default `false`)
* @param {Function} [labelComponent=InputLabel] - A custom label component for the input
* @param {Function} [errorComponent=InputError] - A custom error component for the input
*
* @example
*
* // A custom input to use with redux-forms
* // A custom input to use with redux-form
*
* function LabeledPhoneInput (props) {
* const {
Expand All @@ -40,8 +42,29 @@ import InputLabel from './input-label'
* </LabeledField>
* )
* }
*
* // A custom label to pass in as a label component (using <InputLabel /> and redux-form)
*
* import LabeledPhoneInput from './LabeledPhoneInput'
* import { InputLabel } from 'lp-components'
* import { Field } from 'redux-form'
*
* function CustomLabelComponent ({ onClickLabel, ...rest }) {
* return (
* <InputLabel { ...rest }>
* <span>I agree to the <a onClick={ onClickLabel }>Terms and Conditions</a></span>
* </InputLabel>
* )
* }
*
* <Field
* name="phoneNumber"
* component={ LabeledPhoneInput }
* onClickLabel={ () => 'bar' }
* labelComponent={ CustomLabelComponent }
* />
*
**/
*/

const propTypes = {
...InputLabel.propTypes,
Expand All @@ -59,8 +82,10 @@ function LabeledField ({
input: { name },
meta: { error, touched, invalid },
className,
errorComponent: ErrorComponent = InputError,
hint,
label,
labelComponent: LabelComponent = InputLabel,
tooltip,
required,
requiredIndicator,
Expand All @@ -69,9 +94,9 @@ function LabeledField ({
}) {
return (
<fieldset className={ classnames(className, { 'error': touched && invalid }) }>
<InputLabel { ...{ hint, label, name, id, tooltip, required, requiredIndicator } } />
<LabelComponent { ...{ hint, label, name, id, tooltip, required, requiredIndicator } } />
{ children }
{ !hideErrorLabel && <InputError { ...{ error, invalid, touched, name } } /> }
{ !hideErrorLabel && <ErrorComponent { ...{ error, invalid, touched, name } } /> }
</fieldset>
)
}
Expand Down
2 changes: 1 addition & 1 deletion src/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export {

// Local
export compareAtPath from './compare-at-path'
export generateInputErrorId from './generate-input-error-id'
export serializeOptions from './serialize-options'
export serializeOptionGroups from './serialize-option-groups'
export stripNamespace from './strip-namespace'
export generateInputErrorId from './generate-input-error-id'
33 changes: 33 additions & 0 deletions stories/forms/labels/labeled-field.story.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,37 @@ storiesOf('LabeledField', module)
<Input />
</LabeledField>
))
.add('with custom label', () => {
const CustomLabel = () => <label htmlFor="inputName">This is a <b>custom</b> label</label>
return (
<LabeledField {...{
input: {
name: 'inputName',
},
meta: {},
labelComponent: CustomLabel
}}>
<Input id="inputName" />
</LabeledField>
)
})
.add('with custom error', () => {
// eslint-disable-next-line
const CustomError = (props) => <span id="inputError">This is a <b>custom</b> error message: {props.error}</span>
return (
<LabeledField {...{
input: {
name: 'inputName',
},
meta: {
touched: true,
invalid: true,
error: 'Cannot be blank',
},
errorComponent: CustomError
}}>
<Input id="inputName" aria-describedby="inputError" />
</LabeledField>
)
})

Loading

0 comments on commit 7974687

Please sign in to comment.