<commit_before>// @flow
import {format as d3Format} from 'd3-format'
import toKebabCase from 'lodash/kebabCase'
import React, {PureComponent} from 'react'

/**
 * Store the input value in the state. Locally controlled component.
 */
class StateToValue extends PureComponent {
  state = {
    value: this.props.value === null ? undefined : this.props.value
  }

  componentWillReceiveProps (nextProps) {
    if (nextProps.value !== this.state.value) {
      this.setState({value: nextProps.value})
    }
  }

  _onChange = e => {
    const {value} = e.target
    this.setState({value})
    const {onChange} = this.props
    if (onChange) onChange(e)
  }
}

export class Input extends StateToValue {
  render () {
    const {name, label, placeholder, units, ...props} = this.props
    const {value} = this.state

    if (units) {
      return (
        <div className='InputWithUnits'>
          <input
            className='form-control'
            placeholder={placeholder || units || label || name}
            id={propsToId(this.props)}
            name={name}
            {...props}
            onChange={this._onChange}
            value={value}
          />
          {units &&
            <span className='InputUnits'>
              {units}
            </span>}
        </div>
      )
    } else {
      return (
        <input
          className='form-control'
          placeholder={placeholder || label || name}
          id={propsToId(this.props)}
          name={name}
          {...props}
          onChange={this._onChange}
          value={value}
        />
      )
    }
  }
}

export class Group extends PureComponent {
  render () {
    const {className, label, children} = this.props
    return (
      <div className={['form-group', className].filter(c => !!c).join(' ')}>
        {label &&
          <label htmlFor={propsToId(this.props)}>
            {label}
          </label>}
        {children}
      </div>
    )
  }
}

export class Checkbox extends PureComponent {
  render () {
    const {label, ...restProps} = this.props
    const id = propsToId(this.props)
    return (
      <div className='checkbox'>
        <label htmlFor={id}>
          <input type='checkbox' {...restProps} id={id} /> {label}
        </label>
      </div>
    )
  }
}

export class File extends PureComponent {
  render () {
    const id = propsToId(this.props)
    return (
      <Group {...this.props} id={id}>
        <Input type='file' {...this.props} id={id} />
      </Group>
    )
  }
}

export class Text extends PureComponent {
  render () {
    const id = propsToId(this.props)
    return (
      <Group {...this.props} id={id}>
        <Input type='text' {...this.props} id={id} />
      </Group>
    )
  }
}

/** A textarea, NB it uses the value attribute, rather than chidren, to set the contents */
export class TextArea extends StateToValue {
  render () {
    const id = propsToId(this.props)
    const {value, ...rest} = this.props
    return (
      <Group {...rest} id={id}>
        <textarea className='form-control' {...rest} id={id}>
          {value}
        </textarea>
      </Group>
    )
  }
}

const preventDefaultIfFocused = e => {
  // http://stackoverflow.com/questions/17614844
  if (e.target === document.activeElement) e.preventDefault()
}

export class Number extends PureComponent {
  render () {
    const id = propsToId(this.props)
    return (
      <Group {...this.props} id={id}>
        <Input
          type='number'
          min={0}
          // http://stackoverflow.com/questions/9712295
          onWheel={preventDefaultIfFocused}
          {...this.props}
          id={id}
        />
      </Group>
    )
  }
}

