Skip to content

Commit cc38ee0

Browse files
committed
fix(mui): add default field array
1 parent 5a5cc41 commit cc38ee0

File tree

4 files changed

+304
-53
lines changed

4 files changed

+304
-53
lines changed
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
const arraySchemaDDF = {
2+
title: 'FieldArray',
3+
fields: [
4+
{
5+
component: 'field-array',
6+
name: 'nicePeople',
7+
fieldKey: 'field_array',
8+
label: 'Nice people',
9+
description: 'This allow you to add nice people to the list dynamically',
10+
itemDefault: { name: 'enter a name', lastName: 'enter a last name' },
11+
fields: [
12+
{
13+
component: 'text-field',
14+
name: 'name',
15+
label: 'Name',
16+
placeholder: 'Borek',
17+
isRequired: true,
18+
validate: [
19+
{
20+
type: 'required'
21+
}
22+
]
23+
},
24+
{
25+
component: 'text-field',
26+
name: 'lastName',
27+
label: 'Last Name',
28+
placeholder: 'Stavitel'
29+
}
30+
]
31+
},
32+
{
33+
component: 'field-array',
34+
name: 'minItems',
35+
label: 'A list with a minimal number of items',
36+
validate: [{ type: 'min-items', threshold: 3 }],
37+
fields: [
38+
{
39+
component: 'text-field',
40+
label: 'Item'
41+
}
42+
]
43+
},
44+
{
45+
component: 'field-array',
46+
name: 'number',
47+
defaultItem: 5,
48+
label: 'Default value with initialValues set',
49+
fields: [
50+
{
51+
component: 'text-field',
52+
label: 'Item',
53+
type: 'number'
54+
}
55+
]
56+
},
57+
{
58+
component: 'field-array',
59+
name: 'minMax',
60+
minItems: 4,
61+
maxItems: 6,
62+
label: 'Min 4 item, max 6 items without validators',
63+
fields: [
64+
{
65+
component: 'text-field',
66+
isRequired: true,
67+
validate: [
68+
{
69+
type: 'required'
70+
}
71+
]
72+
}
73+
]
74+
}
75+
]
76+
};
77+
78+
export default arraySchemaDDF;
Lines changed: 40 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,58 @@
1-
/* eslint-disable */
2-
import React from "react";
3-
import ReactDOM from "react-dom";
1+
import React, { useState } from 'react';
2+
import ReactDOM from 'react-dom';
43
import FormRenderer, { componentTypes } from '@data-driven-forms/react-form-renderer';
54

65
import Grid from '@material-ui/core/Grid';
7-
import { componentMapper, FormTemplate } from '../src'
6+
import { componentMapper, FormTemplate } from '../src';
87
import { createMuiTheme } from '@material-ui/core/styles';
98
import { ThemeProvider } from '@material-ui/styles';
109
import Typography from '@material-ui/core/Typography';
1110
import demoSchema from '@data-driven-forms/common/src/demoschema';
11+
import fieldArraySchema from './demo-schemas/field-array-schema';
12+
13+
import Button from '@material-ui/core/Button';
1214

1315
const theme = createMuiTheme({
14-
typography: {
15-
useNextVariants: true,
16-
},
17-
});
16+
typography: {
17+
useNextVariants: true
18+
}
19+
});
1820

1921
const compositeMapper = {
20-
...componentMapper,
21-
[componentTypes.SWITCH]: {
22-
component: componentMapper[componentTypes.SWITCH],
23-
FormControlLabelProps: {
24-
labelPlacement: 'end'
25-
}
22+
...componentMapper,
23+
[componentTypes.SWITCH]: {
24+
component: componentMapper[componentTypes.SWITCH],
25+
FormControlLabelProps: {
26+
labelPlacement: 'end'
2627
}
27-
}
28-
28+
}
29+
};
2930

