@eeacms/volto-eea-website-theme 3.7.0 → 3.9.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.
Files changed (42) hide show
  1. package/CHANGELOG.md +19 -2
  2. package/package.json +3 -1
  3. package/src/actions/index.js +1 -0
  4. package/src/actions/navigation.js +24 -0
  5. package/src/actions/print.js +9 -1
  6. package/src/components/manage/Blocks/ContextNavigation/variations/Accordion.jsx +42 -35
  7. package/src/components/manage/Blocks/LayoutSettings/LayoutSettingsEdit.test.jsx +383 -0
  8. package/src/components/manage/Blocks/Title/variations/WebReportPage.test.jsx +232 -0
  9. package/src/components/theme/Banner/View.jsx +11 -92
  10. package/src/components/theme/PrintLoader/PrintLoader.jsx +56 -0
  11. package/src/components/theme/PrintLoader/PrintLoader.test.jsx +91 -0
  12. package/src/components/theme/PrintLoader/style.less +12 -0
  13. package/src/components/theme/WebReport/WebReportSectionView.test.jsx +462 -0
  14. package/src/components/theme/Widgets/ImageViewWidget.test.jsx +26 -0
  15. package/src/components/theme/Widgets/NavigationBehaviorWidget.jsx +601 -0
  16. package/src/components/theme/Widgets/NavigationBehaviorWidget.test.jsx +507 -0
  17. package/src/components/theme/Widgets/SimpleArrayWidget.jsx +183 -0
  18. package/src/components/theme/Widgets/SimpleArrayWidget.test.jsx +283 -0
  19. package/src/constants/ActionTypes.js +2 -0
  20. package/src/customizations/volto/components/manage/History/History.diff +207 -0
  21. package/src/customizations/volto/components/manage/History/History.jsx +444 -0
  22. package/src/customizations/volto/components/theme/Comments/Comments.jsx +9 -2
  23. package/src/customizations/volto/components/theme/Comments/Comments.test.jsx +4 -4
  24. package/src/customizations/volto/components/theme/Comments/comments.less +16 -0
  25. package/src/customizations/volto/components/theme/Header/Header.jsx +60 -1
  26. package/src/customizations/volto/components/theme/View/DefaultView.jsx +42 -33
  27. package/src/customizations/volto/helpers/Html/Html.jsx +212 -0
  28. package/src/customizations/volto/helpers/Html/Readme.md +1 -0
  29. package/src/customizations/volto/server.jsx +375 -0
  30. package/src/helpers/loadLazyImages.js +11 -0
  31. package/src/helpers/loadLazyImages.test.js +22 -0
  32. package/src/helpers/setupPrintView.js +134 -0
  33. package/src/helpers/setupPrintView.test.js +49 -0
  34. package/src/index.js +11 -1
  35. package/src/index.test.js +6 -0
  36. package/src/middleware/voltoCustom.test.js +282 -0
  37. package/src/reducers/index.js +2 -1
  38. package/src/reducers/navigation/navigation.js +47 -0
  39. package/src/reducers/navigation/navigation.test.js +348 -0
  40. package/src/reducers/navigation.js +55 -0
  41. package/src/reducers/print.js +18 -8
  42. package/src/reducers/print.test.js +117 -0
