@eeacms/volto-tabs-block 6.0.3 → 7.0.1

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 +115 -3
  2. package/README.md +17 -0
  3. package/cypress.config.js +2 -2
  4. package/locales/de/LC_MESSAGES/volto.po +76 -29
  5. package/locales/en/LC_MESSAGES/volto.po +76 -29
  6. package/locales/es/LC_MESSAGES/volto.po +77 -30
  7. package/locales/it/LC_MESSAGES/volto.po +76 -29
  8. package/locales/pt_BR/LC_MESSAGES/volto.po +77 -30
  9. package/locales/ro/LC_MESSAGES/volto.po +76 -29
  10. package/locales/volto.pot +77 -30
  11. package/package.json +1 -1
  12. package/src/components/Edit.jsx +119 -102
  13. package/src/components/View.jsx +6 -4
  14. package/src/components/index.js +1 -0
  15. package/src/components/layoutSchema.js +3 -1
  16. package/src/components/schema.js +169 -70
  17. package/src/components/templates/accordion/Edit.jsx +28 -199
  18. package/src/components/templates/accordion/View.jsx +42 -64
  19. package/src/components/templates/accordion/index.js +1 -2
  20. package/src/components/templates/accordion/schema.js +33 -40
  21. package/src/components/templates/carousel/HorizontalView.jsx +3 -0
  22. package/src/components/templates/carousel/VerticalView.jsx +3 -0
  23. package/src/components/templates/carousel/index.js +1 -2
  24. package/src/components/templates/carousel/schema.js +19 -25
  25. package/src/components/templates/default/Edit.jsx +90 -50
  26. package/src/components/templates/default/View.jsx +112 -42
  27. package/src/components/templates/default/index.js +2 -2
  28. package/src/components/templates/default/schema.js +69 -77
  29. package/src/components/templates/horizontal-responsive/Edit.jsx +63 -46
  30. package/src/components/templates/horizontal-responsive/View.jsx +33 -37
  31. package/src/components/templates/horizontal-responsive/index.js +1 -6
  32. package/src/components/templates/horizontal-responsive/schema.js +6 -5
  33. package/src/helpers.js +14 -10
  34. package/src/helpers.test.js +14 -8
  35. package/src/index.js +34 -22
  36. package/src/less/carousel.less +9 -0
  37. package/src/less/edit.less +26 -0
  38. package/src/less/menu.less +118 -4
  39. package/src/less/menu.variables +16 -0
  40. package/src/less/tabs.less +53 -23
  41. package/src/less/tabs.variables +3 -0
  42. package/src/widgets/TabsWidget.jsx +158 -68
@@ -2,24 +2,28 @@ import React from 'react';
2
2
  import { without } from 'lodash';
3
3
  import cx from 'classnames';
4
4
  import config from '@plone/volto/registry';
5
- import { SidebarPortal, BlocksToolbar } from '@plone/volto/components';
6
- import InlineForm from '@plone/volto/components/manage/Form/InlineForm';
5
+ import {
6
+ SidebarPortal,
7
+ BlocksToolbar,
8
+ BlockDataForm,
9
+ } from '@plone/volto/components';
7
10
  import { getBlocksLayoutFieldname } from '@plone/volto/helpers';
8
11
  import { TABS_BLOCK } from '@eeacms/volto-tabs-block/constants';
9
12
  import { empty, emptyTab } from '@eeacms/volto-tabs-block/helpers';
10
13
  import { StyleWrapperView } from '@eeacms/volto-block-style/StyleWrapper';
11
14
  import { BlockStyleWrapperEdit } from '@eeacms/volto-block-style/BlockStyleWrapper';
12
15
  import { DefaultEdit } from './templates/default';
13
- import { schema } from './schema';
16
+ import { useIntl } from 'react-intl';
14
17
 
15
18
  import '@eeacms/volto-tabs-block/less/edit.less';
16
19
  import '@eeacms/volto-tabs-block/less/tabs.less';
17
20
 
