UNPKG

@bigfishtv/cockpit

Version:

199 lines (183 loc) 4.81 kB
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> ) } @withFormValue 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 }) } }