@eeacms/volto-tabs-block 1.2.11 → 1.3.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.
@@ -9,10 +9,36 @@ export default () => ({
9
9
  {
10
10
  id: 'menu',
11
11
  title: 'Menu',
12
- fields: ['menuAlign', 'menuPosition'],
12
+ fields: [
13
+ 'menuAlign',
14
+ 'menuPosition',
15
+ 'menuSize',
16
+ 'menuColor',
17
+ 'menuBorderless',
18
+ 'menuCompact',
19
+ 'menuFluid',
20
+ 'menuInverted',
21
+ 'menuPointing',
22
+ 'menuSecondary',
23
+ 'menuStackable',
24
+ 'menuTabular',
25
+ 'menuText',
26
+ ],
13
27
  },
14
28
  ],
15
29
  properties: {
30
+ description: {
31
+ title: 'Description',
32
+ },
33
+ menuPosition: {
34
+ title: 'Position',
35
+ choices: [
36
+ ['top', 'Top'],
37
+ ['bottom', 'Bottom'],
38
+ ['left side', 'Left side'],
39
+ ['right side', 'Right side'],
40
+ ],
41
+ },
16
42
  menuAlign: {
17
43
  title: 'Alignment',
18
44
  type: 'array',
@@ -22,20 +48,75 @@ export default () => ({
22
48
  ['right', 'Right'],
23
49
  ['space-between', 'Space between'],
24
50
  ],
25
- default: 'left',
26
51
  },
27
- menuPosition: {
28
- title: 'Position',
29
- type: 'array',
52
+ menuSize: {
53
+ title: 'Size',
30
54
  choices: [
31
- ['inline', 'Inline'],
32
- ['left side', 'Left side'],
33
- ['right side', 'Right side'],
55
+ ['mini', 'Mini'],
56
+ ['tiny', 'Tiny'],
57
+ ['small', 'Small'],
58
+ ['large', 'Large'],
59
+ ['huge', 'Huge'],
60
+ ['massive', 'Masive'],
34
61
  ],
35
- default: 'inline',
36
62
  },
37
- description: {
38
- title: 'Description',
63
+ menuColor: {
64
+ title: 'Color',
65
+ defaultValue: 'green',
66
+ choices: [
67
+ ['red', 'Red'],
68
+ ['orange', 'Orange'],
69
+ ['yellow', 'Yellow'],
70
+ ['olive', 'Olive'],
71
+ ['green', 'Green'],
72
+ ['teal', 'Teal'],
73
+ ['blue', 'Blue'],
74
+ ['violet', 'Violet'],
75
+ ['purple', 'Purple'],
76
+ ['pink', 'Pink'],
77
+ ['brown', 'Brown'],
78
+ ['grey', 'Grey'],
79
+ ['black', 'Black'],
80
+ ],
81
+ },
82
+ menuBorderless: {
83
+ title: 'Borderless',
84
+ type: 'boolean',
85
+ },
86
+ menuCompact: {
87
+ title: 'Compact',
88
+ type: 'boolean',
89
+ defaultValue: true,
90
+ },
91
+ menuFluid: {
92
+ title: 'Fluid',
93
+ type: 'boolean',
94
+ defaultValue: true,
95
+ },
96
+ menuInverted: {
97
+ title: 'Inverted',
98
+ type: 'boolean',
99
+ },
100
+ menuPointing: {
101
+ title: 'Pointing',
102
+ type: 'boolean',
103
+ },
104
+ menuSecondary: {
105
+ title: 'Secondary',
106
+ type: 'boolean',
107
+ },
108
+ menuStackable: {
109
+ title: 'Stackable',
110
+ type: 'boolean',
111
+ },
112
+ menuTabular: {
113
+ title: 'Tabular',
114
+ type: 'boolean',
115
+ },
116
+ menuText: {
117
+ title: 'Text',
118
+ type: 'boolean',
119
+ defaultValue: true,
39
120
  },
40
121
  },
41
122
  required: [],
@@ -0,0 +1,277 @@
1
+ import React from 'react';
2
+ import { connect } from 'react-redux';
3
+ import { compose } from 'redux';
4
+ import { withRouter } from 'react-router';
5
+ import cx from 'classnames';
6
+ import { Menu, Tab, Container, Dropdown, Button } from 'semantic-ui-react';
7
+ import config from '@plone/volto/registry';
8
+ import { RenderBlocks } from '@plone/volto/components';
9
+ import { TABS_BLOCK } from '@eeacms/volto-tabs-block/constants';
10
+ import { withScrollToTarget } from '@eeacms/volto-tabs-block/hocs';
11
+ import {
12
+ SimpleMarkdown,
13
+ getMenuPosition,
14
+ positionedOffset,
15
+ toggleItem,
16
+ } from '@eeacms/volto-tabs-block/utils';
17
+
18
+ import '@eeacms/volto-tabs-block/less/menu.less';
19
+
20
+ const MenuItem = (props) => {
21
+ const { activeTab = null, tabs = {}, setActiveTab = () => {} } = props;
22
+ const { tabsTitle, tabsDescription, tab, index } = props;
23
+ const title = tabs[tab].title;
24
+ const tabIndex = index + 1;
25
+
26
+ const defaultTitle = `Tab ${tabIndex}`;
27
+
28
+ return (
29
+ <React.Fragment>
30
+ {index === 0 && (tabsTitle || tabsDescription) && (
31
+ <Menu.Item className="menu-title">
32
+ <SimpleMarkdown md={tabsTitle} defaultTag="##" className="title" />
33
+ <SimpleMarkdown md={tabsDescription} className="description" />
34
+ </Menu.Item>
35
+ )}
36
+ <Button
37
+ as="a"
38
+ className={cx('item', { active: tab === activeTab })}
39
+ item-data={tab}
40
+ onClick={() => {
41
+ if (activeTab !== tab) {
42
+ setActiveTab(tab);
43
+ }
44
+ }}
45
+ >
46
+ <span className={'menu-item-count'}>{tabIndex}</span>
47
+ <p className={'menu-item-text'}>{title || defaultTitle}</p>
48
+ </Button>
49
+ </React.Fragment>
50
+ );
51
+ };
52
+
53
+ const MenuWrapper = (props) => {
54
+ const {
55
+ data = {},
56
+ panes = [],
57
+ activeTab = null,
58
+ node = null,
59
+ screen = {},
60
+ tabs = {},
61
+ tabsList = [],
62
+ setActiveTab = () => {},
63
+ } = props;
64
+ const [open, setOpen] = React.useState(false);
65
+
66
+ React.useEffect(() => {
67
+ if (!true || !node?.current) return;
68
+ const items = node.current.querySelectorAll(
69
+ '.ui.menu > .menu-wrapper > .item:not(.menu-title)',
70
+ );
71
+ const underlineDropdown = node.current.querySelector('.ui.dropdown');
72
+ if (!underlineDropdown) return;
73
+ const overflowOffset = positionedOffset(underlineDropdown, node.current);
74
+ if (!overflowOffset) {
75
+ return;
76
+ }
77
+ let anyHidden = false;
78
+ for (const item of items) {
79
+ const itemOffset = positionedOffset(item, node.current);
80
+ if (itemOffset) {
81
+ const hidden =
82
+ itemOffset.left + item.offsetWidth >= overflowOffset.left;
83
+ toggleItem(node.current, item, hidden);
84
+ anyHidden = anyHidden || hidden;
85
+ }
86
+ }
87
+ underlineDropdown.style.visibility = anyHidden ? '' : 'hidden';
88
+ if (!anyHidden && open) {
89
+ setOpen(false);
90
+ }
91
+ }, [screen, node, open, data.isResponsive]);
92
+
93
+ return (
94
+ <React.Fragment>
95
+ <div className="menu-wrapper">
96
+ {panes.map((pane, index) => (
97
+ <React.Fragment key={`menu-item-${index}-${pane.id}`}>
98
+ {pane.menuItem}
99
+ </React.Fragment>
100
+ ))}
101
+ </div>
102
+ <Dropdown
103
+ icon="ellipsis horizontal"
104
+ className="item"
105
+ pointing="top right"
106
+ open={open}
107
+ onOpen={() => setOpen(true)}
108
+ onClose={() => setOpen(false)}
109
+ >
110
+ <Dropdown.Menu>
111
+ {tabsList.map((underlineTab, underlineIndex) => {
112
+ const title = tabs[underlineTab].title;
113
+ const defaultTitle = `Tab ${underlineIndex + 1}`;
114
+
115
+ return (
116
+ <Dropdown.Item
117
+ hidden
118
+ key={`underline-tab-${underlineIndex}-${underlineTab}`}
119
+ underline-item-data={underlineTab}
120
+ active={underlineTab === activeTab}
121
+ onClick={() => {
122
+ if (activeTab !== underlineTab) {
123
+ setActiveTab(underlineTab);
124
+ }
125
+ }}
126
+ >
127
+ <span className={'menu-item-count'}>{underlineIndex + 1}</span>
128
+ <p className={'menu-item-text'}>{title || defaultTitle}</p>
129
+ </Dropdown.Item>
130
+ );
131
+ })}
132
+ </Dropdown.Menu>
133
+ </Dropdown>
134
+ </React.Fragment>
135
+ );
136
+ };
137
+
138
+ const View = (props) => {
139
+ const [hashlinkOnMount, setHashlinkOnMount] = React.useState(false);
140
+ const {
141
+ metadata = {},
142
+ data = {},
143
+ tabsList = [],
144
+ tabs = {},
145
+ activeTabIndex = 0,
146
+ hashlink = {},
147
+ screen,
148
+ setActiveTab = () => {},
149
+ } = props;
150
+ const menuPosition = getMenuPosition(data);
151
+ const isContainer = data.align === 'full';
152
+ const tabsTitle = data.title;
153
+ const tabsDescription = data.description;
154
+
155
+ const schema = React.useMemo(
156
+ () =>
157
+ config.blocks.blocksConfig[TABS_BLOCK].templates?.['default']?.schema(
158
+ config,
159
+ props,
160
+ ) || {},
161
+ [props],
162
+ );
163
+
164
+ const getDataValue = React.useCallback(
165
+ (key) => {
166
+ return (
167
+ (schema.properties[key]?.value || data[key]) ??
168
+ schema.properties[key]?.defaultValue
169
+ );
170
+ },
171
+ [schema, data],
172
+ );
173
+
174
+ React.useEffect(() => {
175
+ const urlHash = props.location.hash.substring(1) || '';
176
+ if (
177
+ hashlink.counter > 0 ||
178
+ (hashlink.counter === 0 && urlHash && !hashlinkOnMount)
179
+ ) {
180
+ const id = hashlink.hash || urlHash || '';
181
+ const index = tabsList.indexOf(id);
182
+ const parentId = data.id || props.id;
183
+ const parent = document.getElementById(parentId);
184
+ const headerWrapper = document.querySelector('.header-wrapper');
185
+ const offsetHeight = headerWrapper?.offsetHeight || 0;
186
+ if (id !== parentId && index > -1 && parent) {
187
+ if (activeTabIndex !== index) {
188
+ setActiveTab(id);
189
+ }
190
+ props.scrollToTarget(parent, offsetHeight);
191
+ } else if (id === parentId && parent) {
192
+ props.scrollToTarget(parent, offsetHeight);
193
+ }
194
+ }
195
+ if (!hashlinkOnMount) {
196
+ setHashlinkOnMount(true);
197
+ }
198
+ /* eslint-disable-next-line */
199
+ }, [hashlink.counter]);
200
+
201
+ const panes = tabsList.map((tab, index) => {
202
+ return {
203
+ id: tab,
204
+ menuItem: (
205
+ <MenuItem
206
+ {...props}
207
+ key={tab}
208
+ tab={tab}
209
+ tabsList={tabsList}
210
+ index={index}
211
+ lastIndex={tabsList.length - 1}
212
+ tabsTitle={tabsTitle}
213
+ tabsDescription={tabsDescription}
214
+ />
215
+ ),
216
+ render: () => {
217
+ return (
218
+ <>
219
+ <Tab.Pane as={isContainer ? Container : undefined}>
220
+ <RenderBlocks
221
+ {...props}
222
+ metadata={metadata}
223
+ content={tabs[tab]}
224
+ />
225
+ </Tab.Pane>
226
+ </>
227
+ );
228
+ },
229
+ };
230
+ });
231
+
232
+ return (
233
+ <>
234
+ <Tab
235
+ activeIndex={activeTabIndex}
236
+ className="horizontal-responsive tabs"
237
+ menu={{
238
+ attached: menuPosition.attached,
239
+ borderless: getDataValue('menuBorderless'),
240
+ color: getDataValue('menuColor'),
241
+ compact: getDataValue('menuCompact'),
242
+ fluid: getDataValue('menuFluid'),
243
+ inverted: getDataValue('menuInverted'),
244
+ pointing: getDataValue('menuPointing'),
245
+ secondary: getDataValue('menuSecondary'),
246
+ size: getDataValue('menuSize'),
247
+ stackable: getDataValue('menuStackable'),
248
+ tabular: getDataValue('menuTabular'),
249
+ text: getDataValue('menuText'),
250
+ vertical: menuPosition.vertical,
251
+ className: cx(data.menuAlign, { container: isContainer }),
252
+ children: (
253
+ <MenuWrapper
254
+ {...props}
255
+ panes={panes}
256
+ menuPosition={menuPosition}
257
+ screen={screen}
258
+ />
259
+ ),
260
+ }}
261
+ menuPosition={menuPosition.direction}
262
+ panes={panes}
263
+ grid={{ paneWidth: 9, tabWidth: 3 }}
264
+ />
265
+ </>
266
+ );
267
+ };
268
+
269
+ export default compose(
270
+ connect((state) => {
271
+ return {
272
+ hashlink: state.hashlink,
273
+ screen: state.screen,
274
+ };
275
+ }),
276
+ withScrollToTarget,
277
+ )(withRouter(View));
@@ -0,0 +1,4 @@
1
+ import HorizontalResponsiveView from './View';
2
+ import horizontalResponsiveSchema from './schema';
3
+
4
+ export { HorizontalResponsiveView, horizontalResponsiveSchema };
@@ -0,0 +1,123 @@
1
+ export default () => ({
2
+ title: 'Default tabs block',
3
+ fieldsets: [
4
+ {
5
+ id: 'default',
6
+ title: 'Default',
7
+ fields: ['description'],
8
+ },
9
+ {
10
+ id: 'menu',
11
+ title: 'Menu',
12
+ fields: [
13
+ 'menuAlign',
14
+ 'menuPosition',
15
+ 'menuSize',
16
+ 'menuColor',
17
+ 'menuBorderless',
18
+ 'menuCompact',
19
+ 'menuFluid',
20
+ 'menuInverted',
21
+ 'menuPointing',
22
+ 'menuSecondary',
23
+ 'menuStackable',
24
+ 'menuTabular',
25
+ 'menuText',
26
+ ],
27
+ },
28
+ ],
29
+ properties: {
30
+ description: {
31
+ title: 'Description',
32
+ },
33
+ menuPosition: {
34
+ title: 'Position',
35
+ choices: [
36
+ ['top', 'Top'],
37
+ ['bottom', 'Bottom'],
38
+ ['left side', 'Left side'],
39
+ ['right side', 'Right side'],
40
+ ],
41
+ },
42
+ menuAlign: {
43
+ title: 'Alignment',
44
+ type: 'array',
45
+ choices: [
46
+ ['left', 'Left'],
47
+ ['center', 'Center'],
48
+ ['right', 'Right'],
49
+ ['space-between', 'Space between'],
50
+ ],
51
+ },
52
+ menuSize: {
53
+ title: 'Size',
54
+ choices: [
55
+ ['mini', 'Mini'],
56
+ ['tiny', 'Tiny'],
57
+ ['small', 'Small'],
58
+ ['large', 'Large'],
59
+ ['huge', 'Huge'],
60
+ ['massive', 'Masive'],
61
+ ],
62
+ },
63
+ menuColor: {
64
+ title: 'Color',
65
+ defaultValue: 'green',
66
+ choices: [
67
+ ['red', 'Red'],
68
+ ['orange', 'Orange'],
69
+ ['yellow', 'Yellow'],
70
+ ['olive', 'Olive'],
71
+ ['green', 'Green'],
72
+ ['teal', 'Teal'],
73
+ ['blue', 'Blue'],
74
+ ['violet', 'Violet'],
75
+ ['purple', 'Purple'],
76
+ ['pink', 'Pink'],
77
+ ['brown', 'Brown'],
78
+ ['grey', 'Grey'],
79
+ ['black', 'Black'],
80
+ ],
81
+ },
82
+ menuBorderless: {
83
+ title: 'Borderless',
84
+ type: 'boolean',
85
+ },
86
+ menuCompact: {
87
+ title: 'Compact',
88
+ type: 'boolean',
89
+ defaultValue: true,
90
+ },
91
+ menuFluid: {
92
+ title: 'Fluid',
93
+ type: 'boolean',
94
+ defaultValue: true,
95
+ },
96
+ menuInverted: {
97
+ title: 'Inverted',
98
+ type: 'boolean',
99
+ },
100
+ menuPointing: {
101
+ title: 'Pointing',
102
+ type: 'boolean',
103
+ },
104
+ menuSecondary: {
105
+ title: 'Secondary',
106
+ type: 'boolean',
107
+ },
108
+ menuStackable: {
109
+ title: 'Stackable',
110
+ type: 'boolean',
111
+ },
112
+ menuTabular: {
113
+ title: 'Tabular',
114
+ type: 'boolean',
115
+ },
116
+ menuText: {
117
+ title: 'Text',
118
+ type: 'boolean',
119
+ defaultValue: true,
120
+ },
121
+ },
122
+ required: [],
123
+ });
package/src/index.js CHANGED
@@ -2,9 +2,11 @@ import { TabsEdit, TabsView } from '@eeacms/volto-tabs-block/components';
2
2
  import {
3
3
  DefaultEdit,
4
4
  DefaultView,
5
+ HorizontalResponsiveView,
5
6
  HorizontalCarouselView,
6
7
  VerticalCarouselView,
7
8
  defaultSchema,
9
+ horizontalResponsiveSchema,
8
10
  carouselSchema,
9
11
  } from '@eeacms/volto-tabs-block/components';
10
12
  import { layoutSchema } from '@eeacms/volto-tabs-block/components';
@@ -37,6 +39,12 @@ export default (config) => {
37
39
  view: DefaultView,
38
40
  schema: defaultSchema,
39
41
  },
42
+ 'horizontal-responsive': {
43
+ title: 'Horizontal responsive',
44
+ edit: DefaultEdit,
45
+ view: HorizontalResponsiveView,
46
+ schema: horizontalResponsiveSchema,
47
+ },
40
48
  carousel: {
41
49
  title: 'Carousel horizontal',
42
50
  edit: DefaultEdit,
@@ -7,12 +7,6 @@
7
7
  .loadAddonVariables();
8
8
 
9
9
  .tabs-block {
10
- > .styled {
11
- display: flex !important;
12
- flex: 1;
13
- flex-flow: column;
14
- }
15
-
16
10
  .slick-slider {
17
11
  z-index: 2;
18
12
  display: flex !important;
@@ -97,6 +91,7 @@
97
91
  }
98
92
 
99
93
  .slick-arrows {
94
+ z-index: 3;
100
95
  display: flex;
101
96
  flex-direction: row;
102
97
  justify-content: center;