30-
const schema = {
31-
fields: [{
32-
component: 'switch',
33-
name: 'foo',
34-
label: 'Foo'
35-
}, {
36-
component: 'text-field',
37-
name: 'bar',
38-
label: 'bar',
39-
condition: {
40-
when: 'foo',
41-
is: true
42-
}
43-
}]
44-
}
31+
const App = () => {
32+
const [schema, setSchema] = useState(fieldArraySchema);
4533

46-
const App = () => (
47-
<ThemeProvider theme={ theme }>
48-
<Grid
49-
container
50-
spacing={4}
51-
justify="center"
52-
alignItems="center"
53-
>
54-
<Grid item xs={12}>
55-
<Typography variant="h3" >Material UI component mapper</Typography>
56-
</Grid>
57-
<Grid item xs={12}>
58-
<FormRenderer
59-
onSubmit={console.log}
60-
componentMapper={compositeMapper}
61-
FormTemplate={props => <FormTemplate {...props} />}
62-
schema={schema}
63-
onCancel={() => console.log('canceling')}
64-
/>
65-
</Grid>
34+
return (
35+
<ThemeProvider theme={theme}>
36+
<Grid container spacing={4} justify="center" alignItems="center">
37+
<Grid item xs={12}>
38+
<Typography variant="h3">Material UI component mapper</Typography>
39+
</Grid>
40+
<Grid item xs={12}>
41+
<Button onClick={() => setSchema(demoSchema)}>Demo schema</Button>
42+
<Button onClick={() => setSchema(fieldArraySchema)}>Field array</Button>
43+
</Grid>
44+
<Grid item xs={12}>
45+
<FormRenderer
46+
onSubmit={console.log}
47+
componentMapper={compositeMapper}
48+
FormTemplate={(props) => <FormTemplate {...props} />}
49+
schema={schema}
50+
onCancel={() => console.log('canceling')}
51+
/>
6652
</Grid>
53+
</Grid>
6754
</ThemeProvider>
68-
)
55+
);
56+
};
6957

7058
ReactDOM.render(<App />, document.getElementById('root'));

packages/mui-component-mapper/src/files/component-mapper.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import PlainText from './plain-text';
1111
import Select from './select';
1212
import Radio from './radio';
1313
import Wizard from './wizard';
14+
import FieldArray from './field-array';
1415

