@scenid/react-formulator 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/.eslintignore +21 -0
  2. package/.eslintrc +70 -0
  3. package/.firebase/hosting.c3Rvcnlib29rLXN0YXRpYw.cache +38 -0
  4. package/.firebaserc +5 -0
  5. package/.storybook/main.js +12 -0
  6. package/.storybook/preview.js +9 -0
  7. package/README.md +29 -0
  8. package/dist/index.cjs.css +96 -0
  9. package/dist/index.cjs.js +25 -0
  10. package/dist/index.esm.css +96 -0
  11. package/dist/index.esm.js +25 -0
  12. package/firebase.json +17 -0
  13. package/package.json +74 -0
  14. package/rollup.config.js +35 -0
  15. package/src/Editable/FormBoolean.jsx +44 -0
  16. package/src/Editable/FormField.jsx +223 -0
  17. package/src/Editable/FormNumber.jsx +36 -0
  18. package/src/Editable/FormRepeater.jsx +190 -0
  19. package/src/Editable/FormSelect.jsx +49 -0
  20. package/src/Editable/FormTextfield.jsx +13 -0
  21. package/src/FormGroupHeader.jsx +85 -0
  22. package/src/FormHelpers.js +191 -0
  23. package/src/FormSectionBlock.jsx +70 -0
  24. package/src/FormSectionCard.jsx +62 -0
  25. package/src/FormulatorForm.jsx +537 -0
  26. package/src/FormulatorFormSection.jsx +494 -0
  27. package/src/HiddenData.jsx +24 -0
  28. package/src/ReadOnly/FormBoolean.jsx +36 -0
  29. package/src/ReadOnly/FormField.jsx +126 -0
  30. package/src/ReadOnly/FormMarkdown.jsx +20 -0
  31. package/src/ReadOnly/FormNumber.jsx +17 -0
  32. package/src/ReadOnly/FormReadOnlyText.jsx +19 -0
  33. package/src/ReadOnly/FormRepeater.jsx +36 -0
  34. package/src/ReadOnly/FormSelect.jsx +18 -0
  35. package/src/helpers.js +13 -0
  36. package/src/index.js +3 -0
  37. package/stories/Forms.stories.jsx +126 -0
  38. package/stories/Introduction.stories.mdx +206 -0
  39. package/stories/StoryBase.jsx +35 -0
  40. package/stories/assets/code-brackets.svg +1 -0
  41. package/stories/assets/colors.svg +1 -0
  42. package/stories/assets/comments.svg +1 -0
  43. package/stories/assets/direction.svg +1 -0
  44. package/stories/assets/flow.svg +1 -0
  45. package/stories/assets/plugin.svg +1 -0
  46. package/stories/assets/repo.svg +1 -0
  47. package/stories/assets/stackalt.svg +1 -0
  48. package/stories/forms/login.render.schema.json +23 -0
  49. package/stories/forms/login.validation.schema.json +29 -0
  50. package/stories/forms/markdown.render.schema.json +30 -0
  51. package/stories/forms/markdown.validation.schema.json +18 -0
  52. package/stories/forms/register.render.schema.json +32 -0
  53. package/stories/forms/register.validation.schema.json +34 -0
  54. package/stories/forms/types.schemas.js +171 -0
