@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
@@ -0,0 +1,494 @@
1
+ import React from 'react'
2
+ import PropTypes from 'prop-types'
3
+
4
+ import { Input } from '@material-ui/core'
5
+
6
+ import { render as mm } from 'micromustache'
7
+
8
+ import FormSectionCard from './FormSectionCard'
9
+ import FormSectionBlock from './FormSectionBlock'
10
+
11
+ import FormField from './Editable/FormField'
12
+ import FormTextfield from './Editable/FormTextfield'
13
+ import FormNumber from './Editable/FormNumber'
14
+ import FormSelect from './Editable/FormSelect'
15
+ import FormBoolean from './Editable/FormBoolean'
16
+ import FormRepeater from './Editable/FormRepeater'
17
+
18
+ import FormReadOnlyField from './ReadOnly/FormField'
19
+ import FormReadOnlyText from './ReadOnly/FormReadOnlyText'
20
+ import FormMarkdown from './ReadOnly/FormMarkdown'
21
+ import FormReadOnlyNumber from './ReadOnly/FormNumber'
22
+ import FormReadOnlySelect from './ReadOnly/FormSelect'
23
+ import FormReadOnlyBoolean from './ReadOnly/FormBoolean'
24
+ import FormReadOnlyRepeater from './ReadOnly/FormRepeater'
25
+
26
+ import HiddenData from './HiddenData'
27
+
28
+ const formReadOnlyComponentMap = {
29
+ default: FormReadOnlyText,
30
+ markdown: FormMarkdown,
31
+ string: FormReadOnlyText,
32
+ number: FormReadOnlyNumber,
33
+ select: FormReadOnlySelect,
34
+ boolean: FormReadOnlyBoolean,
35
+ array: FormReadOnlyRepeater
36
+ }
37
+
38
+ const formComponentMap = {
39
+ default: FormTextfield,
40
+ string: FormTextfield,
41
+ number: FormNumber,
42
+ select: FormSelect,
43
+ boolean: FormBoolean,
44
+ array: FormRepeater
45
+ }
46
+
47
+ const apComponentMap = {
48
+ default: Input,
49
+ string: Input,
50
+ number: FormNumber,
51
+ select: FormSelect,
52
+ boolean: FormBoolean
53
+ }
54
+
55
+ class FormulatorFormSection extends React.Component {
56
+ getFieldProps(fieldEntry, componentMap) {
57
+ const {
58
+ schema,
59
+ translations,
60
+ errorMessages,
61
+ results,
62
+ data,
63
+ fieldStates
64
+ } = this.props
65
+
66
+ let field = fieldEntry, specialProps = {}
67
+ if (Array.isArray(fieldEntry)) {
68
+ if (fieldEntry[0] === '@@render') {
69
+ field = fieldEntry[1]
70
+ specialProps = {
71
+ isRender: true,
72
+ ...(fieldEntry[2] || {})
73
+ }
74
+ } else if (fieldEntry[0] === '@@markdown') {
75
+ return ({
76
+ type: 'markdown',
77
+ name: fieldEntry[1],
78
+ component: formReadOnlyComponentMap.markdown,
79
+ componentProps: {
80
+ content: fieldEntry[2]
81
+ }
82
+ })
83
+ } else {
84
+ field = fieldEntry[0]
85
+ specialProps = fieldEntry[1]
86
+ }
87
+ }
88
+
89
+ const { hidden, hideLabel, ...extraProps } = specialProps
90
+
91
+ if (hidden === true) return false
92
+
93
+ const fieldResult = results[field]
94
+ const { prohibited, required, optional, hasErrors, errors, data: fieldData } = fieldResult
95
+ if (prohibited === true || (!required && !optional && fieldData === undefined)) return false
96
+
97
+ const { type, default: defaultValue } = schema.properties[field]
98
+
99
+ let mapKey
100
+ if (specialProps.isRender) mapKey = type
101
+ else if (componentMap[field] !== undefined) mapKey = field
102
+ else if (typeof type === 'string' && componentMap[type] !== undefined) mapKey = type
103
+ else if (Array.isArray(type)) mapKey = 'select'
104
+ else if (componentMap.default !== undefined) mapKey = 'default'
105
+ else throw new Error(`Could not find a Component to render for "${field}"`)
106
+
107
+ const mapEntry = specialProps.isRender ? '@@render' : componentMap[mapKey]
108
+ let component = mapEntry, props = {}
109
+
110
+ if (mapKey === 'select') {
111
+ props.options = (
112
+ type.map(value => ({
113
+ label: (
114
+ translations[field]
115
+ && translations[field][value]
116
+ )
117
+ || value,
118
+ value
119
+ }))
120
+ )
121
+ }
122
+
123
+ if (Array.isArray(mapEntry)) {
124
+ /* eslint-disable prefer-destructuring */
125
+ component = mapEntry[0]
126
+ props = mapEntry[1]
127
+ /* eslint-enable prefer-destructuring */
128
+ }
129
+
130
+ return {
131
+ type: mapKey,
132
+ name: field,
133
+ label: hideLabel !== true && ((translations.labels && translations.labels[field]) || field),
134
+ defaultValue,
135
+ component,
136
+ componentProps: extraProps,
137
+ value: data[field],
138
+ required,
139
+ hasErrors,
140
+ errors: errors.map(({ message, options, validator: eVal, ...restError }) => {
141
+ let finalMessage = message
142
+
143
+ if (options && options.human) finalMessage = options.human
144
+ else if (errorMessages[eVal]) finalMessage = mm(errorMessages[eVal], options)
145
+
146
+ return ({
147
+ message: finalMessage,
148
+ options,
149
+ validator: eVal,
150
+ ...restError
151
+ })
152
+ }),
153
+ ...props,
154
+ ...fieldStates[field]
155
+ }
156
+ }
157
+
158
+ getSectionContent() {
159
+ const {
160
+ id,
161
+ variant,
162
+ inputVariant,
163
+ readOnly,
164
+ nested = 0,
165
+ schema,
166
+ fields,
167
+ hideSensitiveData,
168
+ results,
169
+ data,
170
+ fieldStates,
171
+ translations,
172
+ renderComponentMap,
173
+ errorMessages,
174
+ onChange,
175
+ disabled
176
+ } = this.props
177
+
178
+ const allFieldProps = (
179
+ fields
180
+ .filter(f => typeof f === 'string' || Array.isArray(f))
181
+ .map(fieldEntry => this.getFieldProps(fieldEntry, readOnly ? formReadOnlyComponentMap : formComponentMap))
182
+ )
183
+
184
+ return (
185
+ Array.isArray(fields)
186
+ && (
187
+ fields.map((fieldEntry, fieldIndex) => {
188
+ if (fieldEntry === false) return false
189
+
190
+ if (Array.isArray(fieldEntry) && fieldEntry[0] === '@@render') {
191
+ const cKey = fieldEntry[1]
192
+ const component = renderComponentMap && renderComponentMap[cKey]
193
+ if (!component) throw new Error(`Could not find Component for render field "${cKey}"`)
194
+
195
+ const fieldProps = this.getFieldProps(fieldEntry)
196
+
197
+ const {
198
+ type,
199
+ name,
200
+ componentProps,
201
+ label,
202
+ value,
203
+ required,
204
+ hasErrors,
205
+ errors,
206
+ validating,
207
+ dirty
208
+ } = fieldProps
209
+
210
+ if (readOnly && !value) return false
211
+
212
+ return React.createElement(
213
+ readOnly ? FormReadOnlyField : FormField,
214
+ {
215
+ key: `form-render-component-${cKey}`,
216
+ variant: inputVariant,
217
+ name,
218
+ type,
219
+ label,
220
+ value,
221
+ isRender: true,
222
+ required,
223
+ hasErrors,
224
+ errors,
225
+ validating,
226
+ dirty,
227
+ component,
228
+ componentProps,
229
+ disabled,
230
+ readOnly,
231
+ fields,
232
+ results,
233
+ data,
234
+ fieldStates,
235
+ onChange
236
+ }
237
+ )
238
+ }
239
+
240
+ if (typeof fieldEntry !== 'string' && !Array.isArray(fieldEntry)) {
241
+ return (
242
+ <FormulatorFormSection
243
+ key={`form-section-${fieldEntry.id}`}
244
+ variant={variant}
245
+ inputVariant={inputVariant}
246
+ readOnly={readOnly}
247
+ id={fieldEntry.id}
248
+ nested={nested + 1}
249
+ sectionId={fieldEntry.id}
250
+ schema={schema}
251
+ label={fieldEntry.label}
252
+ fields={fieldEntry.fields}
253
+ hideSensitiveData={hideSensitiveData}
254
+ results={results}
255
+ data={data}
256
+ fieldStates={fieldStates}
257
+ translations={translations}
258
+ errorMessages={errorMessages}
259
+ renderComponentMap={renderComponentMap}
260
+ onChange={onChange}
261
+ disabled={disabled}
262
+ />
263
+ )
264
+ }
265
+
266
+ if (Array.isArray(fieldEntry) && (fieldEntry[1].appends || fieldEntry[1].prepends)) return false
267
+
268
+ const fieldProps = allFieldProps[fieldIndex]
269
+ if (!fieldProps) return false
270
+
271
+ const {
272
+ type,
273
+ name,
274
+ component,
275
+ componentProps,
276
+ label,
277
+ value,
278
+ defaultValue,
279
+ options,
280
+ required,
281
+ hasErrors,
282
+ errors,
283
+ validating,
284
+ dirty
285
+ } = fieldProps
286
+
287
+ if (readOnly && !value) return false
288
+
289
+ const apProps = {}
290
+
291
+ for (let i = 0; i < fields.length; i++) {
292
+ const apField = fields[i]
293
+
294
+ if (Array.isArray(apField) && apField[1].appends && apField[1].appends === name) {
295
+ if (!apProps.append) apProps.append = []
296
+ const aProps = this.getFieldProps(apField, apComponentMap)
297
+
298
+ apProps.append.push(
299
+ React.createElement(
300
+ aProps.component,
301
+ {
302
+ key: `form-field-${name}-append-${aProps.name}`,
303
+ variant: inputVariant,
304
+ disabled,
305
+ onChange,
306
+ ...aProps
307
+ }
308
+ )
309
+ )
310
+ }
311
+
312
+ if (Array.isArray(apField) && apField[1].prepends && apField[1].prepends === name) {
313
+ if (!apProps.prepend) apProps.prepend = []
314
+ const aProps = this.getFieldProps(apField, apComponentMap)
315
+
316
+ apProps.prepend.push(
317
+ React.createElement(
318
+ aProps.component,
319
+ {
320
+ key: `form-field-${name}-append-${aProps.name}`,
321
+ variant: inputVariant,
322
+ disabled,
323
+ onChange,
324
+ ...aProps
325
+ }
326
+ )
327
+ )
328
+ }
329
+ }
330
+
331
+ const FormFieldComponent = readOnly ? FormReadOnlyField : FormField
332
+
333
+ if (hideSensitiveData && (name === 'reportingPin' || name === 'reportingAuthCode')) {
334
+ return <HiddenData subject="Das Feld" label={label} />
335
+ }
336
+
337
+ return (
338
+ React.createElement(
339
+ FormFieldComponent,
340
+ {
341
+ key: `form-${id}-${name}`,
342
+ variant: inputVariant,
343
+ type,
344
+ name,
345
+ label,
346
+ defaultValue,
347
+ value,
348
+ options,
349
+ required,
350
+ hasErrors,
351
+ errors,
352
+ validating,
353
+ dirty,
354
+ component,
355
+ componentProps,
356
+ prepend: apProps.prepend,
357
+ append: apProps.append,
358
+ disabled,
359
+ onChange
360
+ }
361
+ )
362
+ )
363
+ })
364
+ )
365
+ )
366
+ }
367
+
368
+ render() {
369
+ const {
370
+ variant,
371
+ readOnly,
372
+ nested = 0,
373
+ sectionId,
374
+ label,
375
+ labelVariant,
376
+ desc,
377
+ descVariant,
378
+ textAlign,
379
+ fields
380
+ } = this.props
381
+
382
+ const allFieldProps = (
383
+ fields
384
+ .filter(f => typeof f === 'string' || Array.isArray(f))
385
+ .map(fieldEntry => this.getFieldProps(fieldEntry, readOnly ? formReadOnlyComponentMap : formComponentMap))
386
+ )
387
+
388
+ const renderableFields = allFieldProps.filter(fp => {
389
+ if (
390
+ Array.isArray(fp)
391
+ && (
392
+ fp[0] === '@@render'
393
+ || fp[0] === '@@markdown'
394
+ )
395
+ ) return false
396
+
397
+ return fp !== false
398
+ })
399
+
400
+ if (renderableFields.length === 0) return false
401
+
402
+ const sectionContent = this.getSectionContent()
403
+
404
+ if (readOnly && sectionContent.every(c => c === false)) return false
405
+
406
+ if (nested === 0) {
407
+ const FormSection = variant === 'card' ? FormSectionCard : FormSectionBlock
408
+
409
+ return (
410
+ React.createElement(
411
+ FormSection,
412
+ {
413
+ key: `form-section-${sectionId}`,
414
+ groupId: sectionId,
415
+ label,
416
+ labelVariant,
417
+ desc,
418
+ descVariant,
419
+ textAlign
420
+ },
421
+ sectionContent
422
+ )
423
+ )
424
+ }
425
+
426
+ return (
427
+ <FormSectionBlock
428
+ key={`form-section-${sectionId}`}
429
+ label={groupLabel}
430
+ labelVariant={groupLabelVariant}
431
+ desc={groupDesc}
432
+ descVariant={groupDescVariant}
433
+ textAlign={textAlign}
434
+ >
435
+ {sectionContent}
436
+ </FormSectionBlock>
437
+ )
438
+ }
439
+ }
440
+
441
+ FormulatorFormSection.propTypes = {
442
+ id: PropTypes.string.isRequired,
443
+ variant: PropTypes.string.isRequired,
444
+ inputVariant: PropTypes.oneOf(['standard', 'filled', 'outlined']),
445
+ label: PropTypes.string,
446
+ desc: PropTypes.string,
447
+ labelVariant: PropTypes.oneOf([
448
+ 'h1',
449
+ 'h2',
450
+ 'h3',
451
+ 'h4',
452
+ 'h5',
453
+ 'h6',
454
+ 'subtitle1',
455
+ 'subtitle2',
456
+ 'body1',
457
+ 'body2',
458
+ 'button',
459
+ 'caption',
460
+ 'overline'
461
+ ]),
462
+ descVariant: PropTypes.oneOf([
463
+ 'h1',
464
+ 'h2',
465
+ 'h3',
466
+ 'h4',
467
+ 'h5',
468
+ 'h6',
469
+ 'subtitle1',
470
+ 'subtitle2',
471
+ 'body1',
472
+ 'body2',
473
+ 'button',
474
+ 'caption',
475
+ 'overline'
476
+ ]),
477
+ textAlign: PropTypes.oneOf(['left', 'center', 'right']),
478
+ disabled: PropTypes.bool,
479
+ readOnly: PropTypes.bool,
480
+ nested: PropTypes.number,
481
+ sectionId: PropTypes.string.isRequired,
482
+ fields: PropTypes.array.isRequired,
483
+ hideSensitiveData: PropTypes.bool,
484
+ schema: PropTypes.object.isRequired,
485
+ results: PropTypes.object.isRequired,
486
+ data: PropTypes.object.isRequired,
487
+ fieldStates: PropTypes.object.isRequired,
488
+ translations: PropTypes.object.isRequired,
489
+ errorMessages: PropTypes.object.isRequired,
490
+ renderComponentMap: PropTypes.object,
491
+ onChange: PropTypes.func.isRequired
492
+ }
493
+
494
+ export default FormulatorFormSection
@@ -0,0 +1,24 @@
1
+ import React from 'react'
2
+ import PropTypes from 'prop-types'
3
+
4
+ import { Box, Typography } from '@material-ui/core'
5
+ import { Alert } from '@material-ui/lab'
6
+
7
+ const HiddenData = ({ subject, label }) => (
8
+ <Box mb={2}>
9
+ <Alert severity="warning">
10
+ <Typography variant="body1">
11
+ {subject ? `${subject} ` : ''}
12
+ <span style={{ fontWeight: 'bold' }}>{label}</span>
13
+ &nbsp;ist ausgeblendet.
14
+ </Typography>
15
+ </Alert>
16
+ </Box>
17
+ )
18
+
19
+ HiddenData.propTypes = {
20
+ subject: PropTypes.string,
21
+ label: PropTypes.string.isRequired
22
+ }
23
+
24
+ export default HiddenData
@@ -0,0 +1,36 @@
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 }) => {
10
+ if (variant === 'checkbox') {
11
+ return (
12
+ <Checkbox
13
+ name={name}
14
+ checked={isChecked(value, defaultValue)}
15
+ disabled
16
+ />
17
+ )
18
+ }
19
+
20
+ return (
21
+ <Switch
22
+ name={name}
23
+ checked={isChecked(value, defaultValue)}
24
+ disabled
25
+ />
26
+ )
27
+ }
28
+
29
+ FormBoolean.propTypes = {
30
+ variant: PropTypes.string,
31
+ name: PropTypes.string.isRequired,
32
+ value: PropTypes.bool,
33
+ defaultValue: PropTypes.bool
34
+ }
35
+
36
+ export default FormBoolean
@@ -0,0 +1,126 @@
1
+ import React from 'react'
2
+ import PropTypes from 'prop-types'
3
+
4
+ import { Typography, Box } from '@material-ui/core'
5
+
6
+ const FormControlField = ({
7
+ isRender,
8
+ name,
9
+ label,
10
+ component,
11
+ componentProps,
12
+ value,
13
+ options,
14
+ required
15
+ }) => {
16
+ const finalProps = {
17
+ ...componentProps,
18
+ name,
19
+ label,
20
+ value,
21
+ options,
22
+ required,
23
+ readOnly: true
24
+ }
25
+
26
+ if (componentProps?.readOnlyHidden === true) {
27
+ return false
28
+ }
29
+
30
+ let control
31
+ if (isRender) control = React.cloneElement(component, finalProps)
32
+ else control = React.createElement(component, finalProps)
33
+
34
+ return (
35
+ <Box
36
+ width="100%"
37
+ display="flex"
38
+ flexDirection="column"
39
+ mb={1.5}
40
+ >
41
+ {
42
+ label
43
+ && (
44
+ <Typography
45
+ variant="caption"
46
+ color="textSecondary"
47
+ >
48
+ {label}
49
+ </Typography>
50
+ )
51
+ }
52
+ {control}
53
+ </Box>
54
+ )
55
+ }
56
+
57
+ FormControlField.propTypes = {
58
+ isRender: PropTypes.bool,
59
+ component: PropTypes.any.isRequired,
60
+ componentProps: PropTypes.object,
61
+ name: PropTypes.string.isRequired,
62
+ label: PropTypes.string.isRequired,
63
+ value: PropTypes.oneOfType([
64
+ PropTypes.string,
65
+ PropTypes.number,
66
+ PropTypes.bool,
67
+ PropTypes.array
68
+ ]),
69
+ options: PropTypes.arrayOf(PropTypes.shape({
70
+ label: PropTypes.string.isRequired,
71
+ value: PropTypes.any
72
+ })),
73
+ required: PropTypes.bool
74
+ }
75
+
76
+ FormControlField.defaultProps = {
77
+ componentProps: {}
78
+ }
79
+
80
+ const FormField = ({
81
+ prepend,
82
+ append,
83
+ componentProps,
84
+ ...fieldProps
85
+ }) => {
86
+ const finalProps = {
87
+ componentProps,
88
+ ...fieldProps
89
+ }
90
+
91
+ if (componentProps?.type === 'hidden') {
92
+ return <input type="hidden" name={fieldProps.name} value={fieldProps.value} />
93
+ }
94
+
95
+ return (
96
+ // eslint-disable-next-line react/jsx-props-no-spreading
97
+ <FormControlField {...finalProps} />
98
+ )
99
+ }
100
+
101
+ FormField.propTypes = {
102
+ component: PropTypes.any.isRequired,
103
+ componentProps: PropTypes.object,
104
+ type: PropTypes.string.isRequired,
105
+ name: PropTypes.string.isRequired,
106
+ label: PropTypes.string.isRequired,
107
+ value: PropTypes.oneOfType([
108
+ PropTypes.string,
109
+ PropTypes.number,
110
+ PropTypes.bool,
111
+ PropTypes.array
112
+ ]),
113
+ options: PropTypes.arrayOf(PropTypes.shape({
114
+ label: PropTypes.string.isRequired,
115
+ value: PropTypes.any
116
+ })),
117
+ prepend: PropTypes.array,
118
+ append: PropTypes.array,
119
+ hasErrors: PropTypes.bool,
120
+ errors: PropTypes.array,
121
+ validating: PropTypes.bool,
122
+ required: PropTypes.bool,
123
+ dirty: PropTypes.bool
124
+ }
125
+
126
+ export default React.memo(FormField)
@@ -0,0 +1,20 @@
1
+ import React from 'react'
2
+ import PropTypes from 'prop-types'
3
+
4
+ import ReactMarkdown from 'react-markdown'
5
+ import remarkGfm from 'remark-gfm'
6
+ import rehypeHighlight from 'rehype-highlight'
7
+
8
+ const FormMarkdown = ({ content }) => (
9
+ <ReactMarkdown
10
+ children={content}
11
+ remarkPlugins={[remarkGfm]}
12
+ rehypePlugins={[rehypeHighlight]}
13
+ />
14
+ )
15
+
16
+ FormMarkdown.propTypes = {
17
+ content: PropTypes.string
18
+ }
19
+
20
+ export default FormMarkdown
@@ -0,0 +1,17 @@
1
+ import React from 'react'
2
+ import PropTypes from 'prop-types'
3
+
4
+ import FormReadOnlyText from './FormReadOnlyText'
5
+
6
+ import { castToNumber } from '../helpers'
7
+
8
+ const FormNumber = ({ value }) => <FormReadOnlyText value={castToNumber(value)} />
9
+
10
+ FormNumber.propTypes = {
11
+ value: PropTypes.oneOfType([
12
+ PropTypes.number,
13
+ PropTypes.string
14
+ ])
15
+ }
16
+
17
+ export default FormNumber