From 240e77aec9d16176bdb111d4519ef11a62939216 Mon Sep 17 00:00:00 2001 From: alan bount Date: Thu, 12 May 2016 00:13:53 -0400 Subject: [PATCH 01/21] Add first pass at a bootstrap UI --- packages/uniforms-bootstrap4/README.md | 27 +++++++ packages/uniforms-bootstrap4/package.json | 58 ++++++++++++++ packages/uniforms-bootstrap4/src/autoid.js | 6 ++ .../uniforms-bootstrap4/src/buildErrors.js | 22 ++++++ packages/uniforms-bootstrap4/src/buildGrid.js | 23 ++++++ .../src/components/fields/AutoField.js | 49 ++++++++++++ .../src/components/fields/BoolField.js | 39 +++++++++ .../src/components/fields/DateField.js | 46 +++++++++++ .../src/components/fields/ErrorsField.js | 29 +++++++ .../src/components/fields/FormGroup.js | 79 +++++++++++++++++++ .../src/components/fields/ListAddField.js | 17 ++++ .../src/components/fields/ListDelField.js | 20 +++++ .../src/components/fields/ListField.js | 43 ++++++++++ .../src/components/fields/ListItemField.js | 32 ++++++++ .../src/components/fields/LongTextField.js | 31 ++++++++ .../src/components/fields/NestField.js | 34 ++++++++ .../src/components/fields/NumField.js | 39 +++++++++ .../src/components/fields/RadioField.js | 41 ++++++++++ .../src/components/fields/SelectField.js | 49 ++++++++++++ .../src/components/fields/SubmitField.js | 48 +++++++++++ .../src/components/fields/TextField.js | 44 +++++++++++ .../src/components/forms/AutoForm.js | 29 +++++++ .../src/components/forms/BaseForm.js | 13 +++ .../src/components/forms/QuickForm.js | 29 +++++++ .../src/components/forms/ValidatedForm.js | 13 +++ .../components/forms/ValidatedQuickForm.js | 29 +++++++ packages/uniforms-bootstrap4/src/index.js | 21 +++++ 27 files changed, 910 insertions(+) create mode 100644 packages/uniforms-bootstrap4/README.md create mode 100644 packages/uniforms-bootstrap4/package.json create mode 100644 packages/uniforms-bootstrap4/src/autoid.js create mode 100644 packages/uniforms-bootstrap4/src/buildErrors.js create mode 100644 packages/uniforms-bootstrap4/src/buildGrid.js create mode 100644 packages/uniforms-bootstrap4/src/components/fields/AutoField.js create mode 100644 packages/uniforms-bootstrap4/src/components/fields/BoolField.js create mode 100644 packages/uniforms-bootstrap4/src/components/fields/DateField.js create mode 100644 packages/uniforms-bootstrap4/src/components/fields/ErrorsField.js create mode 100644 packages/uniforms-bootstrap4/src/components/fields/FormGroup.js create mode 100644 packages/uniforms-bootstrap4/src/components/fields/ListAddField.js create mode 100644 packages/uniforms-bootstrap4/src/components/fields/ListDelField.js create mode 100644 packages/uniforms-bootstrap4/src/components/fields/ListField.js create mode 100644 packages/uniforms-bootstrap4/src/components/fields/ListItemField.js create mode 100644 packages/uniforms-bootstrap4/src/components/fields/LongTextField.js create mode 100644 packages/uniforms-bootstrap4/src/components/fields/NestField.js create mode 100644 packages/uniforms-bootstrap4/src/components/fields/NumField.js create mode 100644 packages/uniforms-bootstrap4/src/components/fields/RadioField.js create mode 100644 packages/uniforms-bootstrap4/src/components/fields/SelectField.js create mode 100644 packages/uniforms-bootstrap4/src/components/fields/SubmitField.js create mode 100644 packages/uniforms-bootstrap4/src/components/fields/TextField.js create mode 100644 packages/uniforms-bootstrap4/src/components/forms/AutoForm.js create mode 100644 packages/uniforms-bootstrap4/src/components/forms/BaseForm.js create mode 100644 packages/uniforms-bootstrap4/src/components/forms/QuickForm.js create mode 100644 packages/uniforms-bootstrap4/src/components/forms/ValidatedForm.js create mode 100644 packages/uniforms-bootstrap4/src/components/forms/ValidatedQuickForm.js create mode 100644 packages/uniforms-bootstrap4/src/index.js diff --git a/packages/uniforms-bootstrap4/README.md b/packages/uniforms-bootstrap4/README.md new file mode 100644 index 000000000..4cc4c3b8a --- /dev/null +++ b/packages/uniforms-bootstrap4/README.md @@ -0,0 +1,27 @@ +# uniforms-bootstrap4 + +> bootstrap4 UI components for `uniforms`. + +## Install + +```sh +$ npm install uniforms-bootstrap4 +``` + +For more in depth documentation see: [https://github.com/vazco/uniforms/](https://github.com/vazco/uniforms/). + +## Roadmap + +- [x] build out basic, grid enabled, FormGroup wrapper for + label, input, error/help +- [ ] improve `FormGroup` + - [ ] link FormGroup with the form `schema` + - [ ] link FormGroup with the form `error` object (per field) + - [ ] pass the Form `grid` prop down to all _child_ field inputs, into `FormGroup` +- [ ] more configuration options +- [ ] make a bootstrap3 version (similar to bootstrap4, just a few differences) +- [ ] core `uniforms` improvement: track `touched` for inputs +- [ ] core `uniforms` improvement: allow `autosave` + configuration: `onBlur`, `onInput`, `onChange` + (run: validate without throwing errors on other fields true=submit) + diff --git a/packages/uniforms-bootstrap4/package.json b/packages/uniforms-bootstrap4/package.json new file mode 100644 index 000000000..d7c6839a2 --- /dev/null +++ b/packages/uniforms-bootstrap4/package.json @@ -0,0 +1,58 @@ +{ + "name": "uniforms-bootstrap4", + "version": "1.0.0-rc.2", + "main": "lib/index.js", + "jsnext:main": "src/index.js", + "description": "Bootstrap4 UI components for uniforms.", + "repository": "https://github.com/vazco/uniforms/tree/master/packages/uniforms-bootstrap4", + "homepage": "https://github.com/vazco/uniforms/", + "license": "MIT", + "bugs": { + "url": "https://github.com/vazco/uniforms/issues" + }, + "keyword": [ + "forms", + "meteor", + "react", + "schema" + ], + "scripts": { + "lint": "./node_modules/.bin/eslint src", + "test": "true", + "cover": "true", + "build": "./node_modules/.bin/babel --out-dir lib src", + "watch": "./node_modules/.bin/babel --out-dir lib src --watch", + "prepublish": "npm run lint && npm run test && npm run build" + }, + "devDependencies": { + "babel-cli": "^6.8.0", + "babel-eslint": "^6.0.4", + "babel-preset-es2015": "^6.6.0", + "babel-preset-react": "^6.5.0", + "babel-preset-stage-0": "^6.5.0", + "eslint": "^2.9.0", + "eslint-config-vazco": "^1.0.0", + "eslint-plugin-babel": "^3.2.0", + "eslint-plugin-react": "^5.0.1" + }, + "peerDependencies": { + "react": "^15.0.2", + "uniforms": "^1.0.0-rc.2" + }, + "babel": { + "presets": [ + "es2015", + "react", + "stage-0" + ] + }, + "eslintConfig": { + "extends": [ + "vazco" + ] + }, + "dependencies": { + "classnames": "^2.2.5", + "shortid": "^2.2.6" + } +} diff --git a/packages/uniforms-bootstrap4/src/autoid.js b/packages/uniforms-bootstrap4/src/autoid.js new file mode 100644 index 000000000..9880e129a --- /dev/null +++ b/packages/uniforms-bootstrap4/src/autoid.js @@ -0,0 +1,6 @@ +import shortid from 'shortid'; + +export default function autoid (id) { + return id ? id : shortid(); +} + diff --git a/packages/uniforms-bootstrap4/src/buildErrors.js b/packages/uniforms-bootstrap4/src/buildErrors.js new file mode 100644 index 000000000..ac5cb6db2 --- /dev/null +++ b/packages/uniforms-bootstrap4/src/buildErrors.js @@ -0,0 +1,22 @@ +import React from 'react'; + +const buildErrors = (error, schema) => { + console.log('buildErrors (WIP need error & schema)', { error, schema }); + return ( + error && schema ? + + {(!!error.reason && !(error.details || !error.details.map)) && ( + {error.reason} + )} + {(!!error.details && error.details.map) && error.details.map(error => + + {schema.messageForError(error.type, error.name, null, error.details && error.details.value)} + + )} + + : '' + ); +}; + +export default buildErrors; + diff --git a/packages/uniforms-bootstrap4/src/buildGrid.js b/packages/uniforms-bootstrap4/src/buildGrid.js new file mode 100644 index 000000000..87b16b525 --- /dev/null +++ b/packages/uniforms-bootstrap4/src/buildGrid.js @@ -0,0 +1,23 @@ + +function buildGridLabel(size, value, side) { + return (side === 'label' ? `col-${size}-${value}` : `col-${size}-${(12 - value)}`); +} +export default function buildGrid(grid, side) { + if (!grid) return ''; + + if (typeof grid === 'number') { + // grid value is a number [1-11] + // if grid=1, label.col-sm-1 & input.col-sm-11 + // if grid=2, label.col-sm-2 & input.col-sm-10 + // if grid=3, label.col-sm-3 & input.col-sm-9 + // if grid=4, label.col-sm-4 & input.col-sm-8 + return buildGridLabel('sm', grid, side); + } + + if (typeof grid === 'object') { + // grid value is an object config + // eg: { xs: 6, sm: 4, md: 3 } + return Object.keys(grid).map((value, size) => buildGridLabel(size, value, side)); + } +} + diff --git a/packages/uniforms-bootstrap4/src/components/fields/AutoField.js b/packages/uniforms-bootstrap4/src/components/fields/AutoField.js new file mode 100644 index 000000000..162ffdc22 --- /dev/null +++ b/packages/uniforms-bootstrap4/src/components/fields/AutoField.js @@ -0,0 +1,49 @@ +import {connectField} from 'uniforms'; +import {createElement} from 'react'; + +import NumField from './NumField'; +import BoolField from './BoolField'; +import DateField from './DateField'; +import ListField from './ListField'; +import NestField from './NestField'; +import TextField from './TextField'; +import SelectField from './SelectField'; + +// eslint-disable-next-line complexity +const Auto = props => { + let component; + + let uniforms = props.field.uniforms; + if (uniforms) { + if (typeof uniforms === 'string' || + typeof uniforms === 'function') { + component = uniforms; + } + + if (typeof uniforms.component === 'string' || + typeof uniforms.component === 'function') { + component = uniforms.component; + } + } + + if (component === undefined) { + if (props.field.allowedValues) { + component = SelectField; + } else { + switch (props.field.type) { + case Date: component = DateField; break; + case Array: component = ListField; break; + case Number: component = NumField; break; + case Object: component = NestField; break; + case String: component = TextField; break; + case Boolean: component = BoolField; break; + + default: throw new Error(`Unsupported field type: ${props.field.type.toString()}`); + } + } + } + + return createElement(component, props); +}; + +export default connectField(Auto, {includeInChain: false}); diff --git a/packages/uniforms-bootstrap4/src/components/fields/BoolField.js b/packages/uniforms-bootstrap4/src/components/fields/BoolField.js new file mode 100644 index 000000000..a80033a4f --- /dev/null +++ b/packages/uniforms-bootstrap4/src/components/fields/BoolField.js @@ -0,0 +1,39 @@ +import React from 'react'; +import classnames from 'classnames'; +import {connectField} from 'uniforms'; +import autoid from '../../autoid'; +import FormGroup from './FormGroup'; + +const Bool = ({ + field: {inline, optional}, + disabled, error, schema, + label, name, id, value, + placeholder, + inputClassName, + onChange, ...props +}) => { + const idNice = autoid(id); + return ( + +
+ +
+
+ ); +}; + +export default connectField(Bool); + diff --git a/packages/uniforms-bootstrap4/src/components/fields/DateField.js b/packages/uniforms-bootstrap4/src/components/fields/DateField.js new file mode 100644 index 000000000..9ec72678f --- /dev/null +++ b/packages/uniforms-bootstrap4/src/components/fields/DateField.js @@ -0,0 +1,46 @@ +import React from 'react'; +import classnames from 'classnames'; +import {connectField} from 'uniforms'; +import autoid from '../../autoid'; +import FormGroup from './FormGroup'; + +const dateFormat = value => value && value.toISOString().slice(0, -8); +const dateParse = (timestamp, onChange) => { + let date = new Date(timestamp); + if (date.getFullYear() < 10000) { + onChange(date); + } +}; + +const Date_ = ({ + disabled, error, schema, + field: {max, min, optional}, + label, name, id, value, + inputClassName, + onChange, ...props +}) => { + const idNice = autoid(id); + return ( + + dateParse(event.target.valueAsNumber, onChange)} + type="datetime-local" + value={dateFormat(value)} + /> + + ); +}; + +Date_.displayName = 'Date'; + +export default connectField(Date_); diff --git a/packages/uniforms-bootstrap4/src/components/fields/ErrorsField.js b/packages/uniforms-bootstrap4/src/components/fields/ErrorsField.js new file mode 100644 index 000000000..db35e360c --- /dev/null +++ b/packages/uniforms-bootstrap4/src/components/fields/ErrorsField.js @@ -0,0 +1,29 @@ +import React from 'react'; +import classnames from 'classnames'; +import {BaseField} from 'uniforms'; + +const ErrorsField = ({className, children, ...props}, {uniforms: {error, schema}}) => + !error ? null : ( +
+ {children} + + +
+ ) +; + +ErrorsField.contextTypes = BaseField.contextTypes; + +export default ErrorsField; diff --git a/packages/uniforms-bootstrap4/src/components/fields/FormGroup.js b/packages/uniforms-bootstrap4/src/components/fields/FormGroup.js new file mode 100644 index 000000000..569506785 --- /dev/null +++ b/packages/uniforms-bootstrap4/src/components/fields/FormGroup.js @@ -0,0 +1,79 @@ +/** + * This is a consistent wrapper for labels and inputs + * + * Ideally I want to use this as a wrapper for each field type + * and this will render the wrapper, label, error, etc... + */ + +import React from 'react'; +import classnames from 'classnames'; +import {BaseField} from 'uniforms'; +import {connectField} from 'uniforms'; +import autoid from '../../autoid'; +import buildGrid from '../../buildGrid'; +import buildErrors from '../../buildErrors'; + +const FormGroup = ({ + field, + id, // string id (optional, auto-generated if empty) + label, // string label (or false) + grid, // grid is either a int [1-11] or object {xs:6,sm:4,md:2} + labelClassName, // class name for the label + wrapClassName, // class name for the div wrapping the input(s) + inputClassName, // class name for the input .form-control + helpClassName, // class name for the help text (default: 'text-muted') + className, // class name for the whole .form-group + help, // help text + showHelpAndErrors, // boolean, if true, show help & error + disabled, + error, // TODO need to get the error object here + schema, // TODO need to get the schema + ...props} +) => { + const idNice = autoid(id); + + console.log('FormGroup (WIP need error & schema)', { + field, id, + label, grid, help, + error, schema, + }); + + const helpNice = ( + help && (!error || showHelpAndErrors) ? + {help} + : '' + ); + + const errorNice = buildErrors(error, schema); + const required = field && !field.optional; + + return ( +
+ + {label && ( + + )} + + {grid || wrapClassName ? +
+ {props.children} {errorNice} {helpNice} +
+ : + {props.children} {errorNice} {helpNice} + } + +
+ ); +} + +export default connectField(FormGroup); + diff --git a/packages/uniforms-bootstrap4/src/components/fields/ListAddField.js b/packages/uniforms-bootstrap4/src/components/fields/ListAddField.js new file mode 100644 index 000000000..502f1ffbf --- /dev/null +++ b/packages/uniforms-bootstrap4/src/components/fields/ListAddField.js @@ -0,0 +1,17 @@ +import React from 'react'; +import classnames from 'classnames'; +import {connectField} from 'uniforms'; + +const ListAdd = ({className, parent: {field: {maxCount}, value: parentValue, onChange}, value, ...props}) => { + const limitReached = !(maxCount <= parentValue.length); + + return ( + limitReached && onChange(parentValue.concat([value]))} + /> + ); +}; + +export default connectField(ListAdd, {includeParent: true, includeDefault: false}); diff --git a/packages/uniforms-bootstrap4/src/components/fields/ListDelField.js b/packages/uniforms-bootstrap4/src/components/fields/ListDelField.js new file mode 100644 index 000000000..eb4c30f03 --- /dev/null +++ b/packages/uniforms-bootstrap4/src/components/fields/ListDelField.js @@ -0,0 +1,20 @@ +import React from 'react'; +import classnames from 'classnames'; +import {connectField} from 'uniforms'; + +const ListFieldDel = ({className, disabled, name, parent: {field: {minCount}, value, onChange}, ...props}) => { + const fieldIndex = +name.slice(1 + name.lastIndexOf('.')); + const limitReached = !(minCount >= value.length); + + return ( + limitReached && onChange( + [].concat(value.slice(0, fieldIndex)) + .concat(value.slice(1 + fieldIndex)) + )} + /> + ); +}; + +export default connectField(ListFieldDel, {includeParent: true, includeDefault: false}); diff --git a/packages/uniforms-bootstrap4/src/components/fields/ListField.js b/packages/uniforms-bootstrap4/src/components/fields/ListField.js new file mode 100644 index 000000000..e604ccd85 --- /dev/null +++ b/packages/uniforms-bootstrap4/src/components/fields/ListField.js @@ -0,0 +1,43 @@ +import React from 'react'; +import classnames from 'classnames'; +import {Children} from 'react'; +import {connectField} from 'uniforms'; +import {joinName} from 'uniforms'; + +import ListAddField from './ListAddField'; +import ListItemField from './ListItemField'; + +// eslint-disable-next-line max-len, no-unused-vars +const List = ({className, disabled, children, error, field: {optional}, label, name, value, onChange, ...props}) => +
+ {label && ( +
+ + + +
+ )} + +
+ + {children ? ( + value.map((item, index) => + Children.map(children, child => + React.cloneElement(child, { + key: index, + label: null, + name: joinName(name, child.props.name && child.props.name.replace('$', index)) + }) + ) + ) + ) : ( + value.map((item, index) => + + ) + )} +
+; + +export default connectField(List, {includeInChain: false}); diff --git a/packages/uniforms-bootstrap4/src/components/fields/ListItemField.js b/packages/uniforms-bootstrap4/src/components/fields/ListItemField.js new file mode 100644 index 000000000..c05929a95 --- /dev/null +++ b/packages/uniforms-bootstrap4/src/components/fields/ListItemField.js @@ -0,0 +1,32 @@ +import React from 'react'; +import {Children} from 'react'; +import {connectField} from 'uniforms'; +import {joinName} from 'uniforms'; + +import AutoField from './AutoField'; +import ListDelField from './ListDelField'; + +const ListItem = props => +
+ + +
+ {props.children ? ( + Children.map(props.children, child => + React.cloneElement(child, { + name: joinName(props.name, child.props.name), + label: null, + style: { + margin: 0, + ...child.props.style + } + }) + ) + ) : ( + + )} +
+
+; + +export default connectField(ListItem, {includeInChain: false}); diff --git a/packages/uniforms-bootstrap4/src/components/fields/LongTextField.js b/packages/uniforms-bootstrap4/src/components/fields/LongTextField.js new file mode 100644 index 000000000..4fe8e76f7 --- /dev/null +++ b/packages/uniforms-bootstrap4/src/components/fields/LongTextField.js @@ -0,0 +1,31 @@ +import React from 'react'; +import classnames from 'classnames'; +import autoid from '../../autoid'; +import {connectField} from 'uniforms'; + +// eslint-disable-next-line max-len +const LongText = ({ + field: {optional}, + disabled, error, schema, + label, name, id, value, + placeholder, + inputClassName, + onChange, ...props +}) => { + const idNice = autoid(id); + return ( + +