├── .babelrc ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── package.json ├── src ├── array.jsx ├── checkbox.jsx ├── date-picker.jsx ├── file │ ├── index.jsx │ ├── preview.jsx │ └── upload-button.jsx ├── index.js ├── multiple-checkbox.jsx ├── object.jsx ├── radio.jsx ├── select-with-method.jsx ├── select.jsx ├── styles.js ├── tags.jsx ├── text.js ├── textarea.jsx └── toggle.jsx ├── watch.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-2", "react"] 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | lib/ 3 | node_modules/ 4 | build/ 5 | npm-debug.log 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .* 2 | src/ 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Nicolás López 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Material UI - Simple React Form 2 | 3 | [Simple React Form](https://github.com/nicolaslopezj/simple-react-form) is a powerful framework that simplifies the use of forms in React and [React Native](https://github.com/nicolaslopezj/simple-react-form#react-native). This is a set of components that use Material UI. 4 | 5 | To use this fields, import the field and pass it as ```type``` to the ```Field``` component. 6 | 7 | ```js 8 |
this.setState(changes)}> 9 | 10 | 11 | 12 | ``` 13 | 14 | ## Components 15 | 16 | List of the components 17 | 18 | ### [Checkbox](https://github.com/nicolaslopezj/simple-react-form-material-ui/blob/master/src/fields/checkbox.jsx) 19 | 20 | ```js 21 | import Checkbox from 'simple-react-form-material-ui/lib/checkbox' 22 | ``` 23 | 24 | Type: ```Boolean``` 25 | 26 | ### [Date Picker](https://github.com/nicolaslopezj/simple-react-form-material-ui/blob/master/src/fields/date-picker.jsx) 27 | 28 | Renders the [material-ui date picker](http://www.material-ui.com/#/components/date-picker) 29 | 30 | ```js 31 | import DatePicker from 'simple-react-form-material-ui/lib/date-picker' 32 | ``` 33 | 34 | Type: ```Date``` 35 | 36 | ### [Multiple Checkbox](https://github.com/nicolaslopezj/simple-react-form-material-ui/blob/master/src/fields/multiple-checkbox.jsx) 37 | 38 | Select multiple items from a array 39 | 40 | ```js 41 | import MultipleCheckbox from 'simple-react-form-material-ui/lib/multiple-checkbox' 42 | ``` 43 | 44 | Type: ```[String|Number]``` 45 | 46 | Props: 47 | - ```options```: A array of 48 | - ```label``` ```String```: The label of the option 49 | - ```value``` ```String|Number```: The value 50 | - ```description``` ```String``` Optional: A description that will be rendered below the option 51 | 52 | ### [Radio](https://github.com/nicolaslopezj/simple-react-form-material-ui/blob/master/src/fields/radio.jsx) 53 | 54 | Select one item from a array 55 | 56 | ```js 57 | import Radio from 'simple-react-form-material-ui/lib/radio' 58 | ``` 59 | 60 | Type: ```String|Number``` 61 | 62 | Props: 63 | - ```options```: A array of 64 | - ```label``` ```String```: The label of the option 65 | - ```value``` ```String|Number```: The value 66 | - ```description``` ```String``` Optional: A description that will be rendered below the option 67 | 68 | ### [Select With Method](https://github.com/nicolaslopezj/simple-react-form-material-ui/blob/master/src/fields/select-with-method.jsx) 69 | 70 | A text field that searchs items with meteor methods 71 | 72 | ```js 73 | import SelectWithMethod from 'simple-react-form-material-ui/lib/select-with-method' 74 | ``` 75 | 76 | Type: ```String|Number``` 77 | 78 | Props: 79 | - ```multi``` ```Boolean``` Optional: Allow to select multiple items. 80 | - ```methodName``` ```String```: Meteor method that recieves the search string and returns an array of 81 | - ```label``` ```String```: The visible text. 82 | - ```value``` ```String|Number```: The value. 83 | - ```color``` ```String``` Optional: The background color of the chip 84 | - ```image``` ```String``` Optional: The url of the image 85 | - ```initals``` ```String``` Optional: The initals of the chip. Don't provide this if image is present. 86 | - ```labelMethodName``` ```String```: Meteor method that recieves the value and must return the item description. If ```multi``` is set to true, this will recieve an array of the values and must return an array with the items descriptions in the same order. Item description is the same as the one returned in ```methodName```: 87 | - ```label``` ```String```: The visible text. 88 | - ```value``` ```String|Number```: The value. 89 | - ```color``` ```String``` Optional: The background color of the chip 90 | - ```image``` ```String``` Optional: The url of the image 91 | - ```initals``` ```String``` Optional: The initals of the chip. Don't provide this if image is present. 92 | - ```connection``` Optional, defaults to ```Meteor```: A Meteor connection. 93 | - ```waitTime``` Optional, defaults to ```400```: Time with no changes that activates the search. 94 | - ```create``` ```Function``` Optional: A function that creates a document and pass the value in a callback. 95 | - ```createLabel``` ```Function``` Optional: A function that recieves the search input and returns the create label. 96 | - ```canCreate``` ```Function``` Optional, defaults to ```() => true```: A function that recieves the search input and returns a ```Boolean``` indicating if ```create``` can be called. 97 | 98 | ### [Select](https://github.com/nicolaslopezj/simple-react-form-material-ui/blob/master/src/fields/select.jsx) 99 | 100 | Select one item from a array in a select field 101 | 102 | ```js 103 | import Select from 'simple-react-form-material-ui/lib/select' 104 | ``` 105 | 106 | Type: ```String|Number``` 107 | 108 | Props: 109 | - ```options```: A array of 110 | - ```label``` ```String```: The label of the option 111 | - ```value``` ```String|Number```: The value 112 | 113 | ### [Tags](https://github.com/nicolaslopezj/simple-react-form-material-ui/blob/master/src/fields/tags.jsx) 114 | 115 | Create a array of Strings. 116 | 117 | ```js 118 | import Tags from 'simple-react-form-material-ui/lib/tags' 119 | ``` 120 | 121 | Type: ```[String]``` 122 | 123 | ### [Text](https://github.com/nicolaslopezj/simple-react-form-material-ui/blob/master/src/fields/text-field.jsx) 124 | 125 | ```js 126 | import Text from 'simple-react-form-material-ui/lib/text' 127 | ``` 128 | 129 | Type: ```String``` 130 | 131 | Props: 132 | - ```fieldType``` ```String``` Optional: The type of the input. Example: number, email, password. 133 | 134 | ### [Textarea](https://github.com/nicolaslopezj/simple-react-form-material-ui/blob/master/src/fields/textarea.jsx) 135 | 136 | A String with multiple lunes 137 | 138 | ```js 139 | import Textarea from 'simple-react-form-material-ui/lib/textarea' 140 | ``` 141 | 142 | Type: ```String``` 143 | 144 | ### [Toggle](https://github.com/nicolaslopezj/simple-react-form-material-ui/blob/master/src/fields/toggle.jsx) 145 | 146 | ```js 147 | import Toggle from 'simple-react-form-material-ui/lib/toggle' 148 | ``` 149 | 150 | Type: ```Boolean``` 151 | 152 | ### [Object](https://github.com/nicolaslopezj/simple-react-form-material-ui/blob/master/src/fields/object.jsx) 153 | 154 | ```js 155 | import ObjectComponent from 'simple-react-form-material-ui/lib/object' 156 | ``` 157 | 158 | Type: ```Object``` 159 | 160 | Usage: 161 | 162 | ```js 163 |
164 | 165 | 166 | 167 | 168 | 169 | ``` 170 | 171 | ### [Array](https://github.com/nicolaslopezj/simple-react-form-material-ui/blob/master/src/fields/array.jsx) 172 | 173 | ```js 174 | import ArrayComponent from 'simple-react-form-material-ui/lib/array' 175 | ``` 176 | 177 | Type: ```Array``` 178 | 179 | Usage: 180 | 181 | ```js 182 |
183 | 184 | 185 | 186 | 187 | 188 | ``` 189 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simple-react-form-material-ui", 3 | "version": "1.7.13", 4 | "license": "MIT", 5 | "author": "Nicolás López ", 6 | "description": "Material UI components for simple react form", 7 | "main": "./lib", 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/nicolaslopezj/simple-react-form/tree/master/material-ui" 11 | }, 12 | "keywords": [ 13 | "react-component" 14 | ], 15 | "dependencies": { 16 | "underscore": "^1.8.3" 17 | }, 18 | "devDependencies": { 19 | "babel-cli": "^6.18.0", 20 | "babel-eslint": "^7.1.1", 21 | "babel-polyfill": "^6.5.0", 22 | "babel-preset-es2015": "^6.18.0", 23 | "babel-preset-react": "^6.5.0", 24 | "babel-preset-stage-2": "^6.5.0", 25 | "brfs": "^1.4.3", 26 | "browserify": "^13.0.0", 27 | "budo": "^9.4.1", 28 | "colors": "^1.1.2", 29 | "eslint": "^3.12.2", 30 | "eslint-plugin-react": "^6.8.0", 31 | "material-ui": ">=0.15.2", 32 | "node-sass": "^4.1.1", 33 | "react": ">=0.14.0", 34 | "react-addons-test-utils": "^15.4.1", 35 | "react-dom": ">=0.14.0", 36 | "react-tap-event-plugin": ">=1.0.0", 37 | "run-browser-babel": "^5.2.0", 38 | "simple-react-form": "^1.9.1", 39 | "tape": "^4.0.0", 40 | "uglify-js": "^2.6.1" 41 | }, 42 | "peerDependencies": { 43 | "material-ui": ">=0.15.2", 44 | "react": ">=15.0.0", 45 | "react-tap-event-plugin": ">=1.0.0", 46 | "simple-react-form": ">=1.9.0" 47 | }, 48 | "scripts": { 49 | "build": "babel ./src --out-dir ./lib", 50 | "prepublish": "npm run build", 51 | "watch": "node watch.js" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/array.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Paper from 'material-ui/Paper' 3 | import IconButton from 'material-ui/IconButton' 4 | import RaisedButton from 'material-ui/RaisedButton' 5 | import {ArrayComponent} from 'simple-react-form' 6 | 7 | const styles = { 8 | label: { 9 | color: 'rgba(0,0,0,0.5)', 10 | marginBottom: 5, 11 | fontSize: 12 12 | } 13 | } 14 | 15 | const propTypes = { 16 | ...ArrayComponent.propTypes, 17 | parentClassName: React.PropTypes.string, 18 | childrenClassName: React.PropTypes.string, 19 | useSmallSpace: React.PropTypes.bool, 20 | smallRemoveButtonTooltipPosition: React.PropTypes.string 21 | } 22 | 23 | const defaultProps = { 24 | ...ArrayComponent.defaultProps, 25 | childrenClassName: '', 26 | parentClassName: '', 27 | useSmallSpace: false, 28 | smallRemoveButtonTooltipPosition: 'bottom-center' 29 | } 30 | 31 | export default class MaterialArray extends ArrayComponent { 32 | 33 | renderChildrenItem ({ index, children }) { 34 | if (this.props.useSmallSpace) return this.renderChildrenSmallItem({ index, children }) 35 | return ( 36 |
37 | 38 | {this.renderChildrenItemWithContext({index, children})} 39 |
40 | {this.renderRemoveButton(index)} 41 |
42 |
43 |
44 | ) 45 | } 46 | 47 | renderChildrenSmallItem ({ index, children }) { 48 | return ( 49 |
50 |
51 | {this.renderChildrenItemWithContext({index, children})} 52 |
53 |
54 | {this.renderSmallRemoveButton(index)} 55 |
56 |
57 | ) 58 | } 59 | 60 | renderRemoveButton (index) { 61 | if (this.props.disabled) return 62 | return this.removeItem(index)}/> 63 | } 64 | 65 | renderSmallRemoveButton (index) { 66 | if (this.props.disabled) return 67 | return ( 68 | this.removeItem(index)} 71 | tooltip={this.props.removeLabel} 72 | tooltipPosition={this.props.smallRemoveButtonTooltipPosition} 73 | > 74 | clear 75 | 76 | ) 77 | } 78 | 79 | renderAddButton () { 80 | if (!this.props.showAddButton) return 81 | if (this.props.disabled) return 82 | if (this.props.useSmallSpace) return this.renderSmallAddButton() 83 | return this.addItem()}/> 84 | } 85 | 86 | renderSmallAddButton () { 87 | return ( 88 |
89 | this.addItem()} 92 | tooltip={this.props.addLabel} 93 | > 94 | add 95 | 96 |
97 | ) 98 | } 99 | 100 | render () { 101 | return ( 102 |
103 |
{this.props.label}
104 |
{this.props.errorMessage}
105 |
106 | {this.renderChildren()} 107 |
108 |
109 | {this.renderAddButton()} 110 |
111 |
112 | ) 113 | } 114 | } 115 | 116 | MaterialArray.propTypes = propTypes 117 | MaterialArray.defaultProps = defaultProps 118 | -------------------------------------------------------------------------------- /src/checkbox.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Checkbox from 'material-ui/Checkbox' 3 | import * as Colors from 'material-ui/styles/colors' 4 | import {FieldType, registerType} from 'simple-react-form' 5 | 6 | const propTypes = { 7 | ...FieldType.propTypes 8 | } 9 | 10 | const defaultProps = { 11 | 12 | } 13 | 14 | export default class CheckboxComponent extends React.Component { 15 | 16 | render () { 17 | return ( 18 |
19 | this.props.onChange(!this.props.value)} 24 | {...this.props.passProps} 25 | /> 26 | {this.props.errorMessage} 27 |
28 | ) 29 | } 30 | } 31 | 32 | CheckboxComponent.propTypes = propTypes 33 | CheckboxComponent.defaultProps = defaultProps 34 | 35 | registerType({ 36 | type: 'checkbox', 37 | component: CheckboxComponent 38 | }) 39 | 40 | registerType({ 41 | type: 'boolean', 42 | component: CheckboxComponent 43 | }) 44 | -------------------------------------------------------------------------------- /src/date-picker.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import DatePicker from 'material-ui/DatePicker' 3 | import {FieldType, registerType} from 'simple-react-form' 4 | 5 | const propTypes = { 6 | ...FieldType.propTypes 7 | } 8 | 9 | const defaultProps = { 10 | 11 | } 12 | 13 | export default class DatePickerComponent extends React.Component { 14 | 15 | openDialog () { 16 | if (this.props.disabled) return 17 | this.refs.input.openDialog() 18 | } 19 | 20 | render () { 21 | return ( 22 |
23 | this.props.onChange(date)} 32 | {...this.props.passProps} /> 33 |
34 | ) 35 | } 36 | } 37 | 38 | DatePickerComponent.propTypes = propTypes 39 | DatePickerComponent.defaultProps = defaultProps 40 | 41 | registerType({ 42 | type: 'date-picker', 43 | component: DatePickerComponent 44 | }) 45 | -------------------------------------------------------------------------------- /src/file/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import {FieldType, registerType} from 'simple-react-form' 3 | import _ from 'underscore' 4 | 5 | import UploadButton from './upload-button' 6 | import Preview from './preview' 7 | import styles from '../styles' 8 | 9 | const propTypes = { 10 | ...FieldType.propTypes, 11 | /** 12 | * A function that recieves { file, onProgress, onReady, onError }. 13 | * onProgress input is progress, a number from 0 to 1. 14 | * onReady inputs are { url, meta }, 15 | * url is the url of the file, meta is a object with whatever you want. 16 | * onError input is message. 17 | */ 18 | upload: React.PropTypes.func.isRequired, 19 | 20 | /** 21 | * A function that recieves { file, onReady, onError }. 22 | * file is the information of the file (includes the meta from before). 23 | * onReady is a function with no input. 24 | * onError input is message. 25 | */ 26 | delete: React.PropTypes.func, 27 | 28 | /** 29 | * A mime type to match to accept the files. 30 | * If image prop is set and image prop is also set, this mime type is going to stay. 31 | * If this prop is not set and image prop is, the mime type will be 'image/*' 32 | */ 33 | accept: React.PropTypes.string, 34 | 35 | /** 36 | * Only accept images 37 | */ 38 | image: React.PropTypes.bool, 39 | 40 | /** 41 | * Accept multiple files. If you are using simple-schema and this is true, 42 | * you must set [Object] to the type. 43 | */ 44 | multi: React.PropTypes.bool, 45 | 46 | /** 47 | * Pass the styles props to the preview 48 | */ 49 | previewStyles: React.PropTypes.object, 50 | 51 | /** 52 | * This delete the files that are not used 53 | */ 54 | deleteNotUsedFiles: React.PropTypes.bool, 55 | 56 | /** 57 | * The label of the button 58 | */ 59 | uploadLabel: React.PropTypes.any, 60 | 61 | /** 62 | * The label of the delete button 63 | */ 64 | deleteLabel: React.PropTypes.any, 65 | 66 | /** 67 | * The text that is shown when deleting 68 | */ 69 | confirmDeleteText: React.PropTypes.any 70 | } 71 | 72 | const defaultProps = { 73 | accept: false, 74 | image: false, 75 | multi: false, 76 | previewStyles: {}, 77 | deleteLabel: 'Delete', 78 | confirmDeleteText: 'Do you want to delete this file?', 79 | delete: ({ file, onReady, onError }) => onReady() 80 | } 81 | 82 | export default class Component extends React.Component { 83 | 84 | constructor (props) { 85 | super(props) 86 | this.state = {} 87 | this.uploads = [] 88 | this.toDelete = [] 89 | this.limbo = [] 90 | 91 | /* $(window).unload(() => { This will be deactivated until better implementation is made 92 | this.componentWillUnmount() 93 | }) */ 94 | } 95 | 96 | onSuccess () { 97 | this.toDelete.map((file) => { 98 | this.props.delete({ 99 | file, 100 | onReady: () => {}, 101 | 102 | onError: (message) => { 103 | alert(message) 104 | } 105 | }) 106 | }) 107 | this.toDelete = [] 108 | this.limbo = [] 109 | } 110 | 111 | onError (message) { 112 | // Todo something here 113 | } 114 | 115 | componentWillUnmount () { 116 | if (!this.limbo.length) return 117 | if (this.props.hasOwnProperty('deleteNotUsedFiles')) { 118 | if (!this.props.deleteNotUsedFiles) { 119 | return 120 | } 121 | } else { 122 | if (this.props.form.props.hasOwnProperty('onChange')) { 123 | return 124 | } 125 | } 126 | 127 | this.limbo.map((file) => { 128 | this.props.delete({ 129 | file, 130 | onReady: () => {}, 131 | 132 | onError: (message) => { 133 | alert(message) 134 | } 135 | }) 136 | }) 137 | } 138 | 139 | onReady (upload, file) { 140 | if (this.props.multi) { 141 | var newValue = _.clone(this.props.value) || [] 142 | newValue.push(file) 143 | this.props.onChange(newValue) 144 | } else { 145 | this.props.onChange(file) 146 | } 147 | this.limbo.push(file) 148 | } 149 | 150 | startUpload (file, base64) { 151 | var upload = { 152 | key: _.uniqueId('uploadComponent'), 153 | file, 154 | base64, 155 | isUploading: true 156 | } 157 | this.uploads.push(upload) 158 | this.forceUpdate() 159 | 160 | this.props.upload({ 161 | file, 162 | onProgress: (progress) => { 163 | upload.progress = progress 164 | this.forceUpdate() 165 | }, 166 | 167 | onReady: ({ url, meta }) => { 168 | this.onReady(upload, { url, meta }) 169 | const index = this.uploads.indexOf(upload) 170 | this.uploads.splice(index, 1) 171 | this.forceUpdate() 172 | }, 173 | 174 | onError: (message) => { 175 | this.onError(upload, message) 176 | upload.isUploading = false 177 | upload.error = message 178 | this.forceUpdate() 179 | } 180 | }) 181 | } 182 | 183 | deleteFile (file) { 184 | this.toDelete.push(_.clone(file)) 185 | if (this.props.multi) { 186 | var value = _.clone(this.props.value) 187 | const index = value.indexOf(file) 188 | value.splice(index, 1) 189 | this.props.onChange(value) 190 | } else { 191 | this.props.onChange(null) 192 | } 193 | } 194 | 195 | renderPreviews () { 196 | const uploadingPreviews = this.uploads.map((upload, index) => { 197 | return this.deleteFile(upload.file)}/> 206 | }) 207 | 208 | const value = this.props.multi ? (this.props.value || []) : this.props.value ? [this.props.value] : [] 209 | const previews = value.map((file, index) => { 210 | return this.deleteFile(file)} 218 | /> 219 | }) 220 | 221 | return ( 222 |
223 | {previews} 224 | {uploadingPreviews} 225 |
226 | ) 227 | } 228 | 229 | renderUploadButton () { 230 | if (!this.props.multi && (this.props.value || this.uploads.length)) return 231 | const props = { 232 | accept: this.props.accept ? this.props.accept : this.props.image ? 'image/*' : '', 233 | label: this.props.image ? this.props.uploadLabel || 'Upload image' : this.props.uploadLabel || 'Upload file', 234 | multi: !!this.props.multi, 235 | onUpload: this.startUpload.bind(this), 236 | passBase64: !!this.props.image 237 | } 238 | return 239 | } 240 | 241 | render () { 242 | return ( 243 |
244 |
245 | {this.props.label} 246 |
247 | {this.renderPreviews()} 248 | {this.renderUploadButton()} 249 |
250 | {this.props.errorMessage} 251 |
252 |
253 | ) 254 | } 255 | } 256 | 257 | Component.propTypes = propTypes 258 | Component.defaultProps = defaultProps 259 | 260 | registerType({ 261 | type: 'file', 262 | component: Component 263 | }) 264 | -------------------------------------------------------------------------------- /src/file/preview.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import LinearProgress from 'material-ui/LinearProgress' 3 | import CircularProgress from 'material-ui/CircularProgress' 4 | import * as Colors from 'material-ui/styles/colors' 5 | 6 | const styles = { 7 | image: { 8 | marginBottom: 10, 9 | marginRight: 10, 10 | cursor: 'pointer', 11 | display: 'inline-block', 12 | maxHeight: 150, 13 | maxWidth: '100%', 14 | backgroundColor: 'white', 15 | borderRadius: 2, 16 | boxShadow: '0 1px 6px rgba(0,0,0,0.12), 0 1px 4px rgba(0,0,0,0.12)' 17 | }, 18 | imageLoading: { 19 | maxHeight: 150, 20 | maxWidth: '100%', 21 | marginBottom: -5, 22 | opacity: 0.5 23 | }, 24 | progress: { 25 | margin: '0 auto', 26 | display: 'block', 27 | marginTop: -50 28 | } 29 | } 30 | 31 | const propTypes = { 32 | base64: React.PropTypes.string, 33 | url: React.PropTypes.string, 34 | isImage: React.PropTypes.bool, 35 | isUploading: React.PropTypes.bool, 36 | progress: React.PropTypes.number, 37 | onDelete: React.PropTypes.func, 38 | deleteLabel: React.PropTypes.any, 39 | confirmDeleteText: React.PropTypes.any, 40 | styles: React.PropTypes.object.isRequired 41 | } 42 | 43 | export default class FilesPreview extends React.Component { 44 | 45 | askDelete () { 46 | if (confirm(this.props.confirmDeleteText)) { // we should use a react component hereº 47 | this.props.onDelete() 48 | } 49 | } 50 | 51 | renderLoading () { 52 | return ( 53 |
54 | 55 |
56 | ) 57 | } 58 | 59 | renderBase64 () { 60 | return ( 61 |
62 | 63 | 64 |
65 | ) 66 | } 67 | 68 | renderPreviewImage () { 69 | return ( 70 | 74 | ) 75 | } 76 | 77 | renderPreview () { 78 | return ( 79 |
80 | {this.props.url} 81 | {this.props.deleteLabel} 82 |
83 | ) 84 | } 85 | 86 | render () { 87 | if (this.props.isUploading) { 88 | if (this.props.isImage) { 89 | return this.renderBase64() 90 | } else { 91 | return this.renderLoading() 92 | } 93 | } else { 94 | if (this.props.isImage) { 95 | return this.renderPreviewImage() 96 | } else { 97 | return this.renderPreview() 98 | } 99 | } 100 | } 101 | 102 | } 103 | 104 | FilesPreview.propTypes = propTypes 105 | -------------------------------------------------------------------------------- /src/file/upload-button.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import RaisedButton from 'material-ui/RaisedButton' 4 | import _ from 'underscore' 5 | 6 | const propTypes = { 7 | accept: React.PropTypes.string, 8 | label: React.PropTypes.any, 9 | multi: React.PropTypes.bool, 10 | onUpload: React.PropTypes.func.isRequired, 11 | passBase64: React.PropTypes.bool 12 | } 13 | 14 | const defaultProps = { 15 | label: 'Upload image', 16 | multi: false, 17 | accept: null, 18 | passBase64: false 19 | } 20 | 21 | export default class Component extends React.Component { 22 | 23 | openFileDialog () { 24 | var fileInputDom = ReactDOM.findDOMNode(this.refs.input) 25 | fileInputDom.click() 26 | } 27 | 28 | handleFile (event) { 29 | _.keys(event.target.files).map((index) => { 30 | const file = event.target.files[index] 31 | 32 | if (this.props.passBase64) { 33 | const reader = new FileReader() 34 | reader.onload = (upload) => { 35 | const base64 = upload.target.result 36 | this.props.onUpload(file, base64) 37 | } 38 | 39 | reader.readAsDataURL(file) 40 | } else { 41 | this.props.onUpload(file) 42 | } 43 | }) 44 | } 45 | 46 | render () { 47 | return ( 48 |
49 | 52 | 59 |
60 | ) 61 | } 62 | 63 | } 64 | 65 | Component.propTypes = propTypes 66 | Component.defaultProps = defaultProps 67 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import {registerType} from 'simple-react-form' 2 | import ArrayField from './array' 3 | import ObjectField from './object' 4 | 5 | export { default as Checkbox } from './checkbox' 6 | export { default as Radio } from './radio' 7 | export { default as DatePicker } from './date-picker' 8 | export { default as MultipleCheckbox } from './multiple-checkbox' 9 | export { default as SelectWithMethod } from './select-with-method' 10 | export { default as Select } from './select' 11 | export { default as Tags } from './tags' 12 | export { default as TextField } from './text' 13 | export { default as Textarea } from './textarea' 14 | export { default as File } from './file' 15 | export { default as Toggle } from './toggle' 16 | 17 | registerType({ 18 | type: 'array', 19 | component: ArrayField 20 | }) 21 | 22 | registerType({ 23 | type: 'object', 24 | component: ObjectField 25 | }) 26 | 27 | export const ArrayComponent = ArrayField 28 | export const ObjectComponent = ObjectField 29 | -------------------------------------------------------------------------------- /src/multiple-checkbox.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Checkbox from 'material-ui/Checkbox' 3 | import * as Colors from 'material-ui/styles/colors' 4 | import {FieldType, registerType} from 'simple-react-form' 5 | import styles from './styles' 6 | import _ from 'underscore' 7 | 8 | const propTypes = { 9 | ...FieldType.propTypes, 10 | /** 11 | * The options for the checkbox. 12 | */ 13 | options: React.PropTypes.arrayOf(React.PropTypes.shape({ 14 | label: React.PropTypes.string.isRequired, 15 | disabled: React.PropTypes.bool, 16 | value: React.PropTypes.oneOfType([ 17 | React.PropTypes.string, 18 | React.PropTypes.number 19 | ]).isRequired, 20 | description: React.PropTypes.string 21 | })).isRequired 22 | } 23 | 24 | const defaultProps = { 25 | 26 | } 27 | 28 | export default class MultipleCheckboxComponent extends React.Component { 29 | 30 | onCheck (value, currentVal) { 31 | var newVal = [] 32 | if (_.contains(currentVal, value)) { 33 | newVal = _.without(currentVal, value) 34 | } else { 35 | newVal = _.union(currentVal, [value]) 36 | } 37 | 38 | this.props.onChange(newVal) 39 | } 40 | 41 | renderOptions () { 42 | const currentVal = this.props.value || [] 43 | return this.props.options.map(option => { 44 | return ( 45 |
46 | this.onCheck(option.value, currentVal)} 49 | label={option.label} 50 | disabled={this.props.disabled || option.disabled} 51 | {...this.props.passProps} 52 | /> 53 |
this.onCheck(option.value, currentVal)}> 56 | {(option.description || '').split('\n').map((text, index) =>
{text}
)} 57 |
58 |
59 | ) 60 | }) 61 | } 62 | 63 | render () { 64 | return ( 65 |
66 |
67 | {this.props.label} 68 |
69 | {this.renderOptions()} 70 |
{this.props.errorMessage}
71 |
72 | ) 73 | } 74 | } 75 | 76 | MultipleCheckboxComponent.propTypes = propTypes 77 | MultipleCheckboxComponent.defaultProps = defaultProps 78 | 79 | registerType({ 80 | type: 'multiple-checkbox', 81 | component: MultipleCheckboxComponent 82 | }) 83 | -------------------------------------------------------------------------------- /src/object.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Paper from 'material-ui/Paper' 3 | import {ObjectComponent} from 'simple-react-form' 4 | 5 | const styles = { 6 | label: { 7 | color: 'rgba(0,0,0,0.5)', 8 | marginBottom: 5, 9 | fontSize: 12 10 | } 11 | } 12 | 13 | export default class MaterialObject extends ObjectComponent { 14 | 15 | render () { 16 | return ( 17 | 18 |
{this.props.label}
19 |
{this.props.errorMessage}
20 | {this.getChildrenComponents()} 21 |
22 | ) 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/radio.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import RadioButton from 'material-ui/RadioButton' 3 | import * as Colors from 'material-ui/styles/colors' 4 | import {FieldType, registerType} from 'simple-react-form' 5 | import styles from './styles' 6 | 7 | const propTypes = { 8 | ...FieldType.propTypes, 9 | /** 10 | * The options for the select input. Each item must have label and value. 11 | */ 12 | options: React.PropTypes.arrayOf(React.PropTypes.shape({ 13 | label: React.PropTypes.string.isRequired, 14 | value: React.PropTypes.oneOfType([ 15 | React.PropTypes.string, 16 | React.PropTypes.number 17 | ]).isRequired, 18 | description: React.PropTypes.string 19 | })).isRequired 20 | } 21 | 22 | const defaultProps = { 23 | 24 | } 25 | 26 | export default class RadioComponent extends React.Component { 27 | 28 | renderItems () { 29 | return this.props.options.map((item) => { 30 | return ( 31 |
32 | this.props.onChange(item.value)} 36 | disabled={this.props.disabled} 37 | style={{ marginBotton: 16, marginTop: 16 }} 38 | /> 39 |
this.props.onChange(item.value)}> 42 | {(item.description || '').split('\n').map((text, index) =>
{text}
)} 43 |
44 |
45 | ) 46 | }) 47 | } 48 | 49 | render () { 50 | return ( 51 |
52 |
53 | {this.props.label} 54 |
55 | {this.renderItems()} 56 |
{this.props.errorMessage}
57 |
58 | ) 59 | } 60 | } 61 | 62 | RadioComponent.propTypes = propTypes 63 | RadioComponent.defaultProps = defaultProps 64 | 65 | registerType({ 66 | type: 'radio', 67 | component: RadioComponent 68 | }) 69 | -------------------------------------------------------------------------------- /src/select-with-method.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import AutoComplete from 'material-ui/AutoComplete' 3 | import MenuItem from 'material-ui/MenuItem' 4 | import _ from 'underscore' 5 | import {FieldType, registerType} from 'simple-react-form' 6 | import Chip from 'material-ui/Chip' 7 | import * as Colors from 'material-ui/styles/colors' 8 | import Avatar from 'material-ui/Avatar' 9 | import FontIcon from 'material-ui/FontIcon' 10 | 11 | const propTypes = { 12 | ...FieldType.propTypes, 13 | /** 14 | * Allow to select multiple items. 15 | */ 16 | multi: React.PropTypes.bool, 17 | /** 18 | * Meteor method that recieves the search string and returns an array of items 19 | * with 'label' and 'value' attributes. 20 | */ 21 | methodName: React.PropTypes.string.isRequired, 22 | /** 23 | * Meteor method that recieves the value and must return the label. If 24 | * ```multi``` is set to true, it will recieve an array and it must return an 25 | * with the labels in the same order. 26 | */ 27 | labelMethodName: React.PropTypes.string.isRequired, 28 | /** 29 | * A Meteor connection. 30 | */ 31 | connection: React.PropTypes.any, 32 | /** 33 | * Time with no changes that activates the search. 34 | */ 35 | waitTime: React.PropTypes.number, 36 | /** 37 | * A function that creates a document and pass the value in a callback 38 | */ 39 | create: React.PropTypes.func, 40 | /** 41 | * A function that returns the create label 42 | */ 43 | createLabel: React.PropTypes.func, 44 | /** 45 | * A function that returns if a value can be created 46 | */ 47 | canCreate: React.PropTypes.func 48 | } 49 | 50 | const defaultProps = { 51 | multi: false, 52 | waitTime: 400, 53 | createLabel: (search) => `Create '${search}'`, 54 | canCreate: () => true 55 | } 56 | 57 | export default class SelectWithMethodComponent extends React.Component { 58 | 59 | constructor (props) { 60 | super(props) 61 | this.state = { 62 | dataSource: [], 63 | selected: null, 64 | items: [], 65 | knownItems: [], 66 | response: [], 67 | isFetchingData: false, 68 | isFetchingLabel: false, 69 | hasTitleFor: null, 70 | searchText: '' 71 | } 72 | 73 | this.debouncedSearch = _.debounce(this.search.bind(this), this.props.waitTime) 74 | } 75 | 76 | isLoading () { 77 | return this.state.isFetchingData || this.state.isFetchingLabel 78 | } 79 | 80 | componentDidMount () { 81 | this.updateLabel(this.props.value) 82 | } 83 | 84 | componentWillReceiveProps (nextProps) { 85 | // console.log('will recieve props', nextProps) 86 | if (this.props.value !== nextProps.value && nextProps.value) { 87 | this.updateLabel(nextProps.value) 88 | } 89 | } 90 | 91 | componentDidUpdate (prevProps, prevState) { 92 | if (this.state.searchText !== this.refs.input.state.searchText) { 93 | this.refs.input.setState({ searchText: this.state.searchText }) 94 | } 95 | } 96 | 97 | updatedSelectedItems (values) { 98 | var missingLabels = [] 99 | var knownItems = this.state.knownItems 100 | var valueArray = _.isArray(values) ? values : [values] 101 | 102 | if (!values) return 103 | 104 | valueArray.map((value) => { 105 | if (!this.state.knownItems[value]) { 106 | missingLabels.push(value) 107 | } 108 | }) 109 | 110 | if (missingLabels.length > 0) { 111 | var labelMethodName = this.props.labelMethodName 112 | var connection = this.props.connection || global.Meteor 113 | var labelsMethod = this.props.multi ? missingLabels : missingLabels[0] 114 | this.setState({isFetchingLabel: true}) 115 | connection.call(labelMethodName, labelsMethod, (error, response) => { 116 | this.setState({isFetchingLabel: false}) 117 | if (error) { 118 | console.log(`[select-with-method] Recieved error from '${labelMethodName}'`, error) 119 | } else { 120 | if (this.props.multi) { 121 | missingLabels.map((value, index) => { 122 | if (_.isString(response[index])) { 123 | knownItems[value] = {label: response[index]} 124 | } else { 125 | knownItems[value] = response[index] 126 | } 127 | }) 128 | } else { 129 | if (_.isString(response)) { 130 | knownItems[labelsMethod] = {label: response} 131 | } else { 132 | knownItems[labelsMethod] = response 133 | } 134 | // console.log('setting to response', response) 135 | this.setState({ searchText: knownItems[labelsMethod].label }) 136 | } 137 | 138 | this.setState({ knownItems }) 139 | } 140 | }) 141 | } else { 142 | if (!this.props.multi) { 143 | // console.log('setting to known label', knownItems[values]) 144 | this.setState({ searchText: knownItems[values] }) 145 | } 146 | } 147 | } 148 | 149 | updateLabel (value) { 150 | if (!this.props.multi && !value) { 151 | // console.log('clean on update') 152 | this.setState({ searchText: '' }) 153 | return 154 | } 155 | 156 | this.updatedSelectedItems(value) 157 | } 158 | 159 | search (text) { 160 | // console.log('searching with text', text) 161 | this.setState({selected: null, isFetchingData: true}) 162 | 163 | if (!this.props.multi) { 164 | this.props.onChange(null) 165 | } 166 | 167 | var methodName = this.props.methodName 168 | var connection = this.props.connection || global.Meteor 169 | connection.call(methodName, text, (error, response) => { 170 | this.setState({isFetchingData: false}) 171 | if (error) { 172 | console.log(`[select-with-method] Recieved error from '${methodName}'`, error) 173 | } else { 174 | response = response || [] 175 | this.setState({ response }) 176 | var dataSource = response.map((item) => { 177 | return { 178 | text: item.value, 179 | value: 180 | } 181 | }) 182 | if (_.isFunction(this.props.create) && text && this.props.canCreate(text)) { 183 | dataSource.push({ 184 | text: text, 185 | value: 186 | }) 187 | } 188 | this.setState({ dataSource }) 189 | } 190 | }) 191 | } 192 | 193 | onUpdateText (text) { 194 | this.setState({searchText: text, isFetchingData: true}) 195 | this.debouncedSearch(text) 196 | } 197 | 198 | createItem (item) { 199 | this.props.create(item.text, (value) => { 200 | if (this.props.multi) { 201 | setTimeout(() => { 202 | this.setState({ searchText: '' }) 203 | }, 101) 204 | if (_.contains(this.props.value || [], value)) { 205 | return 206 | } 207 | this.props.onChange(_.union(this.props.value || [], [value])) 208 | } else { 209 | this.props.onChange(value) 210 | } 211 | }) 212 | } 213 | 214 | onItemSelected (item, index) { 215 | if (index === this.state.response.length && _.isFunction(this.props.create)) { 216 | return this.createItem(item) 217 | } 218 | var selected = this.state.response[index] 219 | if (this.props.multi) { 220 | // console.log('clean on item selected') 221 | setTimeout(() => { 222 | this.setState({ searchText: '' }) 223 | }, 101) 224 | if (_.contains(this.props.value || [], selected.value)) return 225 | this.props.onChange(_.union(this.props.value || [], [selected.value])) 226 | } else { 227 | this.props.onChange(selected ? selected.value : null) 228 | setTimeout(() => { 229 | this.setState({ searchText: selected.label }) 230 | }, 101) 231 | } 232 | 233 | if (selected) { 234 | this.state.knownItems[selected.value] = selected 235 | this.setState({ knownItems: this.state.knownItems }) 236 | } 237 | } 238 | 239 | removeItem (value) { 240 | this.props.onChange(_.without(this.props.value || [], value)) 241 | } 242 | 243 | onFocus () { 244 | if (!this.props.multi && !this.props.value) { 245 | this.search('') 246 | } 247 | } 248 | 249 | onBlur () { 250 | this.setState({ open: false }) 251 | if (!this.props.value) { 252 | this.setState({ searchText: '' }) 253 | } 254 | 255 | if (this.state.searchText !== this.refs.input.state.searchText) { 256 | // console.log('did blur, not equal') 257 | this.refs.input.setState({ searchText: this.state.searchText }) 258 | } 259 | } 260 | 261 | renderItems () { 262 | return (_.isArray(this.props.value) ? this.props.value : []).map((value, index) => { 263 | const item = this.state.knownItems[value] || 'Loading...' 264 | const label = item.label 265 | const image = item.image 266 | const initials = item.initials || undefined 267 | const color = item.color 268 | const textColor = color ? Colors.white : Colors.grey900 269 | const icon = item.icon ? {item.icon} : null 270 | let avatar = null 271 | if (initials || icon || image) { 272 | avatar = ( 273 | 274 | {initials} 275 | 276 | ) 277 | } 278 | return ( 279 | this.removeItem(value)} 281 | key={value} 282 | labelColor={textColor} 283 | style={{marginBottom: 3}} 284 | backgroundColor={color}> 285 | {avatar} 286 | {label} 287 | 288 | ) 289 | }) 290 | } 291 | 292 | renderLoading () { 293 | if (!this.isLoading()) return 294 | return 295 | 296 | /* return ( 297 | 300 | ) */ 301 | } 302 | 303 | render () { 304 | return ( 305 |
306 | 324 | {this.renderLoading()} 325 |
326 | {this.renderItems()} 327 |
328 |
329 | ) 330 | } 331 | } 332 | 333 | SelectWithMethodComponent.propTypes = propTypes 334 | SelectWithMethodComponent.defaultProps = defaultProps 335 | 336 | registerType({ 337 | type: 'select-with-method', 338 | component: SelectWithMethodComponent 339 | }) 340 | -------------------------------------------------------------------------------- /src/select.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import SelectField from 'material-ui/SelectField' 3 | import MenuItem from 'material-ui/MenuItem' 4 | import {FieldType, registerType} from 'simple-react-form' 5 | import _ from 'underscore' 6 | 7 | const propTypes = { 8 | ...FieldType.propTypes, 9 | /** 10 | * Optional default value. 11 | */ 12 | defaultValue: React.PropTypes.string, 13 | /** 14 | * The options for the select input. Each item must have label and value. 15 | */ 16 | options: React.PropTypes.arrayOf(React.PropTypes.shape({ 17 | label: React.PropTypes.string.isRequired, 18 | value: React.PropTypes.oneOfType([ 19 | React.PropTypes.string, 20 | React.PropTypes.number 21 | ]).isRequired 22 | })) 23 | } 24 | 25 | const defaultProps = { 26 | } 27 | 28 | export default class SelectComponent extends React.Component { 29 | 30 | getOptions () { 31 | if (this.props.options) { 32 | return this.props.options 33 | } else if (this.props.fieldSchema && this.props.fieldSchema.allowedValues) { 34 | return _.map(this.props.fieldSchema.allowedValues, function (allowedValue) { 35 | return { 36 | label: allowedValue, 37 | value: allowedValue 38 | } 39 | }) 40 | } else { 41 | throw new Error('You must set the options for the select field') 42 | } 43 | } 44 | 45 | getDefaultValue () { 46 | if (this.props.defaultValue) { 47 | return this.props.defaultValue 48 | } else if (this.props.fieldSchema && this.props.fieldSchema.defaultValue) { 49 | return this.props.fieldSchema.defaultValue 50 | } 51 | } 52 | 53 | componentDidMount () { 54 | if (!this.props.value) { 55 | this.props.onChange(this.getDefaultValue()) 56 | } 57 | } 58 | 59 | render () { 60 | return ( 61 | 69 | {this.getOptions().map((item) => ( 70 | this.props.onChange(item.value)} /> 71 | ))} 72 | 73 | ) 74 | } 75 | } 76 | 77 | SelectComponent.propTypes = propTypes 78 | SelectComponent.defaultProps = defaultProps 79 | 80 | registerType({ 81 | type: 'select', 82 | component: SelectComponent 83 | }) 84 | -------------------------------------------------------------------------------- /src/styles.js: -------------------------------------------------------------------------------- 1 | import * as Colors from 'material-ui/styles/colors' 2 | 3 | export default { 4 | label: { 5 | color: 'rgba(0,0,0,0.5)', 6 | marginBottom: 5, 7 | fontSize: 12 8 | }, 9 | mirrorLabel: { 10 | color: 'rgba(0,0,0,0.5)', 11 | marginBottom: -6, 12 | fontSize: 12 13 | }, 14 | errorMessage: { 15 | fontSize: 12, 16 | marginTop: 10, 17 | color: Colors.red500 18 | }, 19 | fieldContainer: { 20 | paddingTop: 10, 21 | paddingBottom: 10 22 | }, 23 | tag: { 24 | background: Colors.grey300, 25 | padding: '5px 10px', 26 | display: 'inline-block', 27 | borderRadius: 20, 28 | marginRight: 5, 29 | marginTop: 3, 30 | marginBottom: 2, 31 | cursor: 'pointer' 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/tags.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import TextField from 'material-ui/TextField' 3 | import {FieldType, registerType} from 'simple-react-form' 4 | import styles from './styles' 5 | import _ from 'underscore' 6 | 7 | const propTypes = { 8 | ...FieldType.propTypes 9 | } 10 | 11 | const defaultProps = { 12 | 13 | } 14 | 15 | export default class StringArrayComponent extends React.Component { 16 | 17 | constructor (props) { 18 | super(props) 19 | this.state = {} 20 | } 21 | 22 | onKeyDown (event) { 23 | if (event.keyCode === 13) { 24 | this.addItem() 25 | } 26 | } 27 | 28 | addItem () { 29 | if (!this.state.value) return 30 | var value = (this.props.value || []) 31 | value.push(this.state.value) 32 | this.props.onChange(value) 33 | this.setState({ value: '' }) 34 | } 35 | 36 | removeItem (value) { 37 | const newValue = _.without(this.props.value, value) 38 | this.props.onChange(newValue) 39 | } 40 | 41 | renderItems () { 42 | return (this.props.value || []).map((value, index) => { 43 | return ( 44 |
this.removeItem(value)} key={index} style={styles.tag}> 45 | {value} 46 |
47 | ) 48 | }) 49 | } 50 | 51 | render () { 52 | return ( 53 |
54 | this.setState({ value: event.target.value })} 63 | onKeyDown={this.onKeyDown.bind(this)} 64 | onBlur={this.addItem.bind(this)} 65 | {...this.props.passProps} /> 66 | {this.renderItems()} 67 |
68 | ) 69 | } 70 | } 71 | 72 | StringArrayComponent.propTypes = propTypes 73 | StringArrayComponent.defaultProps = defaultProps 74 | 75 | registerType({ 76 | type: 'string-array', 77 | component: StringArrayComponent 78 | }) 79 | 80 | registerType({ 81 | type: 'tags', 82 | component: StringArrayComponent 83 | }) 84 | -------------------------------------------------------------------------------- /src/text.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import TextField from 'material-ui/TextField' 3 | import {FieldType, registerType} from 'simple-react-form' 4 | 5 | const propTypes = { 6 | ...FieldType.propTypes, 7 | fieldType: React.PropTypes.string 8 | } 9 | 10 | const defaultProps = { 11 | 12 | } 13 | 14 | export default class TextFieldComponent extends React.Component { 15 | 16 | constructor (props) { 17 | super(props) 18 | this.state = { value: props.value } 19 | } 20 | 21 | onKeyDown (event) { 22 | if (event.keyCode === 13) { 23 | this.props.onChange(event.target.value) 24 | } 25 | } 26 | 27 | onBlur (event) { 28 | if (this.props.onBlur) { 29 | this.props.onBlur() 30 | } 31 | this.props.onChange(event.target.value) 32 | } 33 | 34 | isNumberType () { 35 | if (this.props.fieldSchema) { 36 | return this.props.fieldSchema.type === Number 37 | } 38 | if (this.props.fieldType === 'number') { 39 | return true 40 | } 41 | if (this.type === 'number') { 42 | return true 43 | } 44 | return false 45 | } 46 | 47 | onChange (event, other) { 48 | const value = this.isNumberType() ? Number(event.target.value) : event.target.value 49 | this.props.onChange(value) 50 | } 51 | 52 | render () { 53 | var fieldType = this.props.fieldType || this.type || 'text' 54 | return ( 55 | 69 | ) 70 | } 71 | } 72 | 73 | TextFieldComponent.propTypes = propTypes 74 | TextFieldComponent.defaultProps = defaultProps 75 | 76 | registerType({ 77 | type: 'text', 78 | component: TextFieldComponent 79 | }) 80 | 81 | class StringFieldComponent extends TextFieldComponent { 82 | constructor (props) { 83 | super(props) 84 | this.type = 'text' 85 | } 86 | } 87 | 88 | registerType({ 89 | type: 'string', 90 | component: StringFieldComponent 91 | }) 92 | 93 | class NumberFieldComponent extends TextFieldComponent { 94 | constructor (props) { 95 | super(props) 96 | this.type = 'number' 97 | } 98 | } 99 | 100 | registerType({ 101 | type: 'number', 102 | component: NumberFieldComponent 103 | }) 104 | 105 | class DateFieldComponent extends TextFieldComponent { 106 | constructor (props) { 107 | super(props) 108 | this.type = 'date' 109 | } 110 | } 111 | 112 | registerType({ 113 | type: 'date', 114 | component: DateFieldComponent 115 | }) 116 | -------------------------------------------------------------------------------- /src/textarea.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import TextField from 'material-ui/TextField' 3 | import {FieldType, registerType} from 'simple-react-form' 4 | 5 | const propTypes = { 6 | changeOnKeyDown: React.PropTypes.bool, 7 | ...FieldType.propTypes 8 | } 9 | 10 | const defaultProps = { 11 | changeOnKeyDown: false 12 | } 13 | 14 | export default class TextareaComponent extends React.Component { 15 | constructor (props) { 16 | super(props) 17 | this.state = { value: props.value } 18 | } 19 | 20 | componentWillReceiveProps (nextProps) { 21 | this.setState({ value: nextProps.value }) 22 | } 23 | 24 | onKeyDown (event) { 25 | if (event.keyCode === 13) { 26 | this.props.onChange(event.target.value) 27 | } 28 | } 29 | 30 | onBlur (event) { 31 | if (this.props.onBlur) { 32 | this.props.onBlur() 33 | } 34 | this.props.onChange(this.state.value) 35 | } 36 | 37 | onChange (event) { 38 | this.setState({ value: event.target.value }) 39 | if (this.props.changeOnKeyDown) { 40 | this.props.onChange(event.target.value) 41 | } 42 | } 43 | render () { 44 | return ( 45 | 58 | ) 59 | } 60 | } 61 | 62 | TextareaComponent.propTypes = propTypes 63 | TextareaComponent.defaultProps = defaultProps 64 | 65 | registerType({ 66 | type: 'textarea', 67 | component: TextareaComponent 68 | }) 69 | -------------------------------------------------------------------------------- /src/toggle.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Toggle from 'material-ui/Toggle' 3 | import {FieldType, registerType} from 'simple-react-form' 4 | import styles from './styles' 5 | 6 | const propTypes = { 7 | ...FieldType.propTypes 8 | } 9 | 10 | const defaultProps = { 11 | 12 | } 13 | 14 | export default class ToggleComponent extends React.Component { 15 | 16 | render () { 17 | return ( 18 |
19 | this.props.onChange(!this.props.value)} 24 | {...this.props.passProps}/> 25 |
{this.props.errorMessage}
26 |
27 | ) 28 | } 29 | 30 | } 31 | 32 | ToggleComponent.propTypes = propTypes 33 | ToggleComponent.defaultProps = defaultProps 34 | 35 | registerType({ 36 | type: 'toggle', 37 | component: ToggleComponent 38 | }) 39 | -------------------------------------------------------------------------------- /watch.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const spawn = require('child_process').spawn 3 | const babel = require('babel-core') 4 | require('colors') 5 | 6 | const pkg = JSON.parse(fs.readFileSync('package.json')) 7 | console.log(`\nWatching ${pkg.name} v${pkg.version}\n`.underline.bold) 8 | 9 | const build = spawn('yarn', ['run', 'build']) 10 | build.on('close', code => console.log('Initial build ready\n'.grey)) // ready 11 | build.on('error', error => console.log(error)) 12 | 13 | const fileChanged = function (filename) { 14 | if (filename.endsWith('.less')) { 15 | const build = spawn('yarn', ['run', 'build-styles']) 16 | // build.on('close', code => console.log('Build ready')) // ready 17 | build.on('error', error => console.log(error)) 18 | } else if (filename.endsWith('.js')) { 19 | try { 20 | const result = babel.transformFileSync('./src/' + filename).code 21 | fs.writeFileSync('./lib/' + filename, result) 22 | } catch (error) { 23 | console.log(error.message.red + '\n') 24 | if (error._babel) { 25 | console.log(error.codeFrame) 26 | console.log('') 27 | } else { 28 | console.log(error) 29 | } 30 | } 31 | } else if (fs.lstatSync('./src/' + filename).isDirectory()) { 32 | fs.mkdirSync('./lib/' + filename) 33 | fs.readdirSync('./src/' + filename).forEach(file => { 34 | fileChanged(filename + '/' + file) 35 | console.log(filename.grey + '/' + file) 36 | }) 37 | } 38 | } 39 | 40 | const deleteFile = function (filename) { 41 | if (fs.lstatSync(filename).isDirectory()) { 42 | fs.readdirSync(filename).forEach(file => deleteFile(filename + '/' + file)) 43 | fs.rmdirSync(filename) 44 | } else { 45 | fs.unlinkSync(filename) 46 | } 47 | } 48 | 49 | const fileEvent = function (eventType, filename) { 50 | const existsInLib = fs.existsSync('./lib/' + filename) 51 | const existsInSrc = fs.existsSync('./src/' + filename) 52 | const action = eventType === 'rename' ? !existsInSrc ? 'deleted' : 'created' : 'changed' 53 | 54 | console.log(filename.bold + ` ${action}`.grey) 55 | if (action === 'deleted' && existsInLib) { 56 | deleteFile('./lib/' + filename) 57 | } else { 58 | fileChanged(filename) 59 | } 60 | } 61 | 62 | fs.watch('./src', {recursive: true}, fileEvent) 63 | --------------------------------------------------------------------------------