@@ -0,0 +1,507 @@
1
+ import React from 'react';
2
+ import { render, screen, fireEvent, waitFor } from '@testing-library/react';
3
+ import { Provider } from 'react-intl-redux';
4
+ import configureStore from 'redux-mock-store';
5
+ import thunk from 'redux-thunk';
6
+
7
+ // Add jest-dom matchers
8
+ import '@testing-library/jest-dom';
9
+
10
+ const mockStore = configureStore([thunk]);
11
+
12
+ // Mock the getNavigation action
13
+ const mockGetNavigation = jest.fn(() => ({ type: 'GET_NAVIGATION' }));
14
+ jest.mock('@plone/volto/actions', () => ({
15
+ getNavigation: mockGetNavigation,
16
+ }));
17
+
18
+ // Mock the config
19
+ jest.mock('@plone/volto/registry', () => ({
20
+ default: {
21
+ settings: {
22
+ menuItemsLayouts: {
23
+ '/test-route-1': {
24
+ hideChildrenFromNavigation: false,
25
+ menuItemChildrenListColumns: [2, 3],
26
+ menuItemColumns: ['two wide column', 'three wide column'],
27
+ },
28
+ '/test-route-2': {
29
+ hideChildrenFromNavigation: true,
30
+ menuItemColumns: ['four wide column'],
31
+ },
32
+ '*': {
33
+ hideChildrenFromNavigation: true,
34
+ },
35
+ },
36
+ },
37
+ },
38
+ }));
39
+
40
+ // Mock uuid
41
+ jest.mock('uuid', () => ({
42
+ v4: () => 'test-uuid-123',
43
+ }));
44
+
45
+ // Mock Volto components
46
+ jest.mock('@plone/volto/components', () => ({
47
+ Icon: ({ name, size }) => (
48
+ <div data-testid="icon" data-name={name} data-size={size} />
49
+ ),
50
+ FormFieldWrapper: ({ children, ...props }) => (
51
+ <div data-testid="form-field-wrapper" {...props}>
52
+ {children}
53
+ </div>
54
+ ),
55
+ }));
56
+
57
+ // Mock ObjectWidget
58
+ jest.mock('@plone/volto/components/manage/Widgets/ObjectWidget', () => {
59
+ return function MockObjectWidget({ id, schema, value, onChange }) {
60
+ return (
61
+ <div data-testid="object-widget" data-id={id}>
62
+ {schema.properties.hideChildrenFromNavigation && (
63
+ <div>
64
+ <label>Hide Children From Navigation</label>
65
+ <input
66
+ type="checkbox"
67
+ checked={value.hideChildrenFromNavigation || false}
68
+ onChange={(e) =>
69
+ onChange(id, {
70
+ ...value,
71
+ hideChildrenFromNavigation: e.target.checked,
72
+ })
73
+ }
74
+ />
75
+ </div>
76
+ )}
77
+ {schema.properties.menuItemColumns && (
78
+ <div>
79
+ <label>Menu Item Columns</label>
80
+ <div data-testid="menu-item-columns">
81
+ {(value.menuItemColumns || []).map((col, index) => (
82
+ <span key={index}>{col}</span>
83
+ ))}
84
+ </div>
85
+ </div>
86
+ )}
87
+ {schema.properties.menuItemChildrenListColumns && (
88
+ <div>
89
+ <label>Menu Item Children List Columns</label>
90
+ <div data-testid="menu-item-children-columns">
91
+ {(value.menuItemChildrenListColumns || []).map((col, index) => (
92
+ <span key={index}>{col}</span>
93
+ ))}
94
+ </div>
95
+ </div>
96
+ )}
97
+ </div>
98
+ );
99
+ };
100
+ });
101
+
102
+ // Mock semantic-ui-react components
103
+ jest.mock('semantic-ui-react', () => {
104
+ const MockAccordion = ({ children, ...props }) => (
105
+ <div className="ui accordion" data-testid="accordion" {...props}>
106
+ {children}
107
+ </div>
108
+ );
109
+
110
+ MockAccordion.Title = ({ children, onClick, active, index }) => (
111
+ <button
112
+ type="button"
113
+ className={`title ${active ? 'active' : ''}`}
114
+ onClick={(e) => onClick(e, { index })}
115
+ data-testid="accordion-title"
116
+ >
117
+ {children}
118
+ </button>
119
+ );
120
+
121
+ MockAccordion.Content = ({ children, active }) => (
122
+ <div
123
+ className={`content ${active ? 'active' : ''}`}
124
+ data-testid="accordion-content"
125
+ >
126
+ {active && children}
127
+ </div>
128
+ );
129
+
130
+ return {
131
+ Accordion: MockAccordion,
132
+ Button: ({ children, ...props }) => <button {...props}>{children}</button>,
133
+ Segment: ({ children }) => <div data-testid="segment">{children}</div>,
134
+ Form: {
135
+ Field: ({ children }) => <div data-testid="form-field">{children}</div>,
136
+ },
137
+ Dropdown: ({ children }) => <div data-testid="dropdown">{children}</div>,
138
+ };
139
+ });
140
+
141
+ // Mock SVG imports
142
+ jest.mock('@plone/volto/icons/up-key.svg', () => 'up-icon');
143
+ jest.mock('@plone/volto/icons/down-key.svg', () => 'down-icon');
144
+
145
+ describe('NavigationBehaviorWidget', () => {
146
+ let store;
147
+ let NavigationBehaviorWidget;
148
+
149
+ const mockOnChange = jest.fn();
150
+
151
+ const defaultNavigationItems = [
152
+ {
153
+ '@id': 'http://localhost:3000/test-route-1',
154
+ title: 'Test Route 1',
155
+ url: '/test-route-1',
156
+ id: 'test-route-1',
157
+ portal_type: 'Document',
158
+ items: [
159
+ {
160
+ '@id': 'http://localhost:3000/test-route-1/child',
161
+ title: 'Child Route',
162
+ url: '/test-route-1/child',
163
+ id: 'child',
164
+ portal_type: 'Document',
165
+ items: [],
166
+ },
167
+ ],
168
+ },
169
+ {
170
+ '@id': 'http://localhost:3000/test-route-2',
171
+ title: 'Test Route 2',
172
+ url: '/test-route-2',
173
+ id: 'test-route-2',
174
+ portal_type: 'Folder',
175
+ items: [],
176
+ },
177
+ ];
178
+
179
+ beforeEach(() => {
180
+ jest.clearAllMocks();
181
+ store = mockStore({
182
+ intl: {
183
+ locale: 'en',
184
+ messages: {
185
+ 'Load Main Navigation Routes': 'Load Main Navigation Routes',
186
+ 'Hide Children From Navigation': 'Hide Children From Navigation',
187
+ 'Menu Item Children List Columns': 'Menu Item Children List Columns',
188
+ 'Menu Item Columns': 'Menu Item Columns',
189
+ },
190
+ },
191
+ navigation: {
192
+ items: defaultNavigationItems,
193
+ loaded: true,
194
+ },
195
+ vocabularies: {},
196
+ });
197
+
198
+ NavigationBehaviorWidget = require('./NavigationBehaviorWidget').default;
199
+ });
200
+
201
+ it('renders with navigation data', () => {
202
+ render(
203
+ <Provider store={store}>
204
+ <NavigationBehaviorWidget
205
+ id="test"
206
+ value="{}"
207
+ onChange={mockOnChange}
208
+ />
209
+ </Provider>,
210
+ );
211
+
212
+ expect(screen.getByText('Test Route 1')).toBeInTheDocument();
213
+ expect(screen.getByText('Test Route 2')).toBeInTheDocument();
214
+ });
215
+
216
+ it('dispatches getNavigation when not loaded', () => {
217
+ const storeNotLoaded = mockStore({
218
+ ...store.getState(),
219
+ navigation: { items: [], loaded: false },
220
+ });
221
+
222
+ render(
223
+ <Provider store={storeNotLoaded}>
224
+ <NavigationBehaviorWidget
225
+ id="test"
226
+ value="{}"
227
+ onChange={mockOnChange}
228
+ />
229
+ </Provider>,
230
+ );
231
+
232
+ expect(mockGetNavigation).toHaveBeenCalledWith('', 1);
233
+ });
234
+
235
+ it('handles accordion expansion', async () => {
236
+ render(
237
+ <Provider store={store}>
238
+ <NavigationBehaviorWidget
239
+ id="test"
240
+ value="{}"
241
+ onChange={mockOnChange}
242
+ />
243
+ </Provider>,
244
+ );
245
+
246
+ const accordionTitles = screen.getAllByTestId('accordion-title');
247
+ fireEvent.click(accordionTitles[0]);
248
+
249
+ await waitFor(() => {
250
+ expect(
251
+ screen.getByText('Hide Children From Navigation'),
252
+ ).toBeInTheDocument();
253
+ });
254
+ });
255
+
256
+ it('handles JSON parsing correctly', () => {
257
+ render(
258
+ <Provider store={store}>
259
+ <NavigationBehaviorWidget
260
+ id="test"
261
+ value='{"test": {"hideChildrenFromNavigation": false}}'
262
+ onChange={mockOnChange}
263
+ />
264
+ </Provider>,
265
+ );
266
+
267
+ expect(screen.getByText('Test Route 1')).toBeInTheDocument();
268
+ });
269
+
270
+ it('handles invalid JSON gracefully', () => {
271
+ render(
272
+ <Provider store={store}>
273
+ <NavigationBehaviorWidget
274
+ id="test"
275
+ value="invalid json"
276
+ onChange={mockOnChange}
277
+ />
278
+ </Provider>,
279
+ );
280
+
281
+ expect(screen.getByText('Test Route 1')).toBeInTheDocument();
282
+ });
283
+
284
+ it('handles object values correctly', () => {
285
+ render(
286
+ <Provider store={store}>
287
+ <NavigationBehaviorWidget
288
+ id="test"
289
+ value={{ '/test': { hideChildrenFromNavigation: false } }}
290
+ onChange={mockOnChange}
291
+ />
292
+ </Provider>,
293
+ );
294
+
295
+ expect(screen.getByText('Test Route 1')).toBeInTheDocument();
296
+ });
297
+
298
+ it('handles null values correctly', () => {
299
+ render(
300
+ <Provider store={store}>
301
+ <NavigationBehaviorWidget
302
+ id="test"
303
+ value={null}
304
+ onChange={mockOnChange}
305
+ />
306
+ </Provider>,
307
+ );
308
+
309
+ expect(screen.getByText('Test Route 1')).toBeInTheDocument();
310
+ });
311
+
312
+ it('auto-populates settings from config when no settings exist', async () => {
313
+ render(
314
+ <Provider store={store}>
315
+ <NavigationBehaviorWidget
316
+ id="test"
317
+ value="{}"
318
+ onChange={mockOnChange}
319
+ />
320
+ </Provider>,
321
+ );
322
+
323
+ await waitFor(() => {
324
+ expect(mockOnChange).toHaveBeenCalled();
325
+ });
326
+ });
327
+
328
+ it('handles empty navigation data', () => {
329
+ const emptyStore = mockStore({
330
+ ...store.getState(),
331
+ navigation: { items: [], loaded: true },
332
+ });
333
+
334
+ const { container } = render(
335
+ <Provider store={emptyStore}>
336
+ <NavigationBehaviorWidget
337
+ id="test"
338
+ value="{}"
339
+ onChange={mockOnChange}
340
+ />
341
+ </Provider>,
342
+ );
343
+
344
+ expect(
345
+ container.querySelector('.navigation-behavior-widget'),
346
+ ).toBeInTheDocument();
347
+ });
348
+
349
+ it('toggles accordion active state correctly', async () => {
350
+ render(
351
+ <Provider store={store}>
352
+ <NavigationBehaviorWidget
353
+ id="test"
354
+ value="{}"
355
+ onChange={mockOnChange}
356
+ />
357
+ </Provider>,
358
+ );
359
+
360
+ const accordionTitles = screen.getAllByTestId('accordion-title');
361
+
362
+ // Click to expand
363
+ fireEvent.click(accordionTitles[0]);
364
+ await waitFor(() => {
365
+ expect(accordionTitles[0]).toHaveClass('active');
366
+ });
367
+
368
+ // Click again to collapse
369
+ fireEvent.click(accordionTitles[0]);
370
+ await waitFor(() => {
371
+ expect(accordionTitles[0]).not.toHaveClass('active');
372
+ });
373
+ });
374
+
375
+ it('processes routes with config settings correctly', () => {
376
+ const storeWithConfig = mockStore({
377
+ ...store.getState(),
378
+ navigation: {
379
+ items: [
380
+ {
381
+ '@id': 'http://localhost:3000/test-route-1',
382
+ title: 'Test Route 1',
383
+ url: '/test-route-1',
384
+ items: [],
385
+ },
386
+ ],
387
+ loaded: true,
388
+ },
389
+ });
390
+
391
+ render(
392
+ <Provider store={storeWithConfig}>
393
+ <NavigationBehaviorWidget
394
+ id="test"
395
+ value="{}"
396
+ onChange={mockOnChange}
397
+ />
398
+ </Provider>,
399
+ );
400
+
401
+ expect(screen.getByText('Test Route 1')).toBeInTheDocument();
402
+ });
403
+
404
+ it('filters to show only level 0 routes', () => {
405
+ render(
406
+ <Provider store={store}>
407
+ <NavigationBehaviorWidget
408
+ id="test"
409
+ value="{}"
410
+ onChange={mockOnChange}
411
+ />
412
+ </Provider>,
413
+ );
414
+
415
+ // Should only show main routes, not child routes
416
+ expect(screen.getByText('Test Route 1')).toBeInTheDocument();
417
+ expect(screen.getByText('Test Route 2')).toBeInTheDocument();
418
+ expect(screen.queryByText('Child Route')).not.toBeInTheDocument();
419
+ });
420
+
421
+ it('handles routes without @id using fallback uuid', () => {
422
+ const storeWithoutIds = mockStore({
423
+ ...store.getState(),
424
+ navigation: {
425
+ items: [
426
+ {
427
+ title: 'Route Without ID',
428
+ url: '/no-id-route',
429
+ items: [],
430
+ },
431
+ ],
432
+ loaded: true,
433
+ },
434
+ });
435
+
436
+ render(
437
+ <Provider store={storeWithoutIds}>
438
+ <NavigationBehaviorWidget
439
+ id="test"
440
+ value="{}"
441
+ onChange={mockOnChange}
442
+ />
443
+ </Provider>,
444
+ );
445
+
446
+ expect(screen.getByText('Route Without ID')).toBeInTheDocument();
447
+ });
448
+
449
+ it('merges config and saved settings correctly', async () => {
450
+ const existingSettings = {
451
+ 'http://localhost:3000/test-route-1': {
452
+ hideChildrenFromNavigation: true,
453
+ menuItemColumns: [1, 2],
454
+ },
455
+ };
456
+
457
+ render(
458
+ <Provider store={store}>
459
+ <NavigationBehaviorWidget
460
+ id="test"
461
+ value={JSON.stringify(existingSettings)}
462
+ onChange={mockOnChange}
463
+ />
464
+ </Provider>,
465
+ );
466
+
467
+ const accordionTitles = screen.getAllByTestId('accordion-title');
468
+ fireEvent.click(accordionTitles[0]);
469
+
470
+ await waitFor(() => {
471
+ expect(
472
+ screen.getByText('Hide Children From Navigation'),
473
+ ).toBeInTheDocument();
474
+ });
475
+ });
476
+
477
+ it('handles routes with hasChildren property', () => {
478
+ render(
479
+ <Provider store={store}>
480
+ <NavigationBehaviorWidget
481
+ id="test"
482
+ value="{}"
483
+ onChange={mockOnChange}
484
+ />
485
+ </Provider>,
486
+ );
487
+
488
+ // Test Route 1 has children, Test Route 2 doesn't
489
+ expect(screen.getByText('Test Route 1')).toBeInTheDocument();
490
+ expect(screen.getByText('Test Route 2')).toBeInTheDocument();
491
+ });
492
+
493
+ it('displays route paths in accordion titles', () => {
494
+ render(
495
+ <Provider store={store}>
496
+ <NavigationBehaviorWidget
497
+ id="test"
498
+ value="{}"
499
+ onChange={mockOnChange}
500
+ />
501
+ </Provider>,
502
+ );
503
+
504
+ expect(screen.getByText('(/test-route-1)')).toBeInTheDocument();
505
+ expect(screen.getByText('(/test-route-2)')).toBeInTheDocument();
506
+ });
507
+ });
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Simple Array Widget that allows duplicates and direct typing
3
+ * @module components/manage/Widgets/SimpleArrayWidget
4
+ */
5
+
6
+ import React, { useState } from 'react';
7
+ import { defineMessages, injectIntl } from 'react-intl';
8
+ import PropTypes from 'prop-types';
9
+ import { Button, Input, Label } from 'semantic-ui-react';
10
+ import FormFieldWrapper from '@plone/volto/components/manage/Widgets/FormFieldWrapper';
11
+
12
+ const messages = defineMessages({
13
+ add: {
14
+ id: 'Add',
15
+ defaultMessage: 'Add',
16
+ },
17
+ remove: {
18
+ id: 'Remove',
19
+ defaultMessage: 'Remove',
20
+ },
21
+ });
22
+
23
+ const SimpleArrayWidget = (props) => {
24
+ const { id, value: rawValue, onChange, items, intl } = props;
25
+ const [newValue, setNewValue] = useState('');
26
+ const [showInput, setShowInput] = useState(false);
27
+
28
+ // Get min/max from schema
29
+ const minValue = items?.minimum || 1;
30
+ const maxValue = items?.maximum || 10;
31
+
32
+ // Ensure value is always an array
33
+ const value = Array.isArray(rawValue) ? rawValue : [];
34
+
35
+ const handleAdd = () => {
36
+ if (newValue.trim() !== '') {
37
+ const numValue = parseInt(newValue.trim());
38
+ if (!isNaN(numValue) && numValue >= minValue && numValue <= maxValue) {
39
+ const newArray = [...value, numValue];
40
+ onChange(id, newArray);
41
+ setNewValue('');
42
+ setShowInput(false); // Hide input after adding
43
+ }
44
+ }
45
+ };
46
+
47
+ const handleShowInput = () => {
48
+ setShowInput(true);
49
+ // Focus input after it appears
50
+ setTimeout(() => {
51
+ const input = document.querySelector(`#${id}-input`);
52
+ if (input) input.focus();
53
+ }, 100);
54
+ };
55
+
56
+ const handleCancel = () => {
57
+ setNewValue('');
58
+ setShowInput(false);
59
+ };
60
+
61
+ const handleRemove = (index) => {
62
+ const newArray = value.filter((_, i) => i !== index);
63
+ // If array becomes empty, pass null to remove the field from data completely
64
+ onChange(id, newArray.length === 0 ? null : newArray);
65
+ };
66
+
67
+ const handleKeyPress = (e) => {
68
+ if (e.key === 'Enter') {
69
+ e.preventDefault();
70
+ handleAdd();
71
+ } else if (e.key === 'Escape') {
72
+ e.preventDefault();
73
+ handleCancel();
74
+ }
75
+ };
76
+
77
+ return (
78
+ <FormFieldWrapper {...props}>
79
+ <div
80
+ style={{
81
+ display: 'flex',
82
+ alignItems: 'center',
83
+ flexWrap: 'wrap',
84
+ gap: '0.5rem',
85
+ paddingTop: '0.5rem',
86
+ }}
87
+ >
88
+ {/* Display current values as compact labels */}
89
+ {value.map((item, index) => {
90
+ // Force conversion to number for display (in case it comes as string)
91
+ const displayValue =
92
+ typeof item === 'string' ? parseInt(item) || item : item;
93
+ return (
94
+ <Label key={index} size="small" color="blue">
95
+ {displayValue}
96
+ <Label.Detail
97
+ as="a"
98
+ onClick={() => handleRemove(index)}
99
+ style={{ cursor: 'pointer' }}
100
+ title={intl.formatMessage(messages.remove)}
101
+ >
102
+ ×
103
+ </Label.Detail>
104
+ </Label>
105
+ );
106
+ })}
107
+
108
+ {/* Input field (conditionally shown) */}
109
+ {showInput && (
110
+ <div
111
+ style={{ display: 'flex', alignItems: 'center', gap: '0.25rem' }}
112
+ >
113
+ <Input
114
+ id={`${id}-input`}
115
+ type="number"
116
+ min={minValue}
117
+ max={maxValue}
118
+ value={newValue}
119
+ onChange={(e) => setNewValue(e.target.value)}
120
+ onKeyPress={handleKeyPress}
121
+ placeholder={`${minValue}-${maxValue}`}
122
+ size="mini"
123
+ style={{ width: '80px' }}
124
+ />
125
+ <Button
126
+ type="button"
127
+ primary
128
+ size="mini"
129
+ icon="check"
130
+ onClick={handleAdd}
131
+ disabled={!newValue.trim()}
132
+ title={intl.formatMessage(messages.add)}
133
+ />
134
+ <Button
135
+ type="button"
136
+ size="mini"
137
+ icon="close"
138
+ onClick={handleCancel}
139
+ title="Cancel"
140
+ />
141
+ </div>
142
+ )}
143
+
144
+ {/* Add button (shown when input is hidden) */}
145
+ {!showInput && (
146
+ <Button
147
+ type="button"
148
+ basic
149
+ size="mini"
150
+ icon="plus"
151
+ content={intl.formatMessage(messages.add)}
152
+ onClick={handleShowInput}
153
+ />
154
+ )}
155
+ </div>
156
+
157
+ {props.description && (
158
+ <div
159
+ style={{
160
+ fontSize: '0.85em',
161
+ color: '#767676',
162
+ marginTop: '0.5rem',
163
+ fontStyle: 'italic',
164
+ }}
165
+ >
166
+ {props.description}
167
+ </div>
168
+ )}
169
+ </FormFieldWrapper>
170
+ );
171
+ };
172
+
173
+ SimpleArrayWidget.propTypes = {
174
+ id: PropTypes.string.isRequired,
175
+ title: PropTypes.string.isRequired,
176
+ description: PropTypes.string,
177
+ value: PropTypes.arrayOf(PropTypes.number),
178
+ onChange: PropTypes.func.isRequired,
179
+ items: PropTypes.object,
180
+ intl: PropTypes.object.isRequired,
181
+ };
182
+
183
+ export default injectIntl(SimpleArrayWidget);