1516
export const components = {
1617
TextField,
@@ -38,7 +39,8 @@ const componentMapper = {
3839
[componentTypes.TIME_PICKER]: TimePicker,
3940
[componentTypes.SWITCH]: Switch,
4041
[componentTypes.PLAIN_TEXT]: PlainText,
41-
[componentTypes.WIZARD]: Wizard
42+
[componentTypes.WIZARD]: Wizard,
43+
[componentTypes.FIELD_ARRAY]: FieldArray
4244
};
4345

4446
export default componentMapper;
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import { useFormApi, FieldArray } from '@data-driven-forms/react-form-renderer';
4+
5+
import Grid from '@material-ui/core/Grid';
6+
import Button from '@material-ui/core/Button';
7+
import Typography from '@material-ui/core/Typography';
8+
import FormControl from '@material-ui/core/FormControl';
9+
import FormHelperText from '@material-ui/core/FormHelperText';
10+
import { makeStyles } from '@material-ui/core/styles';
11+
12+
import { useFieldApi } from '@data-driven-forms/react-form-renderer';
13+
14+
import FormFieldGrid from '../common/form-field-grid';
15+
import clsx from 'clsx';
16+
17+
const useFielArrayStyles = makeStyles({
18+
formControl: {
19+
width: '100%'
20+
},
21+
centerText: {
22+
display: 'flex',
23+
justifyContent: 'center'
24+
},
25+
buttonsToEnd: {
26+
display: 'flex',
27+
justifyContent: 'flex-end'
28+
},
29+
header: {
30+
display: 'flex'
31+
},
32+
label: {
33+
flexGrow: 1
34+
},
35+
fieldArrayGroup: {
36+
marginBottom: 32
37+
}
38+
});
39+
40+
const ArrayItem = ({ fields, fieldIndex, name, remove, length, minItems, removeLabel }) => {
41+
const { renderForm } = useFormApi();
42+
const classes = useFielArrayStyles();
43+
44+
const editedFields = fields.map((field, index) => {
45+
const computedName = field.name ? `${name}.${field.name}` : name;
46+
return { ...field, name: computedName, key: `${computedName}-${index}` };
47+
});
48+
49+
return (
50+
<Grid container spacing={3}>
51+
<Grid item xs={12}>
52+
<Grid container spacing={3}>
53+
{renderForm([editedFields])}
54+
</Grid>
55+
</Grid>
56+
<Grid item xs={12} className={classes.buttonsToEnd}>
57+
<Button color="secondary" onClick={() => remove(fieldIndex)} disabled={length <= minItems}>
58+
{removeLabel}
59+
</Button>
60+
</Grid>
61+
</Grid>
62+
);
63+
};
64+
65+
ArrayItem.propTypes = {
66+
name: PropTypes.string,
67+
fieldIndex: PropTypes.number.isRequired,
68+
fields: PropTypes.arrayOf(PropTypes.object),
69+
remove: PropTypes.func.isRequired,
70+
length: PropTypes.number,
71+
minItems: PropTypes.number,
72+
removeLabel: PropTypes.node.isRequired
73+
};
74+
75+
const defaultButtonLabels = {
76+
add: 'ADD',
77+
remove: 'REMOVE'
78+
};
79+
80+
const DynamicArray = ({ ...props }) => {
81+
const {
82+
arrayValidator,
83+
label,
84+
description,
85+
fields: formFields,
86+
defaultItem,
87+
meta,
88+
minItems,
89+
maxItems,
90+
noItemsMessage,
91+
FormFieldGridProps,
92+
FormControlProps,
93+
buttonLabels,
94+
...rest
95+
} = useFieldApi(props);
96+
97+
const combinedButtonLabels = {
98+
...defaultButtonLabels,
99+
...buttonLabels
100+
};
101+
102+
const classes = useFielArrayStyles();
103+
104+
const { dirty, submitFailed, error } = meta;
105+
const isError = (dirty || submitFailed) && error && typeof error === 'string';
106+
107+
return (
108+
<FormFieldGrid {...FormFieldGridProps} className={clsx(classes.fieldArrayGroup, FormFieldGridProps.classname)}>
109+
<FormControl component="fieldset" error={isError} {...FormControlProps} className={clsx(classes.formControl, FormControlProps.className)}>
110+
<FieldArray key={rest.input.name} name={rest.input.name} validate={arrayValidator}>
111+
{({ fields: { map, value = [], push, remove } }) => (
112+
<Grid container spacing={3}>
113+
{label && (
114+
<Grid item xs={12} className={classes.header}>
115+
<Typography variant="h6" className={classes.label}>
116+
{label}
117+
</Typography>
118+
<Button color="primary" onClick={() => push(defaultItem)} disabled={value.length >= maxItems}>
119+
{combinedButtonLabels.add}
120+
</Button>
121+
</Grid>
122+
)}
123+
{description && (
124+
<Grid item xs={12}>
125+
<Typography variant="subtitle1">{description}</Typography>
126+
</Grid>
127+
)}
128+
{value.length <= 0 && (
129+
<Grid item xs={12}>
130+
<Typography variant="body1" gutterBottom className={classes.centerText}>
131+
{noItemsMessage}
132+
</Typography>
133+
</Grid>
134+
)}
135+
<Grid item xs={12}>
136+
{map((name, index) => (
137+
<ArrayItem
138+
key={`${name}-${index}`}
139+
fields={formFields}
140+
name={name}
141+
fieldIndex={index}
142+
remove={remove}
143+
length={value.length}
144+
minItems={minItems}
145+
removeLabel={combinedButtonLabels.remove}
146+
/>
147+
))}
148+
{isError && (
149+
<Grid item xs={12}>
150+
<FormHelperText>{error}</FormHelperText>
151+
</Grid>
152+
)}
153+
</Grid>
154+
</Grid>
155+
)}
156+
</FieldArray>
157+
</FormControl>
158+
</FormFieldGrid>
159+
);
160+
};
161+
162+
DynamicArray.propTypes = {
163+
label: PropTypes.node,
164+
description: PropTypes.node,
165+
fields: PropTypes.arrayOf(PropTypes.object).isRequired,
166+
defaultItem: PropTypes.any,
167+
minItems: PropTypes.number,
168+
maxItems: PropTypes.number,
169+
noItemsMessage: PropTypes.node,
170+
FormControlProps: PropTypes.object,
171+
FormFieldGridProps: PropTypes.object,
172+
buttonLabels: PropTypes.object
173+
};
174+
175+
DynamicArray.defaultProps = {
176+
maxItems: Infinity,
177+
minItems: 0,
178+
noItemsMessage: 'No items added',
179+
FormControlProps: {},
180+
FormFieldGridProps: {}
181+
};
182+
183+
export default DynamicArray;

0 commit comments

Comments
 (0)