@eeacms/volto-eea-website-theme 0.5.2 → 0.6.0

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.
@@ -0,0 +1,321 @@
1
+ /**
2
+ * Modal form component.
3
+ * @module components/manage/Form/ModalForm
4
+ */
5
+
6
+ import React, { Component } from 'react';
7
+ import PropTypes from 'prop-types';
8
+ import { keys, map } from 'lodash';
9
+ import {
10
+ Button,
11
+ Form as UiForm,
12
+ Header,
13
+ Menu,
14
+ Message,
15
+ Modal,
16
+ Dimmer,
17
+ Loader,
18
+ } from 'semantic-ui-react';
19
+ import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
20
+ import { FormValidation } from '@plone/volto/helpers';
21
+ import { Field, Icon } from '@plone/volto/components';
22
+ import aheadSVG from '@plone/volto/icons/ahead.svg';
23
+ import clearSVG from '@plone/volto/icons/clear.svg';
24
+
25
+ const messages = defineMessages({
26
+ required: {
27
+ id: 'Required input is missing.',
28
+ defaultMessage: 'Required input is missing.',
29
+ },
30
+ minLength: {
31
+ id: 'Minimum length is {len}.',
32
+ defaultMessage: 'Minimum length is {len}.',
33
+ },
34
+ uniqueItems: {
35
+ id: 'Items must be unique.',
36
+ defaultMessage: 'Items must be unique.',
37
+ },
38
+ save: {
39
+ id: 'Save',
40
+ defaultMessage: 'Save',
41
+ },
42
+ cancel: {
43
+ id: 'Cancel',
44
+ defaultMessage: 'Cancel',
45
+ },
46
+ });
47
+
48
+ /**
49
+ * Modal form container class.
50
+ * @class ModalForm
51
+ * @extends Component
52
+ */
53
+ class ModalForm extends Component {
54
+ /**
55
+ * Property types.
56
+ * @property {Object} propTypes Property types.
57
+ * @static
58
+ */
59
+ static propTypes = {
60
+ schema: PropTypes.shape({
61
+ fieldsets: PropTypes.arrayOf(
62
+ PropTypes.shape({
63
+ fields: PropTypes.arrayOf(PropTypes.string),
64
+ id: PropTypes.string,
65
+ title: PropTypes.string,
66
+ }),
67
+ ),
68
+ properties: PropTypes.objectOf(PropTypes.any),
69
+ required: PropTypes.arrayOf(PropTypes.string),
70
+ }).isRequired,
71
+ title: PropTypes.string.isRequired,
72
+ formData: PropTypes.objectOf(PropTypes.any),
73
+ submitError: PropTypes.string,
74
+ onSubmit: PropTypes.func.isRequired,
75
+ onCancel: PropTypes.func,
76
+ open: PropTypes.bool,
77
+ submitLabel: PropTypes.string,
78
+ loading: PropTypes.bool,
79
+ loadingMessage: PropTypes.string,
80
+ className: PropTypes.string,
81
+ };
82
+
83
+ /**
84
+ * Default properties.
85
+ * @property {Object} defaultProps Default properties.
86
+ * @static
87
+ */
88
+ static defaultProps = {
89
+ submitLabel: null,
90
+ onCancel: null,
91
+ formData: {},
92
+ open: true,
93
+ loading: null,
94
+ loadingMessage: null,
95
+ submitError: null,
96
+ className: null,
97
+ };
98
+
99
+ /**
100
+ * Constructor
101
+ * @method constructor
102
+ * @param {Object} props Component properties
103
+ * @constructs ModalForm
104
+ */
105
+ constructor(props) {
106
+ super(props);
107
+ this.state = {
108
+ currentTab: 0,
109
+ errors: {},
110
+ isFormPristine: true,
111
+ formData: props.formData,
112
+ };
113
+ this.selectTab = this.selectTab.bind(this);
114
+ this.onChangeField = this.onChangeField.bind(this);
115
+ this.onBlurField = this.onBlurField.bind(this);
116
+ this.onClickInput = this.onClickInput.bind(this);
117
+ this.onSubmit = this.onSubmit.bind(this);
118
+ }
119
+
120
+ /**
121
+ * Change field handler
122
+ * @method onChangeField
123
+ * @param {string} id Id of the field
124
+ * @param {*} value Value of the field
125
+ * @returns {undefined}
126
+ */
127
+ onChangeField(id, value) {
128
+ this.setState({
129
+ formData: {
130
+ ...this.state.formData,
131
+ [id]: value,
132
+ },
133
+ });
134
+ }
135
+
136
+ /**
137
+ * If user clicks on input, the form will be not considered pristine
138
+ * this will avoid onBlur effects without interraction with the form
139
+ * @param {Object} e event
140
+ */
141
+ onClickInput(e) {
142
+ this.setState({ isFormPristine: false });
143
+ }
144
+
145
+ /**
146
+ * Validate fields on blur
147
+ * @method onBlurField
148
+ * @param {string} id Id of the field
149
+ * @param {*} value Value of the field
150
+ * @returns {undefined}
151
+ */
152
+ onBlurField(id, value) {
153
+ if (!this.state.isFormPristine) {
154
+ const errors = FormValidation.validateFieldsPerFieldset({
155
+ schema: this.props.schema,
156
+ formData: this.state.formData,
157
+ formatMessage: this.props.intl.formatMessage,
158
+ touchedField: { [id]: value },
159
+ });
160
+
161
+ this.setState({
162
+ errors,
163
+ });
164
+ }
165
+ }
166
+
167
+ /**
168
+ * Submit handler
169
+ * @method onSubmit
170
+ * @param {Object} event Event object.
171
+ * @returns {undefined}
172
+ */
173
+ onSubmit(event) {
174
+ event.preventDefault();
175
+ const errors = FormValidation.validateFieldsPerFieldset({
176
+ schema: this.props.schema,
177
+ formData: this.state.formData,
178
+ formatMessage: this.props.intl.formatMessage,
179
+ });
180
+
181
+ if (keys(errors).length > 0) {
182
+ this.setState({
183
+ errors,
184
+ });
185
+ } else {
186
+ let setFormDataCallback = (formData) => {
187
+ this.setState({ formData: formData, errors: {} });
188
+ };
189
+ this.props.onSubmit(this.state.formData, setFormDataCallback);
190
+ }
191
+ }
192
+
193
+ /**
194
+ * Select tab handler
195
+ * @method selectTab
196
+ * @param {Object} event Event object.
197
+ * @param {number} index Selected tab index.
198
+ * @returns {undefined}
199
+ */
200
+ selectTab(event, { index }) {
201
+ this.setState({
202
+ currentTab: index,
203
+ });
204
+ }
205
+
206
+ /**
207
+ * Render method.
208
+ * @method render
209
+ * @returns {string} Markup for the component.
210
+ */
211
+ render() {
212
+ const { schema, onCancel } = this.props;
213
+ const currentFieldset = schema.fieldsets[this.state.currentTab];
214
+
215
+ const fields = map(currentFieldset.fields, (field) => ({
216
+ ...schema.properties[field],
217
+ id: field,
218
+ value: this.state.formData[field],
219
+ required: schema.required.indexOf(field) !== -1,
220
+ onChange: this.onChangeField,
221
+ onBlur: this.onBlurField,
222
+ onClick: this.onClickInput,
223
+ }));
224
+
225
+ const state_errors = keys(this.state.errors).length > 0;
226
+ return (
227
+ <Modal open={this.props.open} className={this.props.className}>
228
+ <Header>{this.props.title}</Header>
229
+ <Dimmer active={this.props.loading}>
230
+ <Loader>
231
+ {this.props.loadingMessage || (
232
+ <FormattedMessage id="Loading" defaultMessage="Loading." />
233
+ )}
234
+ </Loader>
235
+ </Dimmer>
236
+ <Modal.Content scrolling>
237
+ <UiForm
238
+ method="post"
239
+ onSubmit={this.onSubmit}
240
+ error={state_errors || Boolean(this.props.submitError)}
241
+ >
242
+ <Message error>
243
+ {state_errors ? (
244
+ <FormattedMessage
245
+ id="There were some errors."
246
+ defaultMessage="There were some errors."
247
+ />
248
+ ) : (
249
+ ''
250
+ )}
251
+ <div>{this.props.submitError}</div>
252
+ </Message>
253
+ {schema.fieldsets.length > 1 && (
254
+ <Menu tabular stackable>
255
+ {map(schema.fieldsets, (item, index) => (
256
+ <Menu.Item
257
+ name={item.id}
258
+ index={index}
259
+ key={item.id}
260
+ active={this.state.currentTab === index}
261
+ onClick={this.selectTab}
262
+ >
263
+ {item.title}
264
+ </Menu.Item>
265
+ ))}
266
+ </Menu>
267
+ )}
268
+ {fields.map((field) => (
269
+ <Field
270
+ {...field}
271
+ key={field.id}
272
+ onBlur={this.onBlurField}
273
+ onClick={this.onClickInput}
274
+ error={this.state.errors[field.id]}
275
+ />
276
+ ))}
277
+ </UiForm>
278
+ </Modal.Content>
279
+ <Modal.Actions>
280
+ <Button
281
+ basic
282
+ icon
283
+ circular
284
+ primary
285
+ floated="right"
286
+ aria-label={
287
+ this.props.submitLabel
288
+ ? this.props.submitLabel
289
+ : this.props.intl.formatMessage(messages.save)
290
+ }
291
+ title={
292
+ this.props.submitLabel
293
+ ? this.props.submitLabel
294
+ : this.props.intl.formatMessage(messages.save)
295
+ }
296
+ onClick={this.onSubmit}
297
+ loading={this.props.loading}
298
+ >
299
+ <Icon name={aheadSVG} className="contents circled" size="30px" />
300
+ </Button>
301
+ {onCancel && (
302
+ <Button
303
+ basic
304
+ icon
305
+ circular
306
+ secondary
307
+ aria-label={this.props.intl.formatMessage(messages.cancel)}
308
+ title={this.props.intl.formatMessage(messages.cancel)}
309
+ floated="right"
310
+ onClick={onCancel}
311
+ >
312
+ <Icon name={clearSVG} className="circled" size="30px" />
313
+ </Button>
314
+ )}
315
+ </Modal.Actions>
316
+ </Modal>
317
+ );
318
+ }
319
+ }
320
+
321
+ export default injectIntl(ModalForm);