package/firebase.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "hosting": {
3
+ "site": "react-formulator",
4
+ "public": "storybook-static",
5
+ "ignore": [
6
+ "firebase.json",
7
+ "**/.*",
8
+ "**/node_modules/**"
9
+ ],
10
+ "rewrites": [
11
+ {
12
+ "source": "**",
13
+ "destination": "/index.html"
14
+ }
15
+ ]
16
+ }
17
+ }
package/package.json ADDED
@@ -0,0 +1,74 @@
1
+ {
2
+ "name": "@scenid/react-formulator",
3
+ "version": "0.0.2",
4
+ "main": "dist/index.cjs.js",
5
+ "module": "dist/index.esm.js",
6
+ "repository": "https://dennykoch@bitbucket.org/scenid/react-formulator.git",
7
+ "author": "Denny Koch <denny.koch@scenid.com>",
8
+ "license": "UNLICENSED",
9
+ "scripts": {
10
+ "build": "rollup -c",
11
+ "storybook": "start-storybook -p 6006",
12
+ "storybook:build": "build-storybook",
13
+ "prepublishOnly": "yarn build",
14
+ "git:pushall": "git push && git push --tags",
15
+ "patch": "npm version patch && yarn git:pushall",
16
+ "minor": "npm version minor && yarn git:pushall",
17
+ "major": "npm version major && yarn git:pushall"
18
+ },
19
+ "peerDependencies": {
20
+ "@material-ui/core": "^4.12.4",
21
+ "@material-ui/icons": "^4.11.3",
22
+ "@material-ui/styles": "^4.11.5",
23
+ "classnames": "^2.3.1",
24
+ "prop-types": "^15.8.1",
25
+ "react": "16.11.0",
26
+ "react-dom": "16.11.0"
27
+ },
28
+ "devDependencies": {
29
+ "@babel/core": "^7.17.9",
30
+ "@babel/plugin-transform-runtime": "^7.17.0",
31
+ "@material-ui/core": "^4.12.4",
32
+ "@material-ui/icons": "^4.11.3",
33
+ "@material-ui/lab": "^4.0.0-alpha.61",
34
+ "@material-ui/styles": "^4.11.5",
35
+ "@rollup/plugin-babel": "^5.3.1",
36
+ "@rollup/plugin-commonjs": "^22.0.0",
37
+ "@rollup/plugin-node-resolve": "^13.2.1",
38
+ "@scenid/cloud-icons": "^2.6.0",
39
+ "@scenid/formulator": "^2.0.2",
40
+ "@storybook/addon-actions": "^6.4.22",
41
+ "@storybook/addon-essentials": "^6.4.22",
42
+ "@storybook/addon-interactions": "^6.4.22",
43
+ "@storybook/addon-links": "^6.4.22",
44
+ "@storybook/react": "^6.4.22",
45
+ "@storybook/testing-library": "^0.0.11",
46
+ "babel-loader": "^8.2.5",
47
+ "classnames": "^2.3.1",
48
+ "cross-env": "^7.0.3",
49
+ "deep-equal": "^2.0.5",
50
+ "eslint": "^7.11.0",
51
+ "eslint-config-airbnb": "^18.0.1",
52
+ "eslint-formatter-pretty": "^2.1.1",
53
+ "eslint-import-resolver-webpack": "^0.11.1",
54
+ "eslint-plugin-compat": "^3.1.2",
55
+ "eslint-plugin-import": "^2.17.3",
56
+ "eslint-plugin-jest": "^22.7.1",
57
+ "eslint-plugin-jsx-a11y": "^6.2.3",
58
+ "eslint-plugin-promise": "^4.1.1",
59
+ "eslint-plugin-react": "^7.6.1",
60
+ "eslint-plugin-react-hooks": "^2.3.0",
61
+ "eventemitter3": "^4.0.7",
62
+ "micromustache": "^8.0.3",
63
+ "prop-types": "^15.8.1",
64
+ "react": "16.11.0",
65
+ "react-dom": "16.11.0",
66
+ "react-markdown": "^8.0.3",
67
+ "rehype-highlight": "^5.0.2",
68
+ "remark-gfm": "^3.0.1",
69
+ "rollup": "^2.70.2",
70
+ "rollup-plugin-import-css": "^3.0.3",
71
+ "rollup-plugin-peer-deps-external": "^2.2.4",
72
+ "rollup-plugin-terser": "^7.0.2"
73
+ }
74
+ }
@@ -0,0 +1,35 @@
1
+ import babel from '@rollup/plugin-babel'
2
+
3
+ import resolve from '@rollup/plugin-node-resolve'
4
+ import { terser } from 'rollup-plugin-terser'
5
+ import commonjs from '@rollup/plugin-commonjs'
6
+ import peerDepsExternal from 'rollup-plugin-peer-deps-external'
7
+ import css from "rollup-plugin-import-css"
8
+
9
+ import pkg from './package.json'
10
+
11
+ export default {
12
+ input: 'src/index.js',
13
+ output: [
14
+ { file: pkg.main, format: 'cjs' },
15
+ { file: pkg.module, format: 'esm' }
16
+ ],
17
+ plugins: [
18
+ peerDepsExternal(),
19
+ babel({
20
+ babelHelpers: 'runtime',
21
+ exclude: 'node_modules/**',
22
+ presets: ['@babel/preset-env', '@babel/preset-react'],
23
+ plugins: [
24
+ [
25
+ '@babel/transform-runtime',
26
+ { regenerator: true }
27
+ ]
28
+ ]
29
+ }),
30
+ resolve({ extensions: ['.js', '.jsx'] }),
31
+ commonjs(),
32
+ css(),
33
+ terser()
34
+ ]
35
+ }
@@ -0,0 +1,44 @@
1
+ import React from 'react'
2
+ import PropTypes from 'prop-types'
3
+
4
+ import { Checkbox, Switch } from '@material-ui/core'
5
+
6
+ const isBoolean = val => val === false || val === true
7
+ const isChecked = (value, defaultValue) => isBoolean(value) ? value : (defaultValue || false)
8
+
9
+ const FormBoolean = ({ variant, name, value, defaultValue, needsToBeTrue, onChange }) => {
10
+ const handleChange = e => {
11
+ let newValue = e.target.checked
12
+ if (needsToBeTrue === true && newValue === false) newValue = undefined
13
+ onChange({ target: { name, value: newValue } })
14
+ }
15
+
16
+ if (variant === 'checkbox') {
17
+ return (
18
+ <Checkbox
19
+ name={name}
20
+ checked={isChecked(value, defaultValue)}
21
+ onChange={handleChange}
22
+ />
23
+ )
24
+ }
25
+
26
+ return (
27
+ <Switch
28
+ name={name}
29
+ checked={isChecked(value, defaultValue)}
30
+ onChange={handleChange}
31
+ />
32
+ )
33
+ }
34
+
35
+ FormBoolean.propTypes = {
36
+ variant: PropTypes.string,
37
+ name: PropTypes.string.isRequired,
38
+ value: PropTypes.bool,
39
+ defaultValue: PropTypes.bool,
40
+ needsToBeTrue: PropTypes.bool,
41
+ onChange: PropTypes.func.isRequired
42
+ }
43
+
44
+ export default FormBoolean
@@ -0,0 +1,223 @@
1
+ import React from 'react'
2
+ import PropTypes from 'prop-types'
3
+ import cx from 'classnames'
4
+
5
+ import isEqual from 'deep-equal'
6
+
7
+ import {
8
+ FormControl,
9
+ FormHelperText,
10
+ InputAdornment,
11
+ FormControlLabel,
12
+ FormGroup
13
+ } from '@material-ui/core'
14
+
15
+ import { makeStyles } from '@material-ui/styles'
16
+
17
+ import AutorenewIcon from '@material-ui/icons/Autorenew'
18
+
19
+ const useStyles = makeStyles(theme => ({ error: { color: theme.palette.error.main } }))
20
+
21
+ const FormControlField = ({
22
+ variant,
23
+ type,
24
+ isRender,
25
+ name,
26
+ label,
27
+ component,
28
+ componentProps,
29
+ defaultValue,
30
+ value,
31
+ disabled,
32
+ options,
33
+ hasErrors,
34
+ errors,
35
+ required,
36
+ validating,
37
+ dirty,
38
+ onChange
39
+ }) => {
40
+ const styles = useStyles()
41
+
42
+ const finalProps = {
43
+ name,
44
+ label,
45
+ defaultValue,
46
+ value,
47
+ options,
48
+ required,
49
+ disabled,
50
+ ...componentProps,
51
+ onChange
52
+ }
53
+
54
+ if (validating) {
55
+ finalProps.endAdornment = [(
56
+ <InputAdornment position="end">
57
+ <AutorenewIcon />
58
+ </InputAdornment>
59
+ )]
60
+ }
61
+
62
+ let control
63
+ if (isRender) {
64
+ control = React.cloneElement(
65
+ component,
66
+ {
67
+ variant,
68
+ ...finalProps,
69
+ label,
70
+ hasErrors,
71
+ errors,
72
+ dirty
73
+ }
74
+ )
75
+ } else {
76
+ control = React.createElement(
77
+ component,
78
+ {
79
+ variant,
80
+ ...finalProps
81
+ }
82
+ )
83
+ }
84
+
85
+ const isSwitch = type === 'boolean'
86
+ const isRepeater = type === 'array' && !isRender
87
+
88
+ if (isRepeater || isRender) {
89
+ return control
90
+ }
91
+
92
+ return (
93
+ <FormControl
94
+ className="data-card-field"
95
+ error={dirty && hasErrors}
96
+ variant={variant}
97
+ margin="dense"
98
+ fullWidth
99
+ required={required}
100
+ >
101
+ {
102
+ isSwitch
103
+ && (
104
+ <FormControlLabel
105
+ className={cx({ [styles.error]: dirty && hasErrors })}
106
+ control={control}
107
+ label={label}
108
+ labelPlacement={finalProps.labelPlacement || 'end'}
109
+ />
110
+ )
111
+ }
112
+ {
113
+ (!isSwitch && !isRepeater && !isRender)
114
+ && control
115
+ }
116
+ {
117
+ (dirty && hasErrors)
118
+ && (
119
+ <FormHelperText>
120
+ {errors.map(e => e.message).join('. ')}
121
+ </FormHelperText>
122
+ )
123
+ }
124
+ </FormControl>
125
+ )
126
+ }
127
+
128
+ FormControlField.propTypes = {
129
+ variant: PropTypes.oneOf(['standard', 'filled', 'outlined']),
130
+ isRender: PropTypes.bool,
131
+ component: PropTypes.any.isRequired,
132
+ componentProps: PropTypes.object,
133
+ type: PropTypes.string.isRequired,
134
+ name: PropTypes.string.isRequired,
135
+ label: PropTypes.oneOfType([
136
+ PropTypes.string,
137
+ PropTypes.bool
138
+ ]),
139
+ labelPlacement: PropTypes.oneOf(['top', 'start', 'bottom', 'end']),
140
+ defaultValue: PropTypes.oneOfType([
141
+ PropTypes.string,
142
+ PropTypes.number,
143
+ PropTypes.bool,
144
+ PropTypes.array,
145
+ PropTypes.object
146
+ ]),
147
+ value: PropTypes.oneOfType([
148
+ PropTypes.string,
149
+ PropTypes.number,
150
+ PropTypes.bool,
151
+ PropTypes.array,
152
+ PropTypes.object
153
+ ]),
154
+ options: PropTypes.arrayOf(PropTypes.shape({
155
+ label: PropTypes.string.isRequired,
156
+ value: PropTypes.any
157
+ })),
158
+ hasErrors: PropTypes.bool,
159
+ errors: PropTypes.array,
160
+ validating: PropTypes.bool,
161
+ dirty: PropTypes.bool,
162
+ required: PropTypes.bool,
163
+ onChange: PropTypes.func.isRequired
164
+ }
165
+
166
+ const FormField = ({
167
+ prepend,
168
+ append,
169
+ componentProps,
170
+ ...fieldProps
171
+ }) => {
172
+ const finalProps = {
173
+ componentProps,
174
+ ...fieldProps
175
+ }
176
+
177
+ if (componentProps?.type === 'hidden') {
178
+ return <input type="hidden" name={fieldProps.name} value={fieldProps.value} />
179
+ }
180
+
181
+ return (
182
+ <FormGroup>
183
+ {prepend}
184
+ {/* eslint-disable-next-line react/jsx-props-no-spreading */}
185
+ <FormControlField {...finalProps} />
186
+ {append}
187
+ </FormGroup>
188
+ )
189
+ }
190
+
191
+ FormField.propTypes = {
192
+ variant: PropTypes.oneOf(['standard', 'filled', 'outlined']),
193
+ isRender: PropTypes.bool,
194
+ component: PropTypes.any.isRequired,
195
+ componentProps: PropTypes.object,
196
+ type: PropTypes.string.isRequired,
197
+ name: PropTypes.string.isRequired,
198
+ label: PropTypes.oneOfType([
199
+ PropTypes.string,
200
+ PropTypes.bool
201
+ ]),
202
+ value: PropTypes.oneOfType([
203
+ PropTypes.string,
204
+ PropTypes.number,
205
+ PropTypes.bool,
206
+ PropTypes.array,
207
+ PropTypes.object
208
+ ]),
209
+ options: PropTypes.arrayOf(PropTypes.shape({
210
+ label: PropTypes.string.isRequired,
211
+ value: PropTypes.any
212
+ })),
213
+ prepend: PropTypes.array,
214
+ append: PropTypes.array,
215
+ hasErrors: PropTypes.bool,
216
+ errors: PropTypes.array,
217
+ validating: PropTypes.bool,
218
+ required: PropTypes.bool,
219
+ dirty: PropTypes.bool,
220
+ onChange: PropTypes.func.isRequired
221
+ }
222
+
223
+ export default React.memo(FormField, (prevProps, nextProps) => isEqual(prevProps, nextProps, { strict: true }))
@@ -0,0 +1,36 @@
1
+ import React from 'react'
2
+ import PropTypes from 'prop-types'
3
+
4
+ import { TextField } from '@material-ui/core'
5
+
6
+ import { castToNumber } from '../helpers'
7
+
8
+ const FormNumber = ({ name, value, min, max, step, onChange, ...props }) => (
9
+ <TextField
10
+ name={name}
11
+ type="number"
12
+ value={castToNumber(value, step)}
13
+ inputProps={{ min, max, step }}
14
+ onChange={e => onChange({
15
+ target: {
16
+ name,
17
+ value: castToNumber(e.target.value, step)
18
+ }
19
+ })}
20
+ {...props}
21
+ />
22
+ )
23
+
24
+ FormNumber.propTypes = {
25
+ name: PropTypes.string.isRequired,
26
+ min: PropTypes.number,
27
+ max: PropTypes.number,
28
+ step: PropTypes.number,
29
+ value: PropTypes.oneOfType([
30
+ PropTypes.number,
31
+ PropTypes.string
32
+ ]),
33
+ onChange: PropTypes.func.isRequired
34
+ }
35
+
36
+ export default FormNumber
@@ -0,0 +1,190 @@
1
+ import React, { useState, useEffect } from 'react'
2
+ import PropTypes from 'prop-types'
3
+
4
+ import {
5
+ Box,
6
+ TextField,
7
+ Button,
8
+ List,
9
+ ListItem,
10
+ ListItemText,
11
+ ListItemSecondaryAction,
12
+ IconButton,
13
+ InputAdornment,
14
+ FormControl,
15
+ InputLabel,
16
+ Select,
17
+ MenuItem
18
+ } from '@material-ui/core'
19
+
20
+ import {
21
+ Delete as DeleteIcon,
22
+ Clear as ClearIcon,
23
+ Save as SaveIcon
24
+ } from '@material-ui/icons'
25
+
26
+ const getValue = (value, catalog) => {
27
+ if (catalog && catalog[value] !== undefined) return catalog[value]
28
+ return value
29
+ }
30
+
31
+ const FormRepeater = ({ variant, name, value, catalog, onChange }) => {
32
+ const [entries, setEntries] = useState(value || [])
33
+ const [inEdit, setInEdit] = useState(false)
34
+ const [inputValue, setInputValue] = useState('')
35
+
36
+ const handleEntryAdd = () => {
37
+ if (inputValue.length > 0) {
38
+ setEntries([...entries, inputValue])
39
+ setInputValue('')
40
+ }
41
+ }
42
+
43
+ const handleCancelNewEntry = () => {
44
+ setInEdit(false)
45
+ setInputValue('')
46
+ }
47
+
48
+ const handleEntryDel = delIndex => {
49
+ const newEntries = [...entries]
50
+ newEntries.splice(delIndex, 1)
51
+ setEntries(newEntries)
52
+ }
53
+
54
+ useEffect(() => {
55
+ onChange({ target: { name, value: entries } })
56
+ }, [entries])
57
+
58
+ let blockedOptions = []
59
+ if (catalog) {
60
+ blockedOptions = Object.entries(catalog).filter(e => value.includes(e[0])).map(e => e[0])
61
+ }
62
+
63
+ return (
64
+ <Box>
65
+ <List dense>
66
+ {
67
+ entries.map((entry, index) => (
68
+ // eslint-disable-next-line react/no-array-index-key
69
+ <ListItem key={`entry-${index}`}>
70
+ <ListItemText primary={getValue(entry, catalog)} />
71
+ <ListItemSecondaryAction>
72
+ <IconButton
73
+ edge="end"
74
+ aria-label="delete"
75
+ size="small"
76
+ onClick={() => handleEntryDel(index)}
77
+ >
78
+ <DeleteIcon fontSize="small" />
79
+ </IconButton>
80
+ </ListItemSecondaryAction>
81
+ </ListItem>
82
+ ))
83
+ }
84
+ </List>
85
+ {
86
+ !inEdit
87
+ && (
88
+ <Box>
89
+ <Button
90
+ color="primary"
91
+ variant="outlined"
92
+ onClick={() => setInEdit(true)}
93
+ >
94
+ Eintrag hinzufügen
95
+ </Button>
96
+ </Box>
97
+ )
98
+ }
99
+ {
100
+ (inEdit && !catalog)
101
+ && (
102
+ <Box
103
+ width="100%"
104
+ display="flex"
105
+ alignItems="center"
106
+ >
107
+ <FormControl
108
+ variant="filled"
109
+ fullWidth
110
+ >
111
+ <TextField
112
+ label="Neuer Eintrag"
113
+ variant={variant}
114
+ value={inputValue}
115
+ InputProps={{
116
+ endAdornment: (
117
+ <InputAdornment position="end">
118
+ <Box mr={1}>
119
+ <IconButton size="small" onClick={handleEntryAdd}>
120
+ <SaveIcon fontSize="small" />
121
+ </IconButton>
122
+ </Box>
123
+ <IconButton size="small" onClick={handleCancelNewEntry}>
124
+ <ClearIcon fontSize="small" />
125
+ </IconButton>
126
+ </InputAdornment>
127
+ )
128
+ }}
129
+ onKeyDown={e => {
130
+ if (e.key === 'Enter') handleEntryAdd()
131
+ if (e.key === 'Escape') handleCancelNewEntry()
132
+ }}
133
+ onChange={e => setInputValue(e.target.value)}
134
+ />
135
+ </FormControl>
136
+ </Box>
137
+ )
138
+ }
139
+ {
140
+ (inEdit && catalog)
141
+ && (
142
+ <Box
143
+ width="100%"
144
+ display="flex"
145
+ alignItems="center"
146
+ >
147
+ <FormControl
148
+ variant="filled"
149
+ fullWidth
150
+ >
151
+ <InputLabel>Neuer Eintrag</InputLabel>
152
+ <Select
153
+ defaultValue="please-select"
154
+ onChange={e => setEntries([...entries, e.target.value])}
155
+ >
156
+ <MenuItem value="please-select" disabled>
157
+ Eintrag hinzufügen
158
+ </MenuItem>
159
+ {
160
+ Object
161
+ .entries(catalog)
162
+ .sort((a, b) => a[1].localeCompare(b[1]))
163
+ .map(e => (
164
+ <MenuItem
165
+ key={`option-${e[0]}`}
166
+ value={e[0]}
167
+ disabled={blockedOptions.includes(e[0])}
168
+ >
169
+ {e[1]}
170
+ </MenuItem>
171
+ ))
172
+ }
173
+ </Select>
174
+ </FormControl>
175
+ </Box>
176
+ )
177
+ }
178
+ </Box>
179
+ )
180
+ }
181
+
182
+ FormRepeater.propTypes = {
183
+ variant: PropTypes.oneOf(['standard', 'filled', 'outlined']),
184
+ name: PropTypes.string.isRequired,
185
+ value: PropTypes.any.isRequired,
186
+ catalog: PropTypes.object,
187
+ onChange: PropTypes.func.isRequired
188
+ }
189
+
190
+ export default FormRepeater
@@ -0,0 +1,49 @@
1
+ import React from 'react'
2
+ import PropTypes from 'prop-types'
3
+
4
+ import { TextField, MenuItem } from '@material-ui/core'
5
+
6
+ const FormSelect = ({ options, required, ...props }) => {
7
+ let unselectOption = []
8
+
9
+ if (!required) {
10
+ unselectOption = [{ label: 'Auswahl löschen', value: undefined }]
11
+ }
12
+
13
+ const allOptions = [
14
+ ...unselectOption,
15
+ ...options.sort((l, r) => l.label.localeCompare(r.label))
16
+ ]
17
+
18
+ return (
19
+ <TextField
20
+ select
21
+ {...props}
22
+ >
23
+ {
24
+ allOptions
25
+ .map(({ label: itemLabel, value: itemValue }) => (
26
+ <MenuItem
27
+ key={itemLabel}
28
+ value={itemValue}
29
+ >
30
+ {itemLabel}
31
+ </MenuItem>
32
+ ))
33
+ }
34
+ </TextField>
35
+ )
36
+ }
37
+
38
+ FormSelect.propTypes = {
39
+ name: PropTypes.string.isRequired,
40
+ value: PropTypes.string,
41
+ options: PropTypes.arrayOf(PropTypes.shape({
42
+ label: PropTypes.string.isRequired,
43
+ value: PropTypes.any
44
+ })).isRequired,
45
+ required: PropTypes.bool,
46
+ onChange: PropTypes.func.isRequired
47
+ }
48
+
49
+ export default FormSelect
@@ -0,0 +1,13 @@
1
+ import React from 'react'
2
+
3
+ import { TextField } from '@material-ui/core'
4
+
5
+ // eslint-disable-next-line react/prop-types, react/jsx-props-no-spreading
6
+ export default props => {
7
+ const finalProps = { ...props }
8
+ if (props.type === 'date' || props.type === 'datetime-local') {
9
+ finalProps.InputLabelProps={ shrink: true }
10
+ }
11
+
12
+ return <TextField {...finalProps} />
13
+ }