@eeacms/volto-tabs-block 6.0.3 → 7.0.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 (41) hide show
  1. package/CHANGELOG.md +81 -3
  2. package/README.md +17 -0
  3. package/locales/de/LC_MESSAGES/volto.po +76 -29
  4. package/locales/en/LC_MESSAGES/volto.po +76 -29
  5. package/locales/es/LC_MESSAGES/volto.po +77 -30
  6. package/locales/it/LC_MESSAGES/volto.po +76 -29
  7. package/locales/pt_BR/LC_MESSAGES/volto.po +77 -30
  8. package/locales/ro/LC_MESSAGES/volto.po +76 -29
  9. package/locales/volto.pot +77 -30
  10. package/package.json +1 -1
  11. package/src/components/Edit.jsx +119 -102
  12. package/src/components/View.jsx +6 -4
  13. package/src/components/index.js +1 -0
  14. package/src/components/layoutSchema.js +3 -1
  15. package/src/components/schema.js +169 -70
  16. package/src/components/templates/accordion/Edit.jsx +28 -199
  17. package/src/components/templates/accordion/View.jsx +42 -64
  18. package/src/components/templates/accordion/index.js +1 -2
  19. package/src/components/templates/accordion/schema.js +33 -40
  20. package/src/components/templates/carousel/HorizontalView.jsx +3 -0
  21. package/src/components/templates/carousel/VerticalView.jsx +3 -0
  22. package/src/components/templates/carousel/index.js +1 -2
  23. package/src/components/templates/carousel/schema.js +19 -25
  24. package/src/components/templates/default/Edit.jsx +90 -50
  25. package/src/components/templates/default/View.jsx +112 -43
  26. package/src/components/templates/default/index.js +2 -2
  27. package/src/components/templates/default/schema.js +69 -77
  28. package/src/components/templates/horizontal-responsive/Edit.jsx +63 -46
  29. package/src/components/templates/horizontal-responsive/View.jsx +33 -37
  30. package/src/components/templates/horizontal-responsive/index.js +1 -6
  31. package/src/components/templates/horizontal-responsive/schema.js +6 -5
  32. package/src/helpers.js +14 -10
  33. package/src/helpers.test.js +14 -8
  34. package/src/index.js +34 -22
  35. package/src/less/carousel.less +9 -0
  36. package/src/less/edit.less +26 -0
  37. package/src/less/menu.less +118 -4
  38. package/src/less/menu.variables +16 -0
  39. package/src/less/tabs.less +53 -23
  40. package/src/less/tabs.variables +3 -0
  41. package/src/widgets/TabsWidget.jsx +158 -68
@@ -1,14 +1,6 @@
1
1
  import { defineMessages } from 'react-intl';
2
2
 
