@bigfishtv/cockpit
Version:
199 lines (183 loc) • 4.81 kB
JavaScript
import PropTypes from 'prop-types'
import React, { Component } from 'react'
import classnames from 'classnames'
import { withFormValue, Input as _DefaultInput, ErrorList as BaseErrorList } from '@bigfishtv/react-forms'
import { wordCount, labelFromKeyPath } from '../../utils/stringUtils'
import InfoTooltip from '../InfoTooltip'
import StopShouting from '../StopShouting'
// we define this because react-docgen fails when defaultProp directly references an imported component
class DefaultInput extends Component {
render() {
// don't put thses on to <input> component pls
const { invalid, hint, select, formValue, ...props } = this.props
return <_DefaultInput {...props} />
}
}
const Self = _props => {
// don't put these on div please
const {
children,
fieldClass,
Label,
label,
instructions,
Input,
ErrorList,
Self,
autoLabel,
stopShouting,
invalid,
className,
formValue,
maxWords,
inputProps,
labelWidth,
inline,
hint,
...props
} = _props
return (
<div className={classnames('form-input', fieldClass, { 'form-input-inline': inline })} {...props}>
{children}
</div>
)
}
const Label = ({ labelWidth, hint, label, formValue }) => {
if (label === undefined) {
label = labelFromKeyPath(formValue.keyPath)
}
return label ? (
<label style={{ minWidth: labelWidth }}>
{label} {hint && <InfoTooltip text={hint} />}
</label>
) : (
<span />
)
}
const Error = ({ error, label, noLabel, complete }) =>
error ? <div className="form-message error">{error.message}</div> : null
const ErrorList = ({ formValue }) => <BaseErrorList stylesheet={{ Error, Root: 'div' }} formValue={formValue} />
const WordCount = ({ value, maxWords, ...props }) => {
const count = wordCount(value)
return (
<p {...props} className={classnames('form-message', count > maxWords && 'color-error')}>
{`${count} / ${maxWords} words`}
</p>
)
}
export default class Field extends Component {
static propTypes = {
label: PropTypes.node,
children: PropTypes.element,
Input: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
Self: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
Label: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
ErrorList: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
inputProps: PropTypes.object,
instructions: PropTypes.node,
maxWords: PropTypes.number,
}
static defaultProps = {
Input: DefaultInput,
Label,
ErrorList,
Self,
autoLabel: true,
stopShouting: true,
maxWords: 0,
inputProps: {},
}
constructor(props = {}) {
super(props)
this.state = { dirty: false }
}
render() {
const { dirty } = this.state
const { value, params = {}, errorList = [] } = this.props.formValue
let {
children,
Self,
ErrorList,
Label,
Input,
label,
autoLabel,
labelWidth,
inline,
formValue,
select,
stopShouting,
readOnly,
disabled,
placeholder,
hint,
instructions,
maxWords,
className,
size,
inputProps,
fieldClass,
...rest
} = this.props
if (Input === DefaultInput && readOnly && !rest.onClick) rest.onClick = event => event.target.select()
const showErrors = dirty || params.forceShowErrors
const invalid = errorList.length > 0
className = classnames(className, { error: invalid })
if (!children) {
children = (
<Input
{...rest}
value={value}
onChange={this.onChange}
invalid={invalid}
readOnly={readOnly}
disabled={disabled}
className={className}
placeholder={placeholder}
{...inputProps}
/>
)
} else {
children = React.cloneElement(React.Children.only(children), {
...rest,
value,
onChange: this.onChange,
invalid,
placeholder,
className,
readOnly,
disabled,
...inputProps,
})
}
let { onChange, ...propsWithoutOnChange } = this.props
return (
<Self {...propsWithoutOnChange} onBlur={this.onBlur}>
{autoLabel && <Label {...propsWithoutOnChange} />}
{instructions &&
(typeof instructions == 'string' ? <p className="form-message">{instructions}</p> : instructions)}
{children}
{maxWords > 0 && <WordCount value={value} maxWords={maxWords} />}
{stopShouting && <StopShouting value={value} onChange={this.onChange} />}
{showErrors && <ErrorList formValue={this.formValue} />}
</Self>
)
}
onBlur = event => {
this.setState({ dirty: true })
this.props.onBlur && this.props.onBlur(event)
}
onChange = e => {
let value
if (e && e.target && e.target.value !== undefined) {
e.stopPropagation()
value = e.target.value
} else {
value = e
}
const nextFormValue = this.props.formValue.update(value)
this.props.onChange && this.props.onChange(value, nextFormValue)
this.setState({ dirty: true })
}
}