18
21
  const Edit = (props) => {
19
22
  const view = React.useRef(null);
23
+ const intl = useIntl();
20
24
  const { onChangeBlock, onChangeField } = props;
21
25
  const { data = {}, block = null } = props;
22
- const template = data.template || 'default';
26
+ const template = data.variation || 'default';
23
27
  const tabsData = data.data || {};
24
28
  const tabsList = tabsData.blocks_layout?.items || [];
25
29
  const tabs = tabsData.blocks || {};
@@ -32,25 +36,23 @@ const Edit = (props) => {
32
36
  const tabData = tabs[activeTab] || {};
33
37
  const theme = data.theme || 'light';
34
38
  const verticalAlign = data.verticalAlign || 'flex-start';
39
+ const tabsBlockConfig = config.blocks.blocksConfig[TABS_BLOCK];
35
40
 
36
- const TabsEdit =
37
- config.blocks.blocksConfig[TABS_BLOCK].templates?.[template]?.edit ||
38
- DefaultEdit;
39
- const templateSchema =
40
- config.blocks.blocksConfig[TABS_BLOCK].templates?.[template]?.schema || {};
41
-
42
- const schemaObject = schema(
43
- config,
44
- typeof templateSchema === 'function'
45
- ? templateSchema(config, props)
46
- : templateSchema,
47
- props,
48
- );
41
+ const activeTemplate = config.blocks.blocksConfig[
42
+ TABS_BLOCK
43
+ ].variations.filter((v, _i) => v.id === template);
44
+
45
+ const TabsEdit = activeTemplate?.[0]?.edit || DefaultEdit;
46
+
47
+ const schemaObject = tabsBlockConfig.blockSchema(props);
49
48
 
50
49
  React.useEffect(() => {
51
- if (!Object.keys(data.data || {}).length) {
50
+ if (!Object.keys(data?.data?.blocks || {}).length) {
52
51
  // Initialize TABS_BLOCK
53
- const tabsData = empty();
52
+ const tabsData = empty({
53
+ schema: schemaObject.properties.data.schema,
54
+ intl,
55
+ });
54
56
  onChangeBlock(block, {
55
57
  ...data,
56
58
  data: {
@@ -119,9 +121,9 @@ const Edit = (props) => {
119
121
 
120
122
  if (isMultipleSelection) {
121
123
  selected = null;
122
- const blocksLayoutFieldname = getBlocksLayoutFieldname(tabData);
124
+ const blocksLayoutFieldName = getBlocksLayoutFieldname(tabData);
123
125
 
124
- const blocks_layout = tabData[blocksLayoutFieldname].items;
126
+ const blocks_layout = tabData[blocksLayoutFieldName].items;
125
127
 
126
128
  if (event.shiftKey) {
127
129
  const anchor =
@@ -152,96 +154,111 @@ const Edit = (props) => {
152
154
  setActiveBlock(selected);
153
155
  setMultiSelected(newMultiSelected);
154
156
  };
157
+
155
158
  return (
156
- <BlockStyleWrapperEdit {...props}>
157
- <div
158
- className={cx('tabs-block edit', theme, verticalAlign, template)}
159
- ref={view}
160
- role="presentation"
161
- onKeyDown={(e) => {
162
- handleKeyDown(e, props.index, props.block, props.blockNode.current);
163
- }}
164
- // The tabIndex is required for the keyboard navigation
165
- /* eslint-disable jsx-a11y/no-noninteractive-tabindex */
166
- tabIndex={-1}
167
- >
168
- <StyleWrapperView
169
- {...props}
170
- data={tabData}
171
- styleData={tabData.styles || {}}
172
- styled={true}
159
+ <fieldset>
160
+ <BlockStyleWrapperEdit {...props}>
161
+ <legend
162
+ onClick={() => {
163
+ setActiveBlock(null);
164
+ props.setSidebarTab(1);
165
+ }}
166
+ aria-hidden="true"
167
+ >
168
+ Tabs
169
+ </legend>
170
+ <div
171
+ className={cx('tabs-block edit', theme, verticalAlign, template)}
172
+ ref={view}
173
+ role="presentation"
174
+ onKeyDown={(e) => {
175
+ handleKeyDown(e, props.index, props.block, props.blockNode.current);
176
+ }}
177
+ // The tabIndex is required for the keyboard navigation
178
+ /* eslint-disable jsx-a11y/no-noninteractive-tabindex */
179
+ tabIndex={-1}
173
180
  >
174
- <TabsEdit
181
+ <StyleWrapperView
175
182
  {...props}
176
- activeBlock={activeBlock}
177
- activeTab={activeTab}
178
- activeTabIndex={activeTabIndex}
179
- editingTab={editingTab}
180
- empty={empty}
181
- emptyTab={emptyTab}
182
- metadata={props.metadata || props.properties}
183
- multiSelected={multiSelected}
184
- tabs={tabs}
185
- tabData={tabData}
186
- tabsData={tabsData}
187
- tabsList={tabsList}
188
- node={view}
189
- template={template}
190
- onChangeTabData={onChangeTabData}
191
- onSelectBlock={onSelectBlock}
192
- setActiveBlock={setActiveBlock}
193
- setActiveTab={setActiveTab}
194
- setEditingTab={setEditingTab}
195
- />
196
- </StyleWrapperView>
197
-
198
- {props.selected ? (
199
- <BlocksToolbar
200
- formData={tabData}
201
- selectedBlock={activeTab}
202
- selectedBlocks={multiSelected}
203
- onChangeBlocks={(newBlockData) => {
204
- onChangeBlock(block, {
205
- ...data,
206
- data: {
207
- ...tabsData,
208
- blocks: {
209
- ...tabsData.blocks,
210
- [activeTab]: {
211
- ...tabData,
212
- ...newBlockData,
213
- },
214
- },
215
- },
216
- });
217
- }}
218
- onSetSelectedBlocks={(blockIds) => {
219
- setMultiSelected(blockIds);
220
- }}
221
- onSelectBlock={onSelectBlock}
222
- />
223
- ) : (
224
- ''
225
- )}
226
- {!data?.readOnlySettings && !activeBlock ? (
227
- <SidebarPortal selected={props.selected}>
228
- <InlineForm
183
+ data={tabData}
184
+ styleData={tabData.styles || {}}
185
+ styled={true}
186
+ >
187
+ <TabsEdit
188
+ {...props}
229
189
  schema={schemaObject}
230
- title={schemaObject.title}
231
- onChangeField={(id, value) => {
190
+ activeBlock={activeBlock}
191
+ activeTab={activeTab}
192
+ activeTabIndex={activeTabIndex}
193
+ editingTab={editingTab}
194
+ empty={empty}
195
+ emptyTab={emptyTab}
196
+ metadata={props.metadata || props.properties}
197
+ multiSelected={multiSelected}
198
+ tabs={tabs}
199
+ tabData={tabData}
200
+ tabsData={tabsData}
201
+ tabsList={tabsList}
202
+ node={view}
203
+ template={template}
204
+ onChangeTabData={onChangeTabData}
205
+ onSelectBlock={onSelectBlock}
206
+ setActiveBlock={setActiveBlock}
207
+ setActiveTab={setActiveTab}
208
+ setEditingTab={setEditingTab}
209
+ />
210
+ </StyleWrapperView>
211
+
212
+ {props.selected ? (
213
+ <BlocksToolbar
214
+ formData={tabData}
215
+ selectedBlock={activeTab}
216
+ selectedBlocks={multiSelected}
217
+ onChangeBlocks={(newBlockData) => {
232
218
  onChangeBlock(block, {
233
219
  ...data,
234
- [id]: value,
220
+ data: {
221
+ ...tabsData,
222
+ blocks: {
223
+ ...tabsData.blocks,
224
+ [activeTab]: {
225
+ ...tabData,
226
+ ...newBlockData,
227
+ },
228
+ },
229
+ },
235
230
  });
236
231
  }}
237
- formData={data}
232
+ onSetSelectedBlocks={(blockIds) => {
233
+ setMultiSelected(blockIds);
234
+ }}
235
+ onSelectBlock={onSelectBlock}
238
236
  />
239
- </SidebarPortal>
240
- ) : (
241
- ''
242
- )}
243
- </div>
244
- </BlockStyleWrapperEdit>
237
+ ) : (
238
+ ''
239
+ )}
240
+ {!data?.readOnlySettings && !activeBlock ? (
241
+ <SidebarPortal selected={props.selected}>
242
+ <BlockDataForm
243
+ block={block}
244
+ schema={schemaObject}
245
+ title={schemaObject.title}
246
+ onChangeBlock={onChangeBlock}
247
+ onChangeField={(id, value) => {
248
+ onChangeBlock(block, {
249
+ ...data,
250
+ [id]: value,
251
+ });
252
+ }}
253
+ formData={data}
254
+ />
255
+ </SidebarPortal>
256
+ ) : (
257
+ ''
258
+ )}
259
+ </div>
260
+ </BlockStyleWrapperEdit>
261
+ </fieldset>
245
262
  );
246
263
  };
247
264
 
@@ -17,7 +17,7 @@ const View = (props) => {
17
17
  const view = React.useRef(null);
18
18
  const { data = {}, uiContainer = '', location, history } = props;
19
19
  const metadata = props.metadata || props.properties;
20
- const template = data.template || 'default';
20
+ const template = data.variation || 'default';
21
21
  const tabsData = data.data || {};
22
22
  const tabsList = tabsData.blocks_layout?.items || [];
23
23
  const tabs = tabsData.blocks || {};
@@ -27,9 +27,11 @@ const View = (props) => {
27
27
  const theme = data.theme || 'light';
28
28
  const verticalAlign = data.verticalAlign || 'flex-start';
29
29
 
30
- const TabsView =
31
- config.blocks.blocksConfig[TABS_BLOCK].templates?.[template]?.view ||
32
- DefaultView;
30
+ const activeTemplate = config.blocks.blocksConfig[
31
+ TABS_BLOCK
32
+ ].variations.filter((v, _i) => v.id === template);
33
+
34
+ const TabsView = activeTemplate?.[0]?.view || DefaultView;
33
35
 
34
36
  const useQuery = () => {
35
37
  const { search } = location;
@@ -6,6 +6,7 @@ export { TabsEdit, TabsView };
6
6
 
7
7
  export { layoutSchema };
8
8
 
9
+ export * from './schema';
9
10
  export * from './templates/default';
10
11
  export * from './templates/accordion';
11
12
  export * from './templates/horizontal-responsive';
@@ -11,7 +11,7 @@ const blocksArray = (config) => {
11
11
  .filter((val) => !!val);
12
12
  };
13
13
 
14
- export default (config) => ({
14
+ const layoutSchema = (config) => ({
15
15
  title: 'Tabs block settings',
16
16
  fieldsets: [
17
17
  {
@@ -78,3 +78,5 @@ export default (config) => ({
78
78
  },
79
79
  required: [],
80
80
  });
81
+
82
+ export default layoutSchema;
@@ -1,111 +1,211 @@
1
- import { TABS_BLOCK } from '@eeacms/volto-tabs-block/constants';
1
+ import { cloneDeepSchema } from '@plone/volto/helpers/Utils/Utils';
2
2
  import { defineMessages } from 'react-intl';
3
3
 
4
4
  const messages = defineMessages({
5
- TabsBlock: {
6
- id: 'tabs-block',
7
- defaultMessage: 'Tabs block',
5
+ AssetPosition: {
6
+ defaultMessage: 'Asset position',
7
+ id: 'assetPosition',
8
+ },
9
+ AssetSize: {
10
+ defaultMessage: 'Asset size',
11
+ id: 'assetSize',
12
+ },
13
+ AssetType: {
14
+ defaultMessage: 'Asset type',
15
+ id: 'assetType',
16
+ },
17
+ Bottom: {
18
+ defaultMessage: 'Bottom',
19
+ id: 'bottom',
8
20
  },
9
21
  Default: {
10
- id: 'default',
11
22
  defaultMessage: 'Default',
23
+ id: 'default',
12
24
  },
13
- Tabs: {
14
- id: 'tabs',
15
- defaultMessage: 'Tabs',
25
+ Image: {
26
+ defaultMessage: 'Image',
27
+ id: 'image',
16
28
  },
17
- Title: {
18
- id: 'title',
19
- defaultMessage: 'Title',
29
+ Icon: {
30
+ defaultMessage: 'Icon',
31
+ id: 'icon',
20
32
  },
21
- Template: {
22
- id: 'template',
23
- defaultMessage: 'Template',
33
+ IconName: {
34
+ defaultMessage: 'Icon name',
35
+ id: 'iconName',
24
36
  },
25
- VerticalAlign: {
26
- id: 'vertical-align',
27
- defaultMessage: 'Vertical align',
37
+ LeftSide: {
38
+ id: 'left-side',
39
+ defaultMessage: 'Left side',
28
40
  },
29
- Top: {
30
- id: 'top',
31
- defaultMessage: 'Top',
41
+ RightSide: {
42
+ id: 'right-side',
43
+ defaultMessage: 'Right side',
44
+ },
45
+ Medium: {
46
+ defaultMessage: 'Medium',
47
+ id: 'Medium',
32
48
  },
33
49
  Middle: {
34
- id: 'middle',
35
50
  defaultMessage: 'Middle',
51
+ id: 'middle',
36
52
  },
37
- Bottom: {
38
- id: 'bottom',
39
- defaultMessage: 'Bottom',
53
+ Small: {
54
+ id: 'small',
55
+ defaultMessage: 'Small',
40
56
  },
41
- default: {
42
- id: 'default',
43
- defaultMessage: 'Default',
57
+ Tabs: {
58
+ defaultMessage: 'Tabs',
59
+ id: 'tabs',
60
+ },
61
+ TabsBlock: {
62
+ defaultMessage: 'Tabs block',
63
+ id: 'tabs-block',
64
+ },
65
+ TabTitle: {
66
+ defaultMessage: 'Tab title',
67
+ id: 'tabTitle',
68
+ },
69
+ Title: {
70
+ defaultMessage: 'Title',
71
+ id: 'title',
44
72
  },
45
- accordionResponsive: {
46
- id: 'accordionResponsive',
47
- defaultMessage: 'Accordion responsive',
73
+ Top: {
74
+ defaultMessage: 'Top',
75
+ id: 'top',
48
76
  },
49
- horizontalResponsive: {
50
- id: 'horizontalResponsive',
51
- defaultMessage: 'Horizontal responsive',
77
+ VerticalAlign: {
78
+ defaultMessage: 'Vertical align',
79
+ id: 'vertical-align',
52
80
  },
53
- carouselHorizontal: {
54
- id: 'carouselHorizontal',
55
- defaultMessage: 'Carousel horizontal',
81
+ Tab: {
82
+ id: 'tab',
83
+ defaultMessage: 'Tab',
56
84
  },
57
- carouselVerticalPrototype: {
58
- id: 'carouselVerticalPrototype',
59
- defaultMessage: 'Carousel vertical (prototype)',
85
+ HideTitle: {
86
+ id: 'hideTitle',
87
+ defaultMessage: 'Hide tab title?',
88
+ },
89
+ Large: {
90
+ id: 'large',
91
+ defaultMessage: 'Large',
92
+ },
93
+ Description: {
94
+ id: 'description',
95
+ defaultMessage: 'Description',
60
96
  },
61
97
  });
62
98
 
63
- export const schema = (config, templateSchema = {}, props) => {
64
- const { intl } = props;
65
- const templatesConfig = config.blocks.blocksConfig[TABS_BLOCK].templates;
66
- const templates = Object.keys(templatesConfig).map((template) => {
67
- let templateTitle = templatesConfig[template].title || template;
68
- let templateId = templatesConfig[template].id;
69
- if (templateId && messages[`${templateId}`]) {
70
- templateTitle = intl.formatMessage(messages[`${templateId}`]);
71
- }
72
- return [template, templateTitle];
73
- });
99
+ const tabSchema = (props) => {
100
+ const intl = props.intl;
101
+ return {
102
+ title: intl.formatMessage(messages.Tab),
74
103
 
75
- const defaultFieldset = templateSchema?.fieldsets?.filter(
76
- (fieldset) => fieldset.id === 'default',
77
- )[0];
104
+ fieldsets: [
105
+ {
106
+ id: 'default',
107
+ title: intl.formatMessage(messages.Default),
108
+ fields: ['title', 'assetType'],
109
+ },
110
+ ],
78
111
 
112
+ properties: {
113
+ title: {
114
+ title: intl.formatMessage(messages.TabTitle),
115
+ },
116
+ assetType: {
117
+ title: intl.formatMessage(messages.AssetType),
118
+ choices: [
119
+ ['image', intl.formatMessage(messages.Image)],
120
+ ['icon', intl.formatMessage(messages.Icon)],
121
+ ],
122
+ },
123
+ assetPosition: {
124
+ title: intl.formatMessage(messages.AssetPosition),
125
+ choices: [
126
+ ['top', intl.formatMessage(messages.Top)],
127
+ ['left', intl.formatMessage(messages.LeftSide)],
128
+ ['right', intl.formatMessage(messages.RightSide)],
129
+ ],
130
+ default: 'top',
131
+ },
132
+ image: {
133
+ title: intl.formatMessage(messages.Image),
134
+ widget: 'object_browser',
135
+ mode: 'image',
136
+ allowExternals: true,
137
+ selectedItemAttrs: ['image_field', 'image_scales'],
138
+ },
139
+ imageSize: {
140
+ title: intl.formatMessage(messages.AssetSize),
141
+ choices: [
142
+ ['icon', intl.formatMessage(messages.Small)],
143
+ ['tile', intl.formatMessage(messages.Medium)],
144
+ ['thumb', intl.formatMessage(messages.Large)],
145
+ ],
146
+ default: 'icon',
147
+ },
148
+ iconSize: {
149
+ title: intl.formatMessage(messages.AssetSize),
150
+ choices: [
151
+ ['small', intl.formatMessage(messages.Small)],
152
+ ['medium', intl.formatMessage(messages.Medium)],
153
+ ['large', intl.formatMessage(messages.Large)],
154
+ ],
155
+ default: 'small',
156
+ },
157
+ icon: {
158
+ title: intl.formatMessage(messages.IconName),
159
+ },
160
+ hideTitle: {
161
+ title: intl.formatMessage(messages.HideTitle),
162
+ type: 'boolean',
163
+ },
164
+ },
165
+
166
+ required: [],
167
+ };
168
+ };
169
+
170
+ const toggleIconField = (schema, child) => {
171
+ const cloned = cloneDeepSchema(schema);
172
+
173
+ cloned.fieldsets[0].fields = [
174
+ ...cloned.fieldsets[0].fields,
175
+ ...(child.assetType === 'icon'
176
+ ? ['icon', 'iconSize', 'assetPosition', 'hideTitle']
177
+ : []),
178
+ ...(child.assetType === 'image'
179
+ ? ['image', 'imageSize', 'assetPosition', 'hideTitle']
180
+ : []),
181
+ ];
182
+
183
+ return cloned;
184
+ };
185
+
186
+ export const blockSchema = (props) => {
187
+ const intl = props.intl;
79
188
  return {
80
- title: templateSchema?.title || intl.formatMessage(messages.TabsBlock),
189
+ title: intl.formatMessage(messages.TabsBlock),
81
190
  fieldsets: [
82
191
  {
83
192
  id: 'default',
84
193
  title: intl.formatMessage(messages.Default),
85
- fields: [
86
- 'data',
87
- 'title',
88
- 'template',
89
- 'verticalAlign',
90
- ...(defaultFieldset?.fields || []),
91
- ],
194
+ fields: ['title', 'description', 'verticalAlign', 'data'],
92
195
  },
93
- ...(templateSchema?.fieldsets?.filter(
94
- (fieldset) => fieldset.id !== 'default',
95
- ) || []),
96
196
  ],
97
197
  properties: {
98
198
  data: {
99
199
  title: intl.formatMessage(messages.Tabs),
100
200
  type: 'tabs',
201
+ schema: tabSchema(props),
202
+ schemaExtender: toggleIconField,
101
203
  },
102
204
  title: {
103
205
  title: intl.formatMessage(messages.Title),
104
206
  },
105
- template: {
106
- title: intl.formatMessage(messages.Template),
107
- choices: [...templates],
108
- default: 'default',
207
+ description: {
208
+ title: intl.formatMessage(messages.Description),
109
209
  },
110
210
  verticalAlign: {
111
211
  title: intl.formatMessage(messages.VerticalAlign),
@@ -116,8 +216,7 @@ export const schema = (config, templateSchema = {}, props) => {
116
216
  ],
117
217
  default: 'flex-start',
118
218
  },
119
- ...(templateSchema?.properties || {}),
120
219
  },
121
- required: [...(templateSchema?.required || [])],
220
+ required: [],
122
221
  };
123
222
  };