export class Slider extends StateToValue {
  render () {
    const {format, output, ...props} = this.props
    const outputFormat = d3Format(format || ',r')
    const id = propsToId(props)
    return (
      <Group {...props} id={id}>
        {output &&
          <output className='pull-right' htmlFor={id}>
            {outputFormat(this.state.value)}
          </output>}
        <input
          className='form-control'
          type='range'
          {...props}
          id={id}
          onChange={this._onChange}
          value={this.state.value}
        /><commit_msg>feat(input): add validation to number input<commit_after>// @flow
import {format as d3Format} from 'd3-format'
import toKebabCase from 'lodash/kebabCase'
import isNumber from 'lodash/isNumber'
import React, {PureComponent} from 'react'

/**
 * Store the input value in the state. Locally controlled component.
 */
class StateToValue extends PureComponent {
  state = {
    value: this.props.value === null ? undefined : this.props.value
  }

  componentWillReceiveProps (nextProps) {
    if (nextProps.value !== this.state.value) {
      this.setState({value: nextProps.value})
    }
  }

  _onChange = e => {
    const {value} = e.target
    this.setState({value})
    const {onChange} = this.props
    if (onChange) onChange(e)
  }
}

export class Input extends StateToValue {
  render () {
    const {name, label, placeholder, units, ...props} = this.props
    const {value} = this.state

    if (units) {
      return (
        <div className='InputWithUnits'>
          <input
            className='form-control'
            placeholder={placeholder || units || label || name}
            id={propsToId(this.props)}
            name={name}
            {...props}
            onChange={this._onChange}
            value={value}
          />
          {units &&
            <span className='InputUnits'>
              {units}
            </span>}
        </div>
      )
    } else {
      return (
        <input
          className='form-control'
          placeholder={placeholder || label || name}
          id={propsToId(this.props)}
          name={name}
          {...props}
          onChange={this._onChange}
          value={value}
        />
      )
    }
  }
}

export class Group extends PureComponent {
  render () {
    const {className, label, children} = this.props
    return (
      <div className={['form-group', className].filter(c => !!c).join(' ')}>
        {label &&
          <label htmlFor={propsToId(this.props)}>
            {label}
          </label>}
        {children}
      </div>
    )
  }
}

export class Checkbox extends PureComponent {
  render () {
    const {label, ...restProps} = this.props
    const id = propsToId(this.props)
    return (
      <div className='checkbox'>
        <label htmlFor={id}>
          <input type='checkbox' {...restProps} id={id} /> {label}
        </label>
      </div>
    )
  }
}

export class File extends PureComponent {
  render () {
    const id = propsToId(this.props)
    return (
      <Group {...this.props} id={id}>
        <Input type='file' {...this.props} id={id} />
      </Group>
    )
  }
}

export class Text extends PureComponent {
  render () {
    const id = propsToId(this.props)
    return (
      <Group {...this.props} id={id}>
        <Input type='text' {...this.props} id={id} />
      </Group>
    )
  }
}

/** A textarea, NB it uses the value attribute, rather than chidren, to set the contents */
export class TextArea extends StateToValue {
  render () {
    const id = propsToId(this.props)
    const {value, ...rest} = this.props
    return (
      <Group {...rest} id={id}>
        <textarea className='form-control' {...rest} id={id}>
          {value}
        </textarea>
      </Group>
    )
  }
}

const preventDefaultIfFocused = e => {
  // http://stackoverflow.com/questions/17614844
  if (e.target === document.activeElement) e.preventDefault()
}

export class Number extends PureComponent {
  state = {
    error: false
  }

  _onChange = (e: Event & {target: HTMLInputElement}) => {
    const {max, min, onChange} = this.props
    const value = parseFloat(e.target.value)
    let error = false
    if (!isNumber(value)) {
      error = true
    }
    if (isNumber(min)) {
      if (value < min) {
        error = true
      }
    } else if (value < 0) {
      error = true
    }
    if (isNumber(max) && value > max) {
      error = true
    }
    this.setState({error})
    if (!error) {
      this.props.onChange(e)
    }
  }

  _renderHelpBlock() {
    const {max, min} = this.props
    let helpText = ' '
    if (isNumber(min)) {
      helpText += min
    } else {
      helpText += 0
    }
    if (isNumber(max)) {
      helpText = `Enter a number between ${helpText}-${max}`
    } else {
      helpText = `Enter a number greater than ${helpText}`
    }
    return (
      <span className='help-block'>{helpText}</span>
    )
  }

  render () {
    const id = propsToId(this.props)
    const {className, onChange, ...propsLessOnChange} = this.props
    const {error} = this.state
    const groupProps = {...this.props}
    if (error) {
      groupProps.className = className ? `${className} has-error` : 'has-error'
    }
    return (
      <Group {...groupProps} id={id}>
        <Input
          type='number'
          min={0}
          // http://stackoverflow.com/questions/9712295
          onChange={this._onChange}
          onWheel={preventDefaultIfFocused}
          {...propsLessOnChange}
          id={id}
          />
        {this._renderHelpBlock()}
      </Group>
    )
  }
}

export class Slider extends StateToValue {
  render () {
    const {format, output, ...props} = this.props
    const outputFormat = d3Format(format || ',r')
    const id = propsToId(props)
    return (
      <Group {...props} id={id}>
        {output &&
          <output className='pull-right' htmlFor={id}>
            {outputFormat(this.state.value)}
          </output>}
        <input
          className='form-control'
          type='range'
          {...props}
          id={id}
          onChange={this._onChange}
          value={this.state.value}
        />
{'fraction_non_alphanumeric': 0.1464183964183964, 'fraction_numerical': 0.00407000407000407, 'mean_word_length': 1.9911746804625685, 'pattern_counts': {'":': 0, '<': 71, '<?xml version=': 0, '>': 77, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2847438', 'n_tokens_mistral': 3033, 'n_tokens_neox': 3002, 'n_words': 694}