version 1.0

This commit is contained in:
Maxime Renou 2021-07-07 11:03:35 +02:00
commit 404732ef9d
29 changed files with 26242 additions and 0 deletions

9
.babelrc Normal file
View File

@ -0,0 +1,9 @@
{
"presets": [
["env", {
"modules": false
}],
"stage-0",
"react"
]
}

9
.editorconfig Normal file
View File

@ -0,0 +1,9 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

23
.eslintrc Normal file
View File

@ -0,0 +1,23 @@
{
"parser": "babel-eslint",
"extends": [
"standard",
"standard-react"
],
"env": {
"es6": true
},
"plugins": [
"react"
],
"parserOptions": {
"sourceType": "module"
},
"rules": {
// don't force es6 functions to include space before paren
"space-before-function-paren": 0,
// allow specifying true explicitly for boolean props
"react/jsx-boolean-value": 0
}
}

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
.DS_Store
node_modules/
.idea/
example/

4
.travis.yml Normal file
View File

@ -0,0 +1,4 @@
language: node_js
node_js:
- 9
- 8

28
README.md Normal file
View File

@ -0,0 +1,28 @@
# react-forms
> React form components
[![NPM](https://img.shields.io/npm/v/react-forms.svg)](https://www.npmjs.com/package/react-forms) [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com)
## Install
```bash
npm install --save react-forms
yarn react-forms
```
## Usage
```jsx
import React, { Component } from 'react'
import MyComponent from 'react-forms'
class Example extends Component {
render () {
return (
<MyComponent />
)
}
}
```

8137
dist/index.es.js vendored Normal file

File diff suppressed because it is too large Load Diff

1
dist/index.es.js.map vendored Normal file

File diff suppressed because one or more lines are too long

8155
dist/index.js vendored Normal file

File diff suppressed because it is too large Load Diff

1
dist/index.js.map vendored Normal file

File diff suppressed because one or more lines are too long

65
package.json Normal file
View File

@ -0,0 +1,65 @@
{
"name": "react-forms",
"version": "1.0.0",
"description": "React form components",
"author": "Maxime Renou",
"license": "MIT",
"repository": "bluesquare-packages/react-forms",
"main": "dist/index.js",
"module": "dist/index.es.js",
"jsnext:main": "dist/index.es.js",
"engines": {
"node": ">=8",
"npm": ">=5"
},
"scripts": {
"test": "cross-env CI=1 react-scripts test --env=jsdom",
"test:watch": "react-scripts test --env=jsdom",
"build": "rollup -c",
"start": "rollup -c -w",
"prepare": "npm run build",
"predeploy": "cd example && npm install && npm run build",
"deploy": "gh-pages -d example/build"
},
"peerDependencies": {
"prop-types": "^15.5.4",
"react": "^15.0.0 || ^16.0.0",
"react-dom": "^15.0.0 || ^16.0.0"
},
"devDependencies": {
"@svgr/rollup": "^2.4.1",
"babel-core": "^6.26.3",
"babel-eslint": "^8.2.5",
"babel-plugin-external-helpers": "^6.22.0",
"babel-preset-env": "^1.7.0",
"babel-preset-react": "^6.24.1",
"babel-preset-stage-0": "^6.24.1",
"cross-env": "^5.1.4",
"eslint": "^5.0.1",
"eslint-config-standard": "^11.0.0",
"eslint-config-standard-react": "^6.0.0",
"eslint-plugin-import": "^2.13.0",
"eslint-plugin-node": "^7.0.1",
"eslint-plugin-promise": "^4.0.0",
"eslint-plugin-react": "^7.10.0",
"eslint-plugin-standard": "^3.1.0",
"gh-pages": "^1.2.0",
"react": "^16.4.1",
"react-dom": "^16.4.1",
"react-scripts": "^1.1.4",
"rollup": "^0.64.1",
"rollup-plugin-babel": "^3.0.7",
"rollup-plugin-commonjs": "^9.1.3",
"rollup-plugin-node-resolve": "^3.3.0",
"rollup-plugin-peer-deps-external": "^2.2.0",
"rollup-plugin-postcss": "^1.6.2",
"rollup-plugin-url": "^1.4.0"
},
"files": [
"dist"
],
"dependencies": {
"moment": "^2.24.0",
"react-toastify": "^4.5.2"
}
}

39
rollup.config.js Normal file
View File

@ -0,0 +1,39 @@
import babel from 'rollup-plugin-babel'
import commonjs from 'rollup-plugin-commonjs'
import external from 'rollup-plugin-peer-deps-external'
import postcss from 'rollup-plugin-postcss'
import resolve from 'rollup-plugin-node-resolve'
import url from 'rollup-plugin-url'
import svgr from '@svgr/rollup'
import pkg from './package.json'
export default {
input: 'src/index.js',
output: [
{
file: pkg.main,
format: 'cjs',
sourcemap: true
},
{
file: pkg.module,
format: 'es',
sourcemap: true
}
],
plugins: [
external(),
postcss({
modules: true
}),
url(),
svgr(),
babel({
exclude: 'node_modules/**',
plugins: [ 'external-helpers' ]
}),
resolve(),
commonjs()
]
}

5
src/.eslintrc Normal file
View File

@ -0,0 +1,5 @@
{
"env": {
"jest": true
}
}

49
src/CheckBox.js Normal file
View File

@ -0,0 +1,49 @@
import React, {Component} from 'react';
import {FormConsumer} from "./FormContext";
import FormItemError from "./FormItemError";
export default class CheckBox extends Component
{
render()
{
return (
<FormConsumer>
{(data) => this.renderCheckBox(data)}
</FormConsumer>
)
}
renderCheckBox(data)
{
let props = {...this.props}
if (props.children) delete props.children
if (props.onChangeValue) delete props.onChangeValue
return (
<div className="form-check">
<input
type="checkbox"
checked={this.props.value}
onChange={(e) => this.props.onChangeValue(e.target.checked)}
{...props} />
{this.renderLabel(data)}
<FormItemError name={this.props.name} data={data} />
</div>
)
}
renderLabel(data)
{
if (this.props.children.length) {
return (
<label className="form-check-label" htmlFor={this.props.id}>{this.props.children}</label>
)
}
}
}
CheckBox.defaultProps = {
name: 'checkbox',
id: 'checkbox',
className: 'form-check-input',
onChangeValue: (checked) => {}
}

60
src/DatePicker.js Normal file
View File

@ -0,0 +1,60 @@
import React, {Component} from 'react';
import moment from 'moment';
import {FormConsumer} from "./FormContext";
import FormItemError from "./FormItemError";
export default class DatePicker extends Component
{
render()
{
return (
<FormConsumer>
{(data) => this.renderDatePicker(data)}
</FormConsumer>
)
}
renderDatePicker(data)
{
const value = this.props.value
let props = {...this.props}
if (props.children) delete props.children
if (props.value) delete props.value
if (props.onChangeValue) delete props.onChangeValue
return (
<div className="form-group">
{this.renderLabel(data)}
<input
value={this.parseValue(value)}
onChange={(e) => this.props.onChangeValue(moment(e.target.value))} {...props} />
<p className="text-muted">DatePicker {this.props.type}</p>
<FormItemError name={this.props.name} data={data} />
</div>
)
}
parseValue(value)
{
if (this.props.type == 'date') return moment(value).format('YYYY-MM-DD')
if (this.props.type == 'time') return moment(value).format('HH:mm:ss')
return moment(value).format('YYYY-MM-DDTHH:mm')
}
renderLabel(data)
{
if (this.props.label) {
return (
<label htmlFor={this.props.id}>{this.props.label}</label>
)
}
}
}
DatePicker.defaultProps = {
name: 'input',
id: 'input',
className: 'form-control',
type: 'datetime-local',
value: null,
onChangeValue: (text) => {}
}

71
src/FilePicker.js Normal file
View File

@ -0,0 +1,71 @@
import React, {Component} from 'react';
import {FormConsumer} from "./FormContext";
import FormItemError from "./FormItemError";
export default class FilePicker extends Component
{
render()
{
return (
<FormConsumer>
{(data) => this.renderFilePicker(data)}
</FormConsumer>
)
}
renderFilePicker(data)
{
const value = this.props.value
let props = {...this.props}
if (props.children) delete props.children
if (props.onChangeValue) delete props.onChangeValue
if (!(typeof props.value instanceof File)) delete props.value
return (
<div className="form-group">
{this.renderLabel(data)}
<input
type="file"
onChange={(e) => this.props.onChangeValue(e.target.value)} {...props} />
{this.renderValue(value)}
<FormItemError name={this.props.name} data={data} />
</div>
)
}
renderValue(value)
{
if (value instanceof File)
return <p className="text-muted">{value.name} {this.renderDeleteButton()}</p>
if (value !== null)
return <p className="text-muted">{value} {this.renderDeleteButton()}</p>
return null
}
renderDeleteButton()
{
return (
<i className="fa fa-trash" onClick={() => {
this.props.onChangeValue(null)
}}/>
);
}
renderLabel(data)
{
if (this.props.label) {
return (
<label htmlFor={this.props.id}>{this.props.label}</label>
)
}
}
}
FilePicker.defaultProps = {
name: 'file-picker',
id: 'file-picker',
className: 'form-control',
value: null,
onChangeValue: (file) => {}
}

87
src/Form.js Normal file
View File

@ -0,0 +1,87 @@
import React, {Component} from 'react';
import FormContext from './FormContext';
import {toast, ToastContainer} from 'react-toastify';
import FormTranslationContext, {FormTranslationConsumer} from './FormTranslationContext'
class ContextAwareForm extends Component
{
constructor(props) {
super(props)
this.state = {
data: props.data
}
this.displayError = true
this.handleError(props)
}
componentWillReceiveProps(props)
{
this.handleError(props)
this.setState({
data: props.data
})
}
handleError(props)
{
if (props.data && props.data.error && props.toast && this.displayError == true) {
toast.error(this.props.translator.renderText(props.data.error))
this.displayError = false
}
}
render()
{
return (
<FormContext.Provider value={this.state.data}>
<form onSubmit={this._onSubmit}>
{this.renderError()}
{this.props.children}
</form>
<ToastContainer />
</FormContext.Provider>
)
}
_onSubmit = (e) => {
e.preventDefault()
this.props.onSubmit()
this.displayError = true
return false;
}
renderError()
{
if (this.state.data && this.state.data.error && !this.props.toast)
{
return (
<p className="alert alert-danger">{this.props.translator.renderText(this.state.data.error)}</p>
)
}
return null
}
}
export default class Form extends Component
{
render()
{
return (
<FormTranslationConsumer>
{(translator) => (
<ContextAwareForm translator={translator} {...this.props}/>
)}
</FormTranslationConsumer>
)
}
}
Form.defaultProps = {
data: null,
toast: false,
onSubmit: () => {
console.log('[Form] submitted')
}
}

17
src/FormContext.js Normal file
View File

@ -0,0 +1,17 @@
import React, { Component } from 'react'
const FormContext = React.createContext({ data: null });
export default FormContext
export class FormConsumer extends Component
{
render()
{
return (
<FormContext.Consumer>
{(props) => this.props.children(props)}
</FormContext.Consumer>
)
}
}

22
src/FormItemError.js Normal file
View File

@ -0,0 +1,22 @@
import React, {Component} from 'react';
import {FormTranslationConsumer} from './FormTranslationContext'
export default class FormItemError extends Component
{
render() {
const data = this.props.data
if (!data || !data.errors || !data.errors[this.props.name]) return null
return (
<FormTranslationConsumer>
{(translator) => this.renderError(data, translator)}
</FormTranslationConsumer>
)
}
renderError(data, translator)
{
return <p className="text-danger">{translator.renderText(data.errors[this.props.name].error)}</p>
}
}

View File

@ -0,0 +1,19 @@
import React, { Component } from 'react'
const FormTranslationContext = React.createContext({
renderText: (text) => (text)
});
export default FormTranslationContext
export class FormTranslationConsumer extends Component
{
render()
{
return (
<FormTranslationContext.Consumer>
{(props) => this.props.children(props)}
</FormTranslationContext.Consumer>
)
}
}

46
src/Input.js Normal file
View File

@ -0,0 +1,46 @@
import React, {Component} from 'react';
import {FormConsumer} from "./FormContext";
import FormItemError from "./FormItemError";
export default class Input extends Component
{
render()
{
return (
<FormConsumer>
{(data) => this.renderInput(data)}
</FormConsumer>
)
}
renderInput(data)
{
let props = {...this.props}
if (props.children) delete props.children
if (props.onChangeValue) delete props.onChangeValue
return (
<div className="form-group">
{this.renderLabel(data)}
<input onChange={(e) => this.props.onChangeValue(e.target.value)} {...props} />
<FormItemError name={this.props.name} data={data} />
</div>
)
}
renderLabel(data)
{
if (this.props.label) {
return (
<label htmlFor={this.props.id}>{this.props.label}</label>
)
}
}
}
Input.defaultProps = {
name: 'input',
id: 'input',
className: 'form-control',
type: 'text',
onChangeValue: (text) => {}
}

27
src/Option.js Normal file
View File

@ -0,0 +1,27 @@
import React, {Component} from 'react';
import {OptionGroupConsumer} from "./OptionGroup";
export default class Option extends Component
{
render()
{
return (
<OptionGroupConsumer>
{(data) => this.renderOption(data)}
</OptionGroupConsumer>
)
}
renderOption(data)
{
let props = {...this.props}
return (
<option {...props}>{this.props.children}</option>
)
}
}
Option.defaultProps = {
value: 'option'
}

83
src/OptionGroup.js Normal file
View File

@ -0,0 +1,83 @@
import React, {Component} from 'react';
import {FormConsumer} from './FormContext';
import FormItemError from "./FormItemError";
const OptionGroupContext = React.createContext({});
export class OptionGroupConsumer extends Component
{
render()
{
return (
<OptionGroupContext.Consumer>
{(props) => this.props.children(props)}
</OptionGroupContext.Consumer>
)
}
}
export default class OptionGroup extends Component
{
render()
{
return (
<FormConsumer>
{(data) => this.renderOptionGroup(data)}
</FormConsumer>
)
}
renderOptionGroup(data)
{
const props = {...this.props}
return (
<div className="form-group">
{this.renderLabel()}
<select {...this.props.data} {...props} onChange={(e) => this._onChange(e)}>
{this.renderChildren()}
</select>
<FormItemError name={this.props.name} data={data} />
</div>
)
}
_onChange = (e) => {
if (this.props.multiple) {
let value = []
const options = e.target.options;
for (let i = 0, l = options.length; i < l; i++) {
if (options[i].selected) {
value.push(options[i].value);
}
}
this.props.onChangeValue(value)
}
else {
this.props.onChangeValue(e.target.value)
}
}
renderLabel()
{
if (!this.props.label) return null
return (
<label htmlFor={this.props.id}>{this.props.label}</label>
)
}
renderChildren()
{
return (
<OptionGroupContext.Provider value={{}}>
{this.props.children}
</OptionGroupContext.Provider>
)
}
}
OptionGroup.defaultProps = {
name: 'select',
className: 'form-control',
onChangeValue: (value) => {}
}

48
src/Radio.js Normal file
View File

@ -0,0 +1,48 @@
import React, {Component} from 'react';
import {RadioGroupConsumer} from "./RadioGroup";
export default class Radio extends Component
{
render()
{
return (
<RadioGroupConsumer>
{(data) => this.renderRadio(data)}
</RadioGroupConsumer>
)
}
renderRadio(data)
{
let props = {...this.props}
if (props.children) delete props.children
return (
<div className="form-check">
<input
type="radio"
checked={data.value === this.props.value}
onChange={(e) => {
if (e.target.checked)
data.onChangeValue(this.props.value)
}}
{...props} />
{this.renderLabel(data)}
</div>
)
}
renderLabel(data)
{
if (this.props.children.length) {
return (
<label className="form-check-label" htmlFor={this.props.id}>{this.props.children}</label>
)
}
}
}
Radio.defaultProps = {
value: 'option',
className: 'form-check-input'
}

66
src/RadioGroup.js Normal file
View File

@ -0,0 +1,66 @@
import React, {Component} from 'react';
import {FormConsumer} from './FormContext';
import FormItemError from "./FormItemError";
const RadioGroupContext = React.createContext({});
export class RadioGroupConsumer extends Component
{
render()
{
return (
<RadioGroupContext.Consumer>
{(props) => this.props.children(props)}
</RadioGroupContext.Consumer>
)
}
}
export default class RadioGroup extends Component
{
render()
{
return (
<FormConsumer>
{(data) => this.renderRadioGroup(data)}
</FormConsumer>
)
}
renderRadioGroup(data)
{
return (
<div className="form-options mb-3">
{this.renderLabel()}
{this.renderChildren()}
<FormItemError name={this.props.name} data={data} />
</div>
)
}
renderLabel()
{
if (!this.props.label) return null
return (
<p className="mb-0 mt-2">{this.props.label}</p>
)
}
renderChildren()
{
return (
<RadioGroupContext.Provider value={{
onChangeValue: (value) => this.props.onChangeValue(value),
value: this.props.value
}}>
{this.props.children}
</RadioGroupContext.Provider>
)
}
}
RadioGroup.defaultProps = {
name: 'radio',
onChangeValue: (value) => {}
}

13
src/Submit.js Normal file
View File

@ -0,0 +1,13 @@
import React, {Component} from 'react';
export default class Submit extends Component {
render() {
return (
<button type="submit" {...this.props}>{this.props.children}</button>
)
}
}
Submit.defaultProps = {
className: 'btn btn-primary'
}

44
src/TextArea.js Normal file
View File

@ -0,0 +1,44 @@
import React, {Component} from 'react';
import {FormConsumer} from "./FormContext";
import FormItemError from "./FormItemError";
export default class TextArea extends Component
{
render()
{
return (
<FormConsumer>
{(data) => this.renderTextArea(data)}
</FormConsumer>
)
}
renderTextArea(data)
{
let props = {...this.props}
if (props.onChangeValue) delete props.onChangeValue
return (
<div className="form-group">
{this.renderLabel(data)}
<textarea onChange={(e) => this.props.onChangeValue(e.target.value)} {...props} />
<FormItemError name={this.props.name} data={data} />
</div>
)
}
renderLabel(data)
{
if (this.props.label) {
return (
<label htmlFor={this.props.id}>{this.props.label}</label>
)
}
}
}
TextArea.defaultProps = {
name: 'textarea',
id: 'textarea',
className: 'form-control',
onChangeValue: (text) => {}
}

14
src/index.js Normal file
View File

@ -0,0 +1,14 @@
import CheckBox from './CheckBox'
import Form from './Form'
import Input from './Input'
import Option from './Option'
import OptionGroup from './OptionGroup'
import Radio from './Radio'
import RadioGroup from './RadioGroup'
import Submit from './Submit'
import TextArea from './TextArea'
import FormTranslationContext from './FormTranslationContext'
import DatePicker from './DatePicker'
import FilePicker from './FilePicker'
export { CheckBox, Form, Input, Option, OptionGroup, RadioGroup, Radio, DatePicker, FilePicker, Submit, TextArea, FormTranslationContext }

9096
yarn.lock Normal file

File diff suppressed because it is too large Load Diff