3
3
  const messages = defineMessages({
4
- CarouselTabsBlock: {
5
- id: 'carousel-tabs-block',
6
- defaultMessage: 'Carousel block',
7
- },
8
- Default: {
9
- id: 'default',
10
- defaultMessage: 'Default',
11
- },
12
4
  Theme: {
13
5
  id: 'theme',
14
6
  defaultMessage: 'Theme',
@@ -25,27 +17,29 @@ const messages = defineMessages({
25
17
  id: 'grey',
26
18
  defaultMessage: 'Grey',
27
19
  },
20
+ Style: {
21
+ id: 'style',
22
+ defaultMessage: 'Style',
23
+ },
28
24
  });
29
25
 
30
- export default (config, props) => ({
31
- title: props.intl.formatMessage(messages.CarouselTabsBlock),
32
- fieldsets: [
33
- {
34
- id: 'default',
35
- title: props.intl.formatMessage(messages.Default),
36
- fields: ['theme'],
37
- },
38
- ],
39
- properties: {
26
+ export const carouselSchemaEnhancer = ({ schema, intl }) => {
27
+ schema.fieldsets.splice(1, 0, {
28
+ id: 'style',
29
+ title: intl.formatMessage(messages.Style),
30
+ fields: ['theme'],
31
+ });
32
+ schema.properties = {
33
+ ...schema.properties,
40
34
  theme: {
41
- title: props.intl.formatMessage(messages.Theme),
35
+ title: intl.formatMessage(messages.Theme),
42
36
  choices: [
43
- ['light', props.intl.formatMessage(messages.Light)],
44
- ['dark', props.intl.formatMessage(messages.Dark)],
45
- ['grey', props.intl.formatMessage(messages.Grey)],
37
+ ['light', intl.formatMessage(messages.Light)],
38
+ ['dark', intl.formatMessage(messages.Dark)],
39
+ ['grey', intl.formatMessage(messages.Grey)],
46
40
  ],
47
41
  default: 'light',
48
42
  },
49
- },
50
- required: [],
51
- });
43
+ };
44
+ return schema;
45
+ };
@@ -1,13 +1,14 @@
1
1
  import React from 'react';
2
+ import { useIntl } from 'react-intl';
2
3
  import { isEmpty } from 'lodash';
3
4
  import { v4 as uuid } from 'uuid';
4
5
  import cx from 'classnames';
5
6
  import { Menu, Tab, Input, Container } from 'semantic-ui-react';
6
- import config from '@plone/volto/registry';
7
7
  import { BlocksForm } from '@plone/volto/components';
8
8
  import { emptyBlocksForm } from '@plone/volto/helpers';
9
- import { TABS_BLOCK } from '@eeacms/volto-tabs-block/constants';
10
9
  import EditBlockWrapper from '@eeacms/volto-tabs-block/components/EditBlockWrapper';
10
+ import { defaultSchemaEnhancer } from '@eeacms/volto-tabs-block/components/templates/default/schema';
11
+ import { AssetTab } from '@eeacms/volto-tabs-block/components';
11
12
  import {
12
13
  SimpleMarkdown,
13
14
  getMenuPosition,
@@ -17,9 +18,11 @@ import '@eeacms/volto-tabs-block/less/menu.less';
17
18
 
18
19
  import noop from 'lodash/noop';
19
20
 
20
- const MenuItem = (props) => {
21
+ export const MenuItem = (props) => {
21
22
  const inputRef = React.useRef(null);
23
+ const intl = useIntl();
22
24
  const {
25
+ schema,
23
26
  activeTab = null,
24
27
  activeBlock = null,
25
28
  block = null,
@@ -28,20 +31,22 @@ const MenuItem = (props) => {
28
31
  selected = false,
29
32
  tabData = {},
30
33
  tabs = {},
31
- tabsData = {},
32
34
  tabsDescription,
33
- tabsList = [],
34
35
  tabsTitle,
35
- emptyTab = noop,
36
+ tabsData = {},
37
+ tabsList = [],
38
+ emptyTab = () => {},
36
39
  setActiveBlock = noop,
37
40
  setActiveTab = noop,
38
41
  setEditingTab = noop,
39
42
  onChangeBlock = noop,
40
43
  } = props;
41
44
  const { tab, index } = props;
42
- const title = tabs[tab].title;
43
45
  const tabIndex = index + 1;
44
46
  const defaultTitle = `Tab ${tabIndex}`;
47
+ const tabSettings = tabs[tab];
48
+ const { title, assetType } = tabSettings;
49
+ const tabTitle = title || defaultTitle;
45
50
 
46
51
  const addNewTab = () => {
47
52
  const tabId = uuid();
@@ -53,7 +58,10 @@ const MenuItem = (props) => {
53
58
  blocks: {
54
59
  ...tabsData.blocks,
55
60
  [tabId]: {
56
- ...emptyTab(),
61
+ ...emptyTab({
62
+ schema: schema.properties.data.schema,
63
+ intl,
64
+ }),
57
65
  },
58
66
  },
59
67
  blocks_layout: {
@@ -61,6 +69,7 @@ const MenuItem = (props) => {
61
69
  },
62
70
  },
63
71
  });
72
+ return tabId;
64
73
  };
65
74
 
66
75
  React.useEffect(() => {
@@ -81,6 +90,15 @@ const MenuItem = (props) => {
81
90
  <Menu.Item
82
91
  name={defaultTitle}
83
92
  active={tab === activeTab}
93
+ className="remove-margin"
94
+ tabIndex={0}
95
+ role={'tab'}
96
+ onKeyDown={(e) => {
97
+ if (e.target.tagName === 'A' && e.code === 'Space') {
98
+ e.preventDefault();
99
+ setActiveTab(tab);
100
+ }
101
+ }}
84
102
  onClick={() => {
85
103
  if (activeTab !== tab) {
86
104
  setActiveTab(tab);
@@ -120,21 +138,39 @@ const MenuItem = (props) => {
120
138
  />
121
139
  ) : (
122
140
  <>
123
- <span className={'menu-item-count'}>{tabIndex}</span>
124
- <p className={'menu-item-text'}>{title || defaultTitle}</p>
141
+ {assetType ? (
142
+ <AssetTab
143
+ props={tabSettings}
144
+ tabTitle={tabTitle}
145
+ tabIndex={tabIndex}
146
+ />
147
+ ) : (
148
+ <span>{tabTitle}</span>
149
+ )}
125
150
  </>
126
151
  )}
127
152
  </Menu.Item>
128
153
  {index === tabsList.length - 1 ? (
129
154
  <>
130
155
  <Menu.Item
156
+ tabIndex={0}
157
+ role="tab"
131
158
  name="addition"
159
+ onKeyDown={(e) => {
160
+ if (e.code === 'Space') {
161
+ e.preventDefault();
162
+ const newTab = addNewTab();
163
+ setActiveTab(newTab);
164
+ }
165
+ }}
132
166
  onClick={() => {
133
- addNewTab();
167
+ const newTab = addNewTab();
168
+ setActiveTab(newTab);
134
169
  setEditingTab(null);
135
170
  }}
171
+ className="remove-margin addition-button"
136
172
  >
137
- +
173
+ <p className="menu-item-text">+</p>
138
174
  </Menu.Item>
139
175
  </>
140
176
  ) : (
@@ -145,6 +181,7 @@ const MenuItem = (props) => {
145
181
  };
146
182
 
147
183
  const Edit = (props) => {
184
+ const intl = useIntl();
148
185
  const {
149
186
  activeBlock = null,
150
187
  activeTab = null,
@@ -159,36 +196,32 @@ const Edit = (props) => {
159
196
  tabs = {},
160
197
  tabsData = {},
161
198
  tabsList = [],
162
- emptyTab = noop,
199
+ emptyTab = () => {},
163
200
  onChangeBlock = noop,
164
201
  onChangeTabData = noop,
165
202
  onSelectBlock = noop,
166
203
  setEditingTab = noop,
204
+ schema,
167
205
  } = props;
168
206
  const menuPosition = getMenuPosition(data);
169
- const isContainer = data.align === 'full';
170
- const tabsTitle = data.title;
171
- const tabsDescription = data.description;
172
-
173
- const schema = React.useMemo(
174
- () =>
175
- config.blocks.blocksConfig[TABS_BLOCK].templates?.['default']?.schema(
176
- config,
177
- props,
178
- ) || {},
179
- [props],
180
- );
181
-
182
- const getDataValue = React.useCallback(
183
- (key) => {
184
- return (
185
- (schema.properties[key]?.value || data[key]) ??
186
- schema.properties[key]?.defaultValue
187
- );
188
- },
189
- [schema, data],
190
- );
191
-
207
+ const {
208
+ title,
209
+ description,
210
+ align,
211
+ menuBorderless,
212
+ menuColor,
213
+ menuCompact,
214
+ menuFluid,
215
+ menuInverted,
216
+ menuPointing,
217
+ menuSecondary,
218
+ menuSize,
219
+ menuStackable,
220
+ menuTabular,
221
+ menuText,
222
+ menuAlign,
223
+ } = data;
224
+ const isContainer = align === 'full';
192
225
  const panes = tabsList.map((tab, index) => {
193
226
  return {
194
227
  id: tab,
@@ -200,8 +233,9 @@ const Edit = (props) => {
200
233
  index={index}
201
234
  setEditingTab={setEditingTab}
202
235
  tab={tab}
203
- tabsTitle={tabsTitle}
204
- tabsDescription={tabsDescription}
236
+ tabsTitle={title}
237
+ tabsDescription={description}
238
+ schema={schema}
205
239
  />
206
240
  ),
207
241
  render: () => {
@@ -232,7 +266,10 @@ const Edit = (props) => {
232
266
  [activeTab]: {
233
267
  ...(newFormData.blocks_layout.items.length > 0
234
268
  ? newFormData
235
- : emptyTab()),
269
+ : emptyTab({
270
+ schema: schema.properties.data.schema,
271
+ intl,
272
+ })),
236
273
  },
237
274
  },
238
275
  },
@@ -275,21 +312,22 @@ const Edit = (props) => {
275
312
  className="default tabs"
276
313
  menu={{
277
314
  attached: menuPosition.attached,
278
- borderless: getDataValue('menuBorderless'),
279
- color: getDataValue('menuColor'),
280
- compact: getDataValue('menuCompact'),
281
- fluid: getDataValue('menuFluid'),
282
- inverted: getDataValue('menuInverted'),
283
- pointing: getDataValue('menuPointing'),
284
- secondary: getDataValue('menuSecondary'),
285
- size: getDataValue('menuSize'),
286
- stackable: getDataValue('menuStackable'),
287
- tabular: getDataValue('menuTabular'),
288
- text: getDataValue('menuText'),
315
+ borderless: menuBorderless,
316
+ color: menuColor,
317
+ compact: menuCompact,
318
+ fluid: menuFluid,
319
+ inverted: menuInverted,
320
+ pointing: menuPointing,
321
+ secondary: menuSecondary,
322
+ size: menuSize,
323
+ stackable: menuStackable,
324
+ tabular: menuTabular,
325
+ text: menuText,
289
326
  vertical: menuPosition.vertical,
290
327
  className: cx(
291
328
  'tabs-secondary-variant',
292
329
  data.menuAlign,
330
+ menuAlign,
293
331
  menuPosition.direction === 'left' ? 'border-right' : '',
294
332
  menuPosition.direction === 'right' ? 'border-left' : '',
295
333
  menuPosition.direction === 'top' ? 'border-bottom' : '',
@@ -305,4 +343,6 @@ const Edit = (props) => {
305
343
  );
306
344
  };
307
345
 
346
+ Edit.schemaEnhancer = defaultSchemaEnhancer;
347
+
308
348
  export default Edit;
@@ -2,20 +2,77 @@ import React, { useState, useEffect } from 'react';
2
2
  import { compose } from 'redux';
3
3
  import { withRouter } from 'react-router';
4
4
  import cx from 'classnames';
5
- import { Menu, Tab, Container } from 'semantic-ui-react';
6
- import config from '@plone/volto/registry';
5
+ import { Menu, Tab, Container, Icon, Image } from 'semantic-ui-react';
7
6
  import { RenderBlocks } from '@plone/volto/components';
8
- import { TABS_BLOCK } from '@eeacms/volto-tabs-block/constants';
9
7
  import { withScrollToTarget } from '@eeacms/volto-tabs-block/hocs';
10
8
  import {
11
9
  SimpleMarkdown,
12
10
  getMenuPosition,
13
11
  } from '@eeacms/volto-tabs-block/utils';
14
12
 
13
+ import { isInternalURL, flattenToAppURL } from '@plone/volto/helpers';
14
+
15
15
  import '@eeacms/volto-tabs-block/less/menu.less';
16
16
 
17
17
  import noop from 'lodash/noop';
18
18
 
19
+ export const AssetTab = ({ props, tabIndex, tabTitle }) => {
20
+ const {
21
+ icon,
22
+ image,
23
+ assetType,
24
+ assetPosition,
25
+ iconSize,
26
+ imageSize,
27
+ hideTitle,
28
+ } = props;
29
+ const imageObject = image?.[0];
30
+ return (
31
+ <div
32
+ className={cx('asset-position', {
33
+ 'asset-top': assetPosition === 'top',
34
+ 'asset-left': assetPosition === 'left',
35
+ 'asset-right': assetPosition === 'right',
36
+ })}
37
+ >
38
+ {assetType === 'icon' && icon && (
39
+ <Icon
40
+ className={cx('tab-icon', icon, iconSize, 'aligned')}
41
+ {...{
42
+ ...(hideTitle && {
43
+ role: 'img',
44
+ 'aria-hidden': 'false',
45
+ 'aria-label': tabTitle,
46
+ }),
47
+ }}
48
+ />
49
+ )}
50
+
51
+ {assetType === 'image' && imageObject && (
52
+ <Image
53
+ src={
54
+ isInternalURL(imageObject['@id'])
55
+ ? `${flattenToAppURL(imageObject['@id'])}/${
56
+ imageObject?.image_scales?.image?.[0].scales?.[imageSize]
57
+ ?.download || imageObject?.image_scales?.image?.[0].download
58
+ }`
59
+ : imageObject['@id']
60
+ }
61
+ className={cx('ui', imageSize, 'aligned')}
62
+ alt={hideTitle ? tabTitle : ''}
63
+ />
64
+ )}
65
+
66
+ {!hideTitle && (
67
+ <div>
68
+ <span className="menu-item-count">{tabIndex}</span>
69
+ <p className="menu-item-text">{tabTitle}</p>
70
+ </div>
71
+ )}
72
+ </div>
73
+ );
74
+ };
75
+
19
76
  const MenuItem = (props) => {
20
77
  const {
21
78
  activeTab = null,
@@ -27,10 +84,12 @@ const MenuItem = (props) => {
27
84
  } = props;
28
85
 
29
86
  const { tab, index } = props;
30
- const title = tabs[tab].title;
31
87
  const tabIndex = index + 1;
32
88
  const [tabChanged, setTabChanged] = useState(false);
33
89
  const defaultTitle = `Tab ${tabIndex}`;
90
+ const tabSettings = tabs[tab];
91
+ const { title, assetType } = tabSettings;
92
+ const tabTitle = title || defaultTitle;
34
93
 
35
94
  useEffect(() => {
36
95
  if (
@@ -56,7 +115,9 @@ const MenuItem = (props) => {
56
115
  <Menu.Item
57
116
  name={defaultTitle}
58
117
  active={tab === activeTab}
118
+ aria-selected={tab === activeTab}
59
119
  tabIndex={0}
120
+ role={'tab'}
60
121
  onClick={() => {
61
122
  if (activeTab !== tab) {
62
123
  setActiveTab(tab);
@@ -72,8 +133,20 @@ const MenuItem = (props) => {
72
133
  }
73
134
  }}
74
135
  >
75
- <span className={'menu-item-count'}>{tabIndex}</span>
76
- <p className={'menu-item-text'}>{title || defaultTitle}</p>
136
+ <>
137
+ {assetType ? (
138
+ <AssetTab
139
+ props={tabSettings}
140
+ tabTitle={tabTitle}
141
+ tabIndex={tabIndex}
142
+ />
143
+ ) : (
144
+ <>
145
+ <span className="menu-item-count">{tabIndex}</span>
146
+ <p className="menu-item-text">{tabTitle}</p>
147
+ </>
148
+ )}
149
+ </>
77
150
  </Menu.Item>
78
151
  </React.Fragment>
79
152
  );
@@ -94,28 +167,24 @@ const View = (props) => {
94
167
  }
95
168
  }, [data, menuPosition]);
96
169
 
97
- const isContainer = data.align === 'full';
98
- const tabsTitle = data.title;
99
- const tabsDescription = data.description;
100
-
101
- const schema = React.useMemo(
102
- () =>
103
- config.blocks.blocksConfig[TABS_BLOCK].templates?.['default']?.schema(
104
- config,
105
- props,
106
- ) || {},
107
- [props],
108
- );
109
-
110
- const getDataValue = React.useCallback(
111
- (key) => {
112
- return (
113
- (schema.properties[key]?.value || data[key]) ??
114
- schema.properties[key]?.defaultValue
115
- );
116
- },
117
- [schema, data],
118
- );
170
+ const {
171
+ title,
172
+ description,
173
+ align,
174
+ menuBorderless,
175
+ menuColor,
176
+ menuCompact,
177
+ menuFluid,
178
+ menuInverted,
179
+ menuPointing,
180
+ menuSecondary,
181
+ menuSize,
182
+ menuStackable,
183
+ menuTabular,
184
+ menuText,
185
+ menuAlign,
186
+ } = data;
187
+ const isContainer = align === 'full';
119
188
 
120
189
  const panes = tabsList.map((tab, index) => {
121
190
  return {
@@ -126,8 +195,8 @@ const View = (props) => {
126
195
  key={tab}
127
196
  tab={tab}
128
197
  index={index}
129
- tabsTitle={tabsTitle}
130
- tabsDescription={tabsDescription}
198
+ tabsTitle={title}
199
+ tabsDescription={description}
131
200
  blockId={props?.id || ''}
132
201
  />
133
202
  ),
@@ -157,22 +226,22 @@ const View = (props) => {
157
226
  className="default tabs tabs-accessibility"
158
227
  renderActiveOnly={false}
159
228
  menu={{
229
+ role: 'tablist',
160
230
  attached: menuPosition.attached,
161
- borderless: getDataValue('menuBorderless'),
162
- color: getDataValue('menuColor'),
163
- compact: getDataValue('menuCompact'),
164
- fluid: getDataValue('menuFluid'),
165
- inverted: getDataValue('menuInverted'),
166
- pointing: getDataValue('menuPointing'),
167
- secondary: getDataValue('menuSecondary'),
168
- size: getDataValue('menuSize'),
169
- stackable: getDataValue('menuStackable'),
170
- tabular: getDataValue('menuTabular'),
171
- text: getDataValue('menuText'),
231
+ borderless: menuBorderless,
232
+ color: menuColor,
233
+ compact: menuCompact,
234
+ fluid: menuFluid,
235
+ inverted: menuInverted,
236
+ pointing: menuPointing,
237
+ secondary: menuSecondary,
238
+ size: menuSize,
239
+ stackable: menuStackable,
240
+ tabular: menuTabular,
241
+ text: menuText,
172
242
  vertical: menuPosition.vertical,
173
243
  className: cx(
174
- 'tabs-secondary-variant',
175
- data.menuAlign,
244
+ menuAlign,
176
245
  menuPosition.direction === 'left' ? 'border-right' : '',
177
246
  menuPosition.direction === 'right' ? 'border-left' : '',
178
247
  menuPosition.direction === 'top' ? 'border-bottom' : '',
@@ -1,5 +1,5 @@
1
1
  import DefaultEdit from './Edit';
2
2
  import DefaultView from './View';
3
- import defaultSchema from './schema';
3
+ import { AssetTab } from './View';
4
4
 
5
- export { DefaultEdit, DefaultView, defaultSchema };
5
+ export { DefaultEdit, DefaultView, AssetTab };