@eeacms/volto-tabs-block 1.2.10 → 1.3.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.
@@ -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,274 @@
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
+
65
+ React.useEffect(() => {
66
+ if (!true || !node?.current) return;
67
+ const items = node.current.querySelectorAll(
68
+ '.ui.menu > .menu-wrapper > .item:not(.menu-title)',
69
+ );
70
+ const underlineMenu = node.current.querySelector('.ui.dropdown');
71
+ if (!underlineMenu) return;
72
+ const overflowOffset = positionedOffset(underlineMenu, node.current);
73
+ if (!overflowOffset) {
74
+ return;
75
+ }
76
+ let anyHidden = false;
77
+ for (const item of items) {
78
+ const itemOffset = positionedOffset(item, node.current);
79
+ if (itemOffset) {
80
+ const hidden =
81
+ itemOffset.left + item.offsetWidth >= overflowOffset.left;
82
+ toggleItem(node.current, item, hidden);
83
+ anyHidden = anyHidden || hidden;
84
+ }
85
+ }
86
+ underlineMenu.style.visibility = anyHidden ? '' : 'hidden';
87
+ }, [screen, node, data.isResponsive]);
88
+
89
+ return (
90
+ <React.Fragment>
91
+ <div className="menu-wrapper">
92
+ {panes.map((pane, index) => (
93
+ <React.Fragment key={`menu-item-${index}-${pane.id}`}>
94
+ {pane.menuItem}
95
+ </React.Fragment>
96
+ ))}
97
+ </div>
98
+ {true && (
99
+ <Dropdown
100
+ icon="ellipsis horizontal"
101
+ className="item"
102
+ pointing="top right"
103
+ >
104
+ <Dropdown.Menu>
105
+ {tabsList.map((underlineTab, underlineIndex) => {
106
+ const title = tabs[underlineTab].title;
107
+ const defaultTitle = `Tab ${underlineIndex + 1}`;
108
+
109
+ return (
110
+ <Dropdown.Item
111
+ hidden
112
+ key={`underline-tab-${underlineIndex}-${underlineTab}`}
113
+ underline-item-data={underlineTab}
114
+ active={underlineTab === activeTab}
115
+ onClick={() => {
116
+ if (activeTab !== underlineTab) {
117
+ setActiveTab(underlineTab);
118
+ }
119
+ }}
120
+ >
121
+ <span className={'menu-item-count'}>
122
+ {underlineIndex + 1}
123
+ </span>
124
+ <p className={'menu-item-text'}>{title || defaultTitle}</p>
125
+ </Dropdown.Item>
126
+ );
127
+ })}
128
+ </Dropdown.Menu>
129
+ </Dropdown>
130
+ )}
131
+ </React.Fragment>
132
+ );
133
+ };
134
+
135
+ const View = (props) => {
136
+ const [hashlinkOnMount, setHashlinkOnMount] = React.useState(false);
137
+ const {
138
+ metadata = {},
139
+ data = {},
140
+ tabsList = [],
141
+ tabs = {},
142
+ activeTabIndex = 0,
143
+ hashlink = {},
144
+ screen,
145
+ setActiveTab = () => {},
146
+ } = props;
147
+ const menuPosition = getMenuPosition(data);
148
+ const isContainer = data.align === 'full';
149
+ const tabsTitle = data.title;
150
+ const tabsDescription = data.description;
151
+
152
+ const schema = React.useMemo(
153
+ () =>
154
+ config.blocks.blocksConfig[TABS_BLOCK].templates?.['default']?.schema(
155
+ config,
156
+ props,
157
+ ) || {},
158
+ [props],
159
+ );
160
+
161
+ const getDataValue = React.useCallback(
162
+ (key) => {
163
+ return (
164
+ (schema.properties[key]?.value || data[key]) ??
165
+ schema.properties[key]?.defaultValue
166
+ );
167
+ },
168
+ [schema, data],
169
+ );
170
+
171
+ React.useEffect(() => {
172
+ const urlHash = props.location.hash.substring(1) || '';
173
+ if (
174
+ hashlink.counter > 0 ||
175
+ (hashlink.counter === 0 && urlHash && !hashlinkOnMount)
176
+ ) {
177
+ const id = hashlink.hash || urlHash || '';
178
+ const index = tabsList.indexOf(id);
179
+ const parentId = data.id || props.id;
180
+ const parent = document.getElementById(parentId);
181
+ const headerWrapper = document.querySelector('.header-wrapper');
182
+ const offsetHeight = headerWrapper?.offsetHeight || 0;
183
+ if (id !== parentId && index > -1 && parent) {
184
+ if (activeTabIndex !== index) {
185
+ setActiveTab(id);
186
+ }
187
+ props.scrollToTarget(parent, offsetHeight);
188
+ } else if (id === parentId && parent) {
189
+ props.scrollToTarget(parent, offsetHeight);
190
+ }
191
+ }
192
+ if (!hashlinkOnMount) {
193
+ setHashlinkOnMount(true);
194
+ }
195
+ /* eslint-disable-next-line */
196
+ }, [hashlink.counter]);
197
+
198
+ const panes = tabsList.map((tab, index) => {
199
+ return {
200
+ id: tab,
201
+ menuItem: (
202
+ <MenuItem
203
+ {...props}
204
+ tab={tab}
205
+ tabsList={tabsList}
206
+ index={index}
207
+ lastIndex={tabsList.length - 1}
208
+ tabsTitle={tabsTitle}
209
+ tabsDescription={tabsDescription}
210
+ />
211
+ ),
212
+ render: () => {
213
+ return (
214
+ <>
215
+ <Tab.Pane as={isContainer ? Container : undefined}>
216
+ <RenderBlocks
217
+ {...props}
218
+ metadata={metadata}
219
+ content={tabs[tab]}
220
+ />
221
+ </Tab.Pane>
222
+ </>
223
+ );
224
+ },
225
+ };
226
+ });
227
+
228
+ return (
229
+ <>
230
+ <Tab
231
+ activeIndex={activeTabIndex}
232
+ className="horizontal-responsive tabs"
233
+ menu={{
234
+ attached: menuPosition.attached,
235
+ borderless: getDataValue('menuBorderless'),
236
+ color: getDataValue('menuColor'),
237
+ compact: getDataValue('menuCompact'),
238
+ fluid: getDataValue('menuFluid'),
239
+ inverted: getDataValue('menuInverted'),
240
+ pointing: getDataValue('menuPointing'),
241
+ secondary: getDataValue('menuSecondary'),
242
+ size: getDataValue('menuSize'),
243
+ stackable: getDataValue('menuStackable'),
244
+ tabular: getDataValue('menuTabular'),
245
+ text: getDataValue('menuText'),
246
+ vertical: menuPosition.vertical,
247
+ className: cx(data.menuAlign, { container: isContainer }),
248
+ items: undefined,
249
+ children: (
250
+ <MenuWrapper
251
+ {...props}
252
+ panes={panes}
253
+ menuPosition={menuPosition}
254
+ screen={screen}
255
+ />
256
+ ),
257
+ }}
258
+ menuPosition={menuPosition.direction}
259
+ panes={panes}
260
+ grid={{ paneWidth: 9, tabWidth: 3 }}
261
+ />
262
+ </>
263
+ );
264
+ };
265
+
266
+ export default compose(
267
+ connect((state) => {
268
+ return {
269
+ hashlink: state.hashlink,
270
+ screen: state.screen,
271
+ };
272
+ }),
273
+ withScrollToTarget,
274
+ )(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,145 @@
1
+ .inner.tabs_block {
2
+ margin-bottom: 0;
3
+
4
+ .tabs_block {
5
+ padding-right: 0.5rem;
6
+ padding-left: 0.5rem;
7
+ margin-bottom: 0;
8
+ }
9
+ }
10
+
11
+ .styled.screen-height {
12
+ .tabs-block {
13
+ display: flex;
14
+ flex: 1;
15
+ flex-direction: column;
16
+
17
+ > .default.tabs {
18
+ flex: 1;
19
+ }
20
+
21
+ > .styled {
22
+ > .default.tabs {
23
+ flex: 1;
24
+ }
25
+ }
26
+ }
27
+ }
28
+
29
+ /**
30
+ ** Edit form style
31
+ **/
32
+ .tabs-block {
33
+ [data-rbd-draggable-context-id] {
34
+ margin-bottom: 0;
35
+ }
36
+
37
+ .blocks-form {
38
+ width: 100%;
39
+ // padding-bottom: 0.1rem;
40
+ }
41
+
42
+ .block-wrapper {
43
+ &:not(:last-child) {
44
+ margin-bottom: 2rem;
45
+ }
46
+ }
47
+
48
+ .block {
49
+ .block-add-button,
50
+ .delete-button {
51
+ display: none;
52
+ }
53
+
54
+ .block.selected::before {
55
+ border-top-left-radius: 0;
56
+ }
57
+ }
58
+
59
+ .block .block:not(.inner)::before {
60
+ z-index: 0;
61
+ pointer-events: none;
62
+ }
63
+
64
+ .block.wrapper.multiSelected {
65
+ .block .block::before {
66
+ z-index: 1;
67
+ background-color: rgba(120, 192, 215, 0.375);
68
+ }
69
+ }
70
+
71
+ .block-toolbar {
72
+ position: absolute;
73
+ z-index: 3;
74
+ right: unset;
75
+ left: -9px;
76
+ display: flex;
77
+ padding: 5px;
78
+ border: 1px solid #78c0d7bf;
79
+ border-bottom: 1px solid #fff;
80
+ margin-top: -45px;
81
+ background-color: #fff;
82
+ border-top-left-radius: 1rem;
83
+ border-top-right-radius: 1rem;
84
+
85
+ .ui.basic.button {
86
+ padding: 3px;
87
+ padding-right: 0;
88
+ box-shadow: none;
89
+ }
90
+
91
+ .wrapper-column-block {
92
+ > .ui.basic.button {
93
+ &:hover {
94
+ background-color: transparent !important;
95
+ }
96
+ }
97
+ }
98
+
99
+ > .ui.basic.button {
100
+ &:hover {
101
+ background-color: transparent !important;
102
+ }
103
+
104
+ svg {
105
+ color: #134448;
106
+ }
107
+ }
108
+ }
109
+
110
+ .blocks-chooser {
111
+ left: 0;
112
+ margin-top: 50px;
113
+ }
114
+ }
115
+
116
+ .drag-drop-list-widget {
117
+ .tabs-area {
118
+ padding: 1em 0;
119
+
120
+ [data-rbd-draggable-context-id] {
121
+ margin-bottom: 0.3em;
122
+ }
123
+
124
+ .drag.handle.wrapper {
125
+ min-height: auto;
126
+ }
127
+
128
+ .tab-area {
129
+ display: flex;
130
+
131
+ .label {
132
+ flex-grow: 2;
133
+ padding-left: 1em;
134
+ }
135
+
136
+ button {
137
+ flex-grow: 0;
138
+ }
139
+ }
140
+
141
+ button {
142
+ cursor: pointer;
143
+ }
144
+ }
145
+ }