├── .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 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------