@eeacms/volto-tabs-block 1.2.12 → 1.3.2

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;
@@ -0,0 +1,140 @@
1
+ @type: 'extra';
2
+ @element: 'custom';
3
+
4
+ @import (multiple, reference, optional) '../../theme.config';
5
+
6
+ @borderColor: rgba(120, 192, 215, 0.75);
7
+
8
+ .inner.tabs_block {
9
+ margin-bottom: 0;
10
+
11
+ .tabs_block {
12
+ padding-right: 0.5rem;
13
+ padding-left: 0.5rem;
14
+ margin-bottom: 0;
15
+ }
16
+ }
17
+
18
+ .styled.screen-height {
19
+ .tabs-block {
20
+ display: flex;
21
+ flex: 1;
22
+ flex-direction: column;
23
+
24
+ > .default.tabs {
25
+ flex: 1;
26
+ }
27
+
28
+ > .styled {
29
+ > .default.tabs {
30
+ flex: 1;
31
+ }
32
+ }
33
+ }
34
+ }
35
+
36
+ /**
37
+ ** Edit form style
38
+ **/
39
+ .tabs-block {
40
+ [data-rbd-draggable-context-id] {
41
+ margin-bottom: 0;
42
+ }
43
+
44
+ .blocks-form {
45
+ width: 100%;
46
+ // padding-bottom: 0.1rem;
47
+ }
48
+
49
+ .block-wrapper {
50
+ &:not(:last-child) {
51
+ margin-bottom: 2rem;
52
+ }
53
+ }
54
+
55
+ .block {
56
+ .block-add-button,
57
+ .delete-button {
58
+ display: none;
59
+ }
60
+
61
+ .block.selected::before {
62
+ border-top-left-radius: 0;
63
+ }
64
+ }
65
+
66
+ .block .block:not(.inner)::before {
67
+ z-index: 0;
68
+ pointer-events: none;
69
+ }
70
+
71
+ .block.wrapper.multiSelected {
72
+ .block .block::before {
73
+ z-index: 1;
74
+ background-color: rgba(120, 192, 215, 0.375);
75
+ }
76
+ }
77
+
78
+ .blocks-form {
79
+ margin-top: 0.5rem;
80
+ }
81
+
82
+ .blocks-chooser {
83
+ right: 0;
84
+ left: auto;
85
+ margin-top: 3rem;
86
+ }
87
+
88
+ .block-toolbar {
89
+ position: absolute;
90
+ z-index: 3;
91
+ right: -9px;
92
+ display: flex;
93
+ border: none;
94
+ border: 1px solid @borderColor;
95
+ border-bottom: 1px solid @pageBackground;
96
+ margin-top: -45px;
97
+ background-color: @pageBackground;
98
+ border-top-left-radius: 1rem;
99
+ border-top-right-radius: 1rem;
100
+
101
+ .ui.basic.button {
102
+ padding: 8px 5px;
103
+ }
104
+
105
+ .ui.basic.button:hover {
106
+ background: transparent !important;
107
+ }
108
+ }
109
+ }
110
+
111
+ .drag-drop-list-widget {
112
+ .tabs-area {
113
+ padding: 1em 0;
114
+
115
+ [data-rbd-draggable-context-id] {
116
+ margin-bottom: 0.3em;
117
+ }
118
+
119
+ .drag.handle.wrapper {
120
+ min-height: auto;
121
+ }
122
+
123
+ .tab-area {
124
+ display: flex;
125
+
126
+ .label {
127
+ flex-grow: 2;
128
+ padding-left: 1em;
129
+ }
130
+
131
+ button {
132
+ flex-grow: 0;
133
+ }
134
+ }
135
+
136
+ button {
137
+ cursor: pointer;
138
+ }
139
+ }
140
+ }