@eeacms/volto-cca-policy 0.1.44 → 0.1.45

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.
package/CHANGELOG.md CHANGED
@@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file. Dates are d
4
4
 
5
5
  Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
6
6
 
7
+ ### [0.1.45](https://github.com/eea/volto-cca-policy/compare/0.1.44...0.1.45) - 25 September 2023
8
+
9
+ #### :hammer_and_wrench: Others
10
+
11
+ - Refs #161483 - Fix name. [GhitaB - [`e614a27`](https://github.com/eea/volto-cca-policy/commit/e614a275681b42d23292f0eb8f371686fefa8d70)]
12
+ - Code cleanup [Tiberiu Ichim - [`c0521db`](https://github.com/eea/volto-cca-policy/commit/c0521dbd4642516c49ec1560d7cc648b2918fddc)]
13
+ - Refs #161483 - Fix jenkins and infinite loop render. [GhitaB - [`2080298`](https://github.com/eea/volto-cca-policy/commit/20802988bbf2bee0759f524c0907da8dd6769284)]
14
+ - Refs #161483 - Add ECDE C3S indicators glossary block. [GhitaB - [`806a768`](https://github.com/eea/volto-cca-policy/commit/806a76888290cb52616dfd6a937a8e49fc6a9ed3)]
15
+ - Refs #161483 - Fix error in documents list when no files. (in add/edit/view publication report form) [GhitaB - [`954f19d`](https://github.com/eea/volto-cca-policy/commit/954f19dd2f09fd2a884c34c95a7c818304b231ae)]
7
16
  ### [0.1.44](https://github.com/eea/volto-cca-policy/compare/0.1.43...0.1.44) - 22 September 2023
8
17
 
9
18
  #### :hammer_and_wrench: Others
@@ -173,7 +182,6 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
173
182
 
174
183
  #### :house: Internal changes
175
184
 
176
- - chore: [JENKINS] Remove alpha testing version [valentinab25 - [`ad1ced0`](https://github.com/eea/volto-cca-policy/commit/ad1ced0971ba116c13a3b5fcc039172cc915c919)]
177
185
 
178
186
  #### :hammer_and_wrench: Others
179
187
 
@@ -654,7 +662,6 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
654
662
  #### :hammer_and_wrench: Others
655
663
 
656
664
  - Refs #158294 - Update supported languages list. [GhitaB - [`0a4f91f`](https://github.com/eea/volto-cca-policy/commit/0a4f91f39b7edc367bd4c127d6a8f273c7788361)]
657
- - Add Sonarqube tag using cca-frontend addons list [EEA Jenkins - [`8f1f9ce`](https://github.com/eea/volto-cca-policy/commit/8f1f9ce6c22805670cc0800d3c779b6d619d0f31)]
658
665
  ### [0.1.1](https://github.com/eea/volto-cca-policy/compare/0.1.0...0.1.1) - 13 December 2022
659
666
 
660
667
  #### :hammer_and_wrench: Others
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eeacms/volto-cca-policy",
3
- "version": "0.1.44",
3
+ "version": "0.1.45",
4
4
  "description": "@eeacms/volto-cca-policy: Volto add-on",
5
5
  "main": "src/index.js",
6
6
  "author": "European Environment Agency: IDM2 A-Team",
@@ -0,0 +1,32 @@
1
+ import React from 'react';
2
+
3
+ import { SidebarPortal } from '@plone/volto/components';
4
+ import BlockDataForm from '@plone/volto/components/manage/Form/BlockDataForm';
5
+
6
+ import C3SIndicatorsGlossaryBlockView from './C3SIndicatorsGlossaryBlockView';
7
+ import schema from './schema';
8
+
9
+ export default function C3SIndicatorsGlossaryBlockEdit(props) {
10
+ const { block, data, onChangeBlock, selected, id } = props;
11
+
12
+ return (
13
+ <div>
14
+ <C3SIndicatorsGlossaryBlockView data={data} id={id} mode="edit" />
15
+ <SidebarPortal selected={selected}>
16
+ <BlockDataForm
17
+ block={block}
18
+ title={schema.title}
19
+ schema={schema}
20
+ onChangeField={(id, value) => {
21
+ onChangeBlock(block, {
22
+ ...data,
23
+ [id]: value,
24
+ });
25
+ }}
26
+ onChangeBlock={onChangeBlock}
27
+ formData={data}
28
+ />
29
+ </SidebarPortal>
30
+ </div>
31
+ );
32
+ }
@@ -0,0 +1,35 @@
1
+ import React from 'react';
2
+ import axios from 'axios';
3
+
4
+ export default function C3SIndicatorsGlossaryBlockView(props) {
5
+ const [tableHTML, setTableHTML] = React.useState('');
6
+
7
+ const getIndicatorsData = () => {
8
+ const url =
9
+ '/++api++/en/knowledge/european-climate-data-explorer/@c3s_indicators_glossary_table';
10
+
11
+ axios
12
+ .get(url)
13
+ .then((response) => {
14
+ setTableHTML(response.data.c3s_indicators_glossary_table);
15
+ })
16
+ .catch((error) => {
17
+ // console.error(error);
18
+ });
19
+ };
20
+
21
+ React.useEffect(() => {
22
+ getIndicatorsData();
23
+ });
24
+
25
+ return (
26
+ <div className="block c3sindicators-glossary-block">
27
+ <div
28
+ className="glossary-table"
29
+ dangerouslySetInnerHTML={{
30
+ __html: tableHTML,
31
+ }}
32
+ />
33
+ </div>
34
+ );
35
+ }
@@ -0,0 +1,28 @@
1
+ import zoomSVG from '@plone/volto/icons/zoom.svg';
2
+ import C3SIndicatorsGlossaryBlockEdit from './C3SIndicatorsGlossaryBlockEdit';
3
+ import C3SIndicatorsGlossaryBlockView from './C3SIndicatorsGlossaryBlockView';
4
+ import { blockAvailableInMission } from '@eeacms/volto-cca-policy/utils';
5
+
6
+ export default function installBlock(config) {
7
+ const blocksConfig = config.blocks.blocksConfig;
8
+
9
+ blocksConfig.c3SIndicatorsGlossaryBlock = {
10
+ id: 'c3SIndicatorsGlossaryBlock',
11
+ title: 'C3S Indicators Glossary',
12
+ icon: zoomSVG,
13
+ group: 'site',
14
+ view: C3SIndicatorsGlossaryBlockView,
15
+ edit: C3SIndicatorsGlossaryBlockEdit,
16
+ sidebarTab: 1,
17
+ security: {
18
+ addPermission: [],
19
+ view: [],
20
+ },
21
+ variations: [],
22
+ restricted: ({ properties, block }) => {
23
+ return blockAvailableInMission(properties, block);
24
+ },
25
+ };
26
+
27
+ return config;
28
+ }
@@ -0,0 +1,14 @@
1
+ export default {
2
+ title: 'C3S Indicators Glossary',
3
+
4
+ fieldsets: [
5
+ {
6
+ id: 'default',
7
+ title: 'Default',
8
+ },
9
+ ],
10
+
11
+ properties: {},
12
+
13
+ required: [],
14
+ };
@@ -27,7 +27,8 @@ export default function C3SIndicatorsOverviewBlockView(props) {
27
27
 
28
28
  React.useEffect(() => {
29
29
  getIndicatorsData();
30
- });
30
+ // eslint-disable-next-line react-hooks/exhaustive-deps
31
+ }, [category]);
31
32
 
32
33
  return (
33
34
  <div className="block c3sindicators-overview-block">
@@ -12,6 +12,7 @@ import installCountryMapProfile from './CountryMapProfile';
12
12
  import installListing from './Listing';
13
13
  import installRAST from './RASTBlock';
14
14
  import installC3SIndicatorsOverviewBlock from './C3SIndicatorsOverviewBlock';
15
+ import installC3SIndicatorsGlossaryBlock from './C3SIndicatorsGlossaryBlock';
15
16
  import installReadMore from './ReadMore';
16
17
 
17
18
  export default function installBlocks(config) {
@@ -22,6 +23,7 @@ export default function installBlocks(config) {
22
23
  installRAST,
23
24
  installReadMore,
24
25
  installC3SIndicatorsOverviewBlock,
26
+ installC3SIndicatorsGlossaryBlock,
25
27
  installMKHMap,
26
28
  installECDEIndicatorsBlock,
27
29
  installCaseStudyExplorerBlock,
@@ -138,16 +138,6 @@ function AdaptationOptionView(props) {
138
138
 
139
139
  <h4>Adaptation Details</h4>
140
140
 
141
- <div id={sectionID('Category')} className="section">
142
- <h5 className="section-title">Category</h5>
143
- {content.category
144
- .map((item) => item.token)
145
- .sort()
146
- .map((cat, index) => (
147
- <Fragment key={index}>{cat}</Fragment>
148
- ))}
149
- </div>
150
-
151
141
  <div id={sectionID('IPCC categories')} className="section">
152
142
  <h5 className="section-title">IPCC categories</h5>
153
143
  {content.ipcc_category
@@ -0,0 +1,395 @@
1
+ /**
2
+ * Header component.
3
+ * @module components/theme/Header/Header
4
+ */
5
+
6
+ import React from 'react'; // , { Component }
7
+ import { useHistory } from 'react-router-dom';
8
+ import cx from 'classnames';
9
+ import { Container, Image, Menu, Dropdown } from 'semantic-ui-react'; // Dropdown,
10
+
11
+ import closeIcon from '@eeacms/volto-eea-design-system/../theme/themes/eea/assets/images/Header/close-line.svg';
12
+ import searchIcon from '@eeacms/volto-eea-design-system/../theme/themes/eea/assets/images/Header/search-line.svg';
13
+ import burgerIcon from '@eeacms/volto-eea-design-system/../theme/themes/eea/assets/images/Header/menu-line.svg';
14
+
15
+ import HeaderSearchPopUp from './HeaderSearchPopUp';
16
+ import HeaderMenuPopUp from './HeaderMenuPopUp';
17
+ import PropTypes from 'prop-types';
18
+
19
+ import { isInternalURL } from '@plone/volto/helpers';
20
+ import config from '@plone/volto/registry';
21
+
22
+ Header.propTypes = {
23
+ transparency: PropTypes.bool,
24
+ inverted: PropTypes.bool,
25
+ };
26
+
27
+ function Header({ children }) {
28
+ return <div className="eea header">{children}</div>;
29
+ }
30
+
31
+ const TopHeader = ({ children }) => (
32
+ <div className="top bar">
33
+ <Container>{children}</Container>
34
+ </div>
35
+ );
36
+
37
+ const TopItem = ({ children, className, id }) => (
38
+ <div className={cx('item', 'header-top-item', className)} id={id}>
39
+ {children}
40
+ </div>
41
+ );
42
+
43
+ const onKeyDownHandler = (event) => {
44
+ if (event.key === 'Enter') {
45
+ event.preventDefault();
46
+ event.target.click();
47
+ //event.target.focus();
48
+ }
49
+ };
50
+
51
+ const TopDropdownMenu = ({
52
+ children,
53
+ className,
54
+ icon,
55
+ hasLanguageDropdown = false,
56
+ id,
57
+ tabletText,
58
+ mobileText,
59
+ text,
60
+ viewportWidth,
61
+ }) => {
62
+ const isTablet = viewportWidth < 991;
63
+ const isMobile = viewportWidth < 767;
64
+
65
+ const Component = ({ mobileText }) => {
66
+ return (
67
+ <>
68
+ {children.props['aria-label'] === 'language switcher' ? (
69
+ hasLanguageDropdown && (
70
+ <Dropdown
71
+ id={id}
72
+ className={className}
73
+ text={mobileText || text}
74
+ icon={icon || 'chevron down'}
75
+ aria-label="dropdown"
76
+ role="dropdown"
77
+ lazyLoad
78
+ closeOnChange={true}
79
+ closeOnBlur={false}
80
+ closeOnEscape={true}
81
+ openOnFocus={false}
82
+ onKeyDown={onKeyDownHandler}
83
+ >
84
+ <Dropdown.Menu role="option">{children}</Dropdown.Menu>
85
+ </Dropdown>
86
+ )
87
+ ) : (
88
+ <Dropdown
89
+ id={id}
90
+ className={className}
91
+ text={mobileText || text}
92
+ icon={icon || 'chevron down'}
93
+ role="dropdown"
94
+ aria-label="dropdown"
95
+ lazyLoad
96
+ closeOnChange={true}
97
+ closeOnBlur={false}
98
+ closeOnEscape={true}
99
+ openOnFocus={false}
100
+ onKeyDown={onKeyDownHandler}
101
+ >
102
+ <Dropdown.Menu role="option">{children}</Dropdown.Menu>
103
+ </Dropdown>
104
+ )}
105
+ </>
106
+ );
107
+ };
108
+ return (
109
+ <>
110
+ {isMobile ? (
111
+ <Component mobileText={mobileText} />
112
+ ) : isTablet ? (
113
+ <Component mobileText={tabletText} />
114
+ ) : (
115
+ <Component />
116
+ )}
117
+ </>
118
+ );
119
+ };
120
+
121
+ // disable sticky until it's more stable
122
+ // const useScrollingUp = () => {
123
+ // let prevScroll;
124
+ //
125
+ // if (process.browser) {
126
+ // prevScroll = window.pageYOffset;
127
+ // }
128
+ // const [scrollingUp, setScrollingUp] = React.useState(false);
129
+ // const handleScroll = () => {
130
+ // const currScroll = window.pageYOffset;
131
+ // const isScrolled = prevScroll > currScroll;
132
+ // setScrollingUp(isScrolled);
133
+ // prevScroll = currScroll;
134
+ // };
135
+ // React.useEffect(() => {
136
+ // window.addEventListener('scroll', handleScroll, { passive: true });
137
+ // return () => {
138
+ // window.removeEventListener('scroll', handleScroll, { passive: true });
139
+ // };
140
+ // });
141
+ // return scrollingUp;
142
+ // };
143
+
144
+ const Main = ({
145
+ logo,
146
+ menuItems,
147
+ menuItemsLayouts,
148
+ renderMenuItem,
149
+ renderGlobalMenuItem,
150
+ headerSearchBox,
151
+ pathname,
152
+ transparency,
153
+ inverted,
154
+ hideSearch,
155
+ isMultilingual,
156
+ }) => {
157
+ const history = useHistory();
158
+ const [activeItem, setActiveItem] = React.useState(pathname);
159
+ const [menuIsActive, setMenuIsActive] = React.useState(false);
160
+ const [searchIsActive, setSearchIsActive] = React.useState(false);
161
+ const [burger, setBurger] = React.useState('');
162
+ const searchInputRef = React.useRef(null);
163
+ const [isClient, setIsClient] = React.useState();
164
+ const itemsLayouts = menuItemsLayouts || config.settings?.menuItemsLayouts;
165
+
166
+ React.useEffect(() => setIsClient(true), []);
167
+
168
+ React.useEffect(() => {
169
+ setMenuIsActive(false);
170
+ setSearchIsActive(false);
171
+ setBurger('');
172
+ // remove active menu when we have no pathname which means we hit logo to go home
173
+ //remove the lang route in order to check if empty
174
+ //setActiveItem as pathname when pathname changed
175
+ if (
176
+ !pathname ||
177
+ (isMultilingual === true && !pathname?.split('/')?.slice(2)?.join('/'))
178
+ ) {
179
+ setActiveItem('');
180
+ } else setActiveItem(pathname);
181
+ }, [isMultilingual, pathname]);
182
+
183
+ React.useEffect(() => {
184
+ if (searchIsActive) {
185
+ searchInputRef.current && searchInputRef.current.focus();
186
+ }
187
+ }, [searchIsActive]);
188
+
189
+ const searchOnClick = (e, x) => {
190
+ if (menuIsActive === true) {
191
+ setBurger('');
192
+ setMenuIsActive(false);
193
+ setActiveItem('');
194
+ }
195
+ setSearchIsActive(!searchIsActive);
196
+ };
197
+
198
+ const mobileBurgerOnClick = () => {
199
+ if (searchIsActive === true) {
200
+ setSearchIsActive(false);
201
+ }
202
+
203
+ if (burger === '') {
204
+ setBurger('open');
205
+ setMenuIsActive(true);
206
+ } else {
207
+ setBurger('');
208
+ setMenuIsActive(false);
209
+ setActiveItem('');
210
+ }
211
+ };
212
+
213
+ const menuOnClickOutside = () => {
214
+ // restore active element if nothing was selected from the menu dropdown
215
+ if (pathname !== activeItem) {
216
+ setActiveItem(pathname);
217
+ }
218
+ // close mobile navigation when clicking outside if we have value for nav
219
+ if (burger) {
220
+ setBurger('');
221
+ }
222
+ // always close the menu
223
+ setMenuIsActive(false);
224
+ };
225
+
226
+ const menuOnClick = (e, item) => {
227
+ if (searchIsActive) setSearchIsActive(false);
228
+ setActiveItem(item['@id'] || item.url);
229
+ if (item.items.length) {
230
+ setMenuIsActive(true);
231
+ } else {
232
+ if (isInternalURL(item.url)) {
233
+ history.push(item.url);
234
+ } else if (isClient) {
235
+ window.location.replace(item.url);
236
+ }
237
+ }
238
+ };
239
+
240
+ // Listens for escape keydown event
241
+ React.useEffect(() => {
242
+ const escKeyPressed = (e) => {
243
+ if (e.key === 'Escape') {
244
+ // menuOnClickOutside();
245
+ // restore active element if nothing was selected from the menu dropdown
246
+ if (pathname !== activeItem) {
247
+ setActiveItem(pathname);
248
+ }
249
+ // close mobile navigation when clicking outside if we have value for nav
250
+ if (burger) {
251
+ setBurger('');
252
+ }
253
+ // always close the menu & search
254
+ setMenuIsActive(false);
255
+ setSearchIsActive(false);
256
+ }
257
+ };
258
+
259
+ document.addEventListener('keydown', escKeyPressed);
260
+
261
+ return () => {
262
+ document.removeEventListener('keydown', escKeyPressed);
263
+ };
264
+ }, [activeItem, burger, pathname]);
265
+
266
+ // React.useEffect(() => {
267
+ // if (searchIsActive || burger === 'open' || menuIsActive) {
268
+ // document.body.style.overflow = 'hidden';
269
+ // } else {
270
+ // document.body.style.overflow = 'unset';
271
+ // }
272
+ // }, [searchIsActive, burger, menuIsActive]);
273
+
274
+ const node = React.useRef();
275
+ const searchButtonRef = React.useRef();
276
+ const mobileMenuBurgerRef = React.useRef();
277
+ const desktopMenuRef = React.useRef();
278
+
279
+ // disable sticky setting until feature is more stable
280
+ // const isScrollingUp = useScrollingUp();
281
+ // <div
282
+ // className={`main bar ${transparency ? 'transparency' : ''} ${
283
+ // isScrollingUp ? 'sticky' : ''
284
+ // }`}
285
+ // >
286
+
287
+ return (
288
+ <div
289
+ className={`main bar ${transparency ? 'transparency' : ''}`}
290
+ ref={node}
291
+ >
292
+ <Container>
293
+ <div className={inverted ? 'main-menu inverted' : 'main-menu'}>
294
+ <div className="header-wrapper">
295
+ {logo}
296
+
297
+ <div className={inverted ? 'main-menu inverted' : 'main-menu'}>
298
+ {menuItems && (
299
+ <ul
300
+ className="ui text eea-main-menu tablet or lower hidden menu"
301
+ ref={desktopMenuRef}
302
+ id={'navigation'}
303
+ >
304
+ {menuItems.map((item) => (
305
+ <Menu.Item
306
+ name={item['@id'] || item.url}
307
+ key={item['@id'] || item.url}
308
+ as={'li'}
309
+ active={
310
+ activeItem.indexOf(item['@id']) !== -1 ||
311
+ activeItem.indexOf(item.url) !== -1
312
+ }
313
+ >
314
+ {renderGlobalMenuItem(item, {
315
+ onClick: menuOnClick,
316
+ })}
317
+ </Menu.Item>
318
+ ))}
319
+ </ul>
320
+ )}
321
+ </div>
322
+ </div>
323
+
324
+ <div className="header-actions">
325
+ {!hideSearch && (
326
+ <button
327
+ className="search-action"
328
+ onClick={searchOnClick}
329
+ tabIndex="0"
330
+ aria-pressed="false"
331
+ aria-haspopup="true"
332
+ ref={searchButtonRef}
333
+ >
334
+ {/* <Icon name={!state.activeSearch ? 'search' : 'close'} /> */}
335
+ <Image
336
+ src={!searchIsActive ? `${searchIcon}` : `${closeIcon}`}
337
+ alt="search button open/close"
338
+ />
339
+ </button>
340
+ )}
341
+ <Header.BurgerAction
342
+ className={`mobile ${burger}`}
343
+ onClick={mobileBurgerOnClick}
344
+ ref={mobileMenuBurgerRef}
345
+ >
346
+ <Image
347
+ src={burger === 'open' ? `${closeIcon}` : `${burgerIcon}`}
348
+ alt="menu icon open/close"
349
+ />
350
+ </Header.BurgerAction>
351
+ </div>
352
+ </div>
353
+ </Container>
354
+ {searchIsActive && (
355
+ <HeaderSearchPopUp
356
+ onClose={searchOnClick}
357
+ searchInputRef={searchInputRef}
358
+ triggerRefs={[searchButtonRef]}
359
+ headerSearchBox={headerSearchBox}
360
+ />
361
+ )}
362
+ <HeaderMenuPopUp
363
+ renderMenuItem={renderMenuItem}
364
+ activeItem={activeItem}
365
+ menuItems={menuItems}
366
+ menuItemsLayouts={itemsLayouts}
367
+ pathName={pathname}
368
+ onClose={menuOnClickOutside}
369
+ triggerRefs={[mobileMenuBurgerRef, desktopMenuRef]}
370
+ visible={menuIsActive}
371
+ />
372
+ </div>
373
+ );
374
+ };
375
+
376
+ const BurgerAction = React.forwardRef((props, ref) => (
377
+ <button
378
+ ref={ref}
379
+ className={`burger-action ${props.className}`}
380
+ tabIndex="0"
381
+ aria-pressed="false"
382
+ aria-haspopup="true"
383
+ onClick={props.onClick}
384
+ >
385
+ {props.children}
386
+ </button>
387
+ ));
388
+
389
+ Header.BurgerAction = BurgerAction;
390
+ Header.Main = Main;
391
+ Header.TopDropdownMenu = TopDropdownMenu;
392
+ Header.TopHeader = TopHeader;
393
+ Header.TopItem = TopItem;
394
+
395
+ export default Header;
@@ -0,0 +1,403 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import {
3
+ Accordion,
4
+ Container,
5
+ Grid,
6
+ Icon,
7
+ List,
8
+ Transition,
9
+ } from 'semantic-ui-react';
10
+
11
+ import { cloneDeep } from 'lodash';
12
+
13
+ import { useClickOutside } from '@eeacms/volto-eea-design-system/helpers';
14
+
15
+ const createColumns = (item, renderMenuItem, item_id) => {
16
+ return item.items.map((item, index) => (
17
+ <React.Fragment key={index}>
18
+ {renderMenuItem(item, {
19
+ className: 'item',
20
+ key: index,
21
+ id: item_id,
22
+ })}
23
+ </React.Fragment>
24
+ ));
25
+ };
26
+
27
+ const ItemGrid = ({
28
+ item,
29
+ columns,
30
+ renderMenuItem,
31
+ hideChildrenFromNavigation,
32
+ }) => {
33
+ const item_id = item.title.toLowerCase().replaceAll(' ', '-') + '-sub-title';
34
+ return (
35
+ <>
36
+ {renderMenuItem(item, { className: 'sub-title', id: item_id })}
37
+ {item.items.length && !hideChildrenFromNavigation ? (
38
+ <List
39
+ aria-labelledby={item_id}
40
+ className={columns && columns > 1 ? `has--${columns}--columns` : ''}
41
+ >
42
+ {createColumns(item, renderMenuItem, item_id)}
43
+ </List>
44
+ ) : null}
45
+ </>
46
+ );
47
+ };
48
+
49
+ const Item = ({
50
+ item,
51
+ icon = false,
52
+ iconName,
53
+ renderMenuItem,
54
+ hideChildrenFromNavigation,
55
+ }) => {
56
+ const item_id = item.title.toLowerCase().replaceAll(' ', '-') + '-sub-title';
57
+ return (
58
+ <>
59
+ {renderMenuItem(item, {
60
+ className: 'sub-title',
61
+ id: item_id,
62
+ })}
63
+ {!hideChildrenFromNavigation && (
64
+ <List className="menu-list" aria-labelledby={item_id}>
65
+ {item.items.map((listItem, index) => (
66
+ <React.Fragment key={index}>
67
+ {renderMenuItem(
68
+ listItem,
69
+ {
70
+ className: 'item',
71
+ key: index,
72
+ },
73
+ { children: icon && <Icon className={iconName} /> },
74
+ )}
75
+ </React.Fragment>
76
+ ))}
77
+ </List>
78
+ )}
79
+ </>
80
+ );
81
+ };
82
+
83
+ const RenderItem = ({ layout, section, renderMenuItem, index }) => {
84
+ const hideChildrenFromNavigation =
85
+ layout.hideChildrenFromNavigation === undefined
86
+ ? true
87
+ : layout.hideChildrenFromNavigation;
88
+ return !layout.menuItemChildrenListColumns ||
89
+ layout.menuItemChildrenListColumns[index] === 1 ? (
90
+ <Item
91
+ item={section}
92
+ renderMenuItem={renderMenuItem}
93
+ hideChildrenFromNavigation={hideChildrenFromNavigation}
94
+ />
95
+ ) : (
96
+ <ItemGrid
97
+ item={section}
98
+ columns={layout.menuItemChildrenListColumns[index]}
99
+ renderMenuItem={renderMenuItem}
100
+ hideChildrenFromNavigation={hideChildrenFromNavigation}
101
+ />
102
+ );
103
+ };
104
+
105
+ export const StandardMegaMenuGrid = ({ menuItem, renderMenuItem, layout }) => {
106
+ const menuItemColumns = layout && layout.menuItemColumns;
107
+ const menuItemColumnsLength =
108
+ (menuItemColumns && menuItemColumns.length - 1) || 0;
109
+
110
+ const renderColumnContent = (section, columnIndex) => (
111
+ <RenderItem
112
+ layout={layout}
113
+ section={section}
114
+ renderMenuItem={renderMenuItem}
115
+ index={columnIndex}
116
+ />
117
+ );
118
+
119
+ const renderColumns = () => (
120
+ <Grid>
121
+ {menuItemColumns.map((section, columnIndex) => (
122
+ <div className={layout.menuItemColumns[columnIndex]} key={columnIndex}>
123
+ {columnIndex !== menuItemColumnsLength
124
+ ? renderColumnContent(menuItem.items[columnIndex], columnIndex)
125
+ : menuItem.items
126
+ .slice(menuItemColumnsLength)
127
+ .map((section, _idx) =>
128
+ renderColumnContent(section, columnIndex),
129
+ )}
130
+ </div>
131
+ ))}
132
+ </Grid>
133
+ );
134
+
135
+ const renderDefaultColumns = () => (
136
+ <div className={layout?.gridContainerClass || 'ui four column grid'}>
137
+ {menuItem.items.map((section, index) => (
138
+ <Grid.Column key={index}>
139
+ {renderColumnContent(section, index)}
140
+ </Grid.Column>
141
+ ))}
142
+ </div>
143
+ );
144
+
145
+ return menuItemColumns ? renderColumns() : renderDefaultColumns();
146
+ };
147
+
148
+ const FirstLevelContent = ({ element, renderMenuItem, pathName }) => {
149
+ const topics = element.title === 'Topics';
150
+ let defaultIndex = -1;
151
+
152
+ return (
153
+ <>
154
+ {!topics ? (
155
+ <React.Fragment>
156
+ {element.items.map((item, index) => {
157
+ let firstLevelPanels = [];
158
+ if (!item.items.length) {
159
+ return (
160
+ <React.Fragment key={index}>
161
+ {renderMenuItem(item, { className: 'item sub-title' })}
162
+ </React.Fragment>
163
+ );
164
+ }
165
+ let x = {};
166
+ x.key = item['@id'] || item['url'];
167
+ if (pathName.indexOf(item.url) !== -1) {
168
+ defaultIndex = index;
169
+ }
170
+ x.title = (
171
+ <Accordion.Title
172
+ key={`title=${index}`}
173
+ as="button"
174
+ aria-expanded={false}
175
+ onClick={(e) => {
176
+ e.currentTarget.setAttribute(
177
+ 'aria-expanded',
178
+ e.currentTarget.className.indexOf('active') === -1,
179
+ );
180
+ }}
181
+ >
182
+ {item.title}
183
+ <Icon className="ri-arrow-down-s-line" size="small" />
184
+ </Accordion.Title>
185
+ );
186
+ let overflow_item = cloneDeep(item);
187
+ overflow_item.title = 'See all';
188
+ x.content = (
189
+ <Accordion.Content>
190
+ {renderMenuItem(overflow_item, {
191
+ className: 'item title-item',
192
+ })}
193
+ <SecondLevelContent
194
+ element={item}
195
+ renderMenuItem={renderMenuItem}
196
+ />
197
+ </Accordion.Content>
198
+ );
199
+ firstLevelPanels.push(x);
200
+ return (
201
+ <Accordion.Accordion
202
+ panels={firstLevelPanels}
203
+ key={index}
204
+ defaultActiveIndex={defaultIndex === index ? 0 : -1}
205
+ />
206
+ );
207
+ })}
208
+ </React.Fragment>
209
+ ) : (
210
+ <SecondLevelContent
211
+ element={element}
212
+ topics={true}
213
+ renderMenuItem={renderMenuItem}
214
+ />
215
+ )}
216
+ </>
217
+ );
218
+ };
219
+
220
+ const SecondLevelContent = ({ element, topics = false, renderMenuItem }) => {
221
+ let content;
222
+ if (topics) {
223
+ const atAGlance = element.items.find(
224
+ (element) => element.title === 'At a glance',
225
+ );
226
+ const inDepth = element.items.find(
227
+ (element) => element.url.indexOf('in-depth') !== -1,
228
+ );
229
+ content = (
230
+ <List>
231
+ {atAGlance &&
232
+ atAGlance.items.map((item, index) => (
233
+ <React.Fragment key={index}>
234
+ {renderMenuItem(item, {
235
+ key: index,
236
+ className: 'item',
237
+ })}
238
+ </React.Fragment>
239
+ ))}
240
+ {inDepth && (
241
+ <React.Fragment key={inDepth.url}>
242
+ {renderMenuItem(inDepth, {
243
+ key: inDepth.url,
244
+ className: 'item',
245
+ })}
246
+ </React.Fragment>
247
+ )}
248
+ </List>
249
+ );
250
+ } else {
251
+ content = (
252
+ <List>
253
+ {element.items.map((item, index) => (
254
+ <React.Fragment key={index}>
255
+ {renderMenuItem(item, {
256
+ key: index,
257
+ className: 'item',
258
+ })}
259
+ </React.Fragment>
260
+ ))}
261
+ </List>
262
+ );
263
+ }
264
+
265
+ return <>{content}</>;
266
+ };
267
+
268
+ const NestedAccordion = ({ menuItems, renderMenuItem, pathName }) => {
269
+ const [activeIndex, setActiveIndex] = useState(-1);
270
+
271
+ useEffect(() => {
272
+ let index = 0;
273
+ menuItems.forEach((menuItem) => {
274
+ if (pathName.includes(menuItem.url)) setActiveIndex(index);
275
+ ++index;
276
+ });
277
+ }, [menuItems, pathName]);
278
+
279
+ const rootPanels = [];
280
+ menuItems.forEach((element, index) => {
281
+ let x = {};
282
+ x.key = index;
283
+ x.title = (
284
+ <Accordion.Title
285
+ key={`title-${index}`}
286
+ index={index}
287
+ aria-expanded={activeIndex === index}
288
+ as="button"
289
+ onClick={() => {
290
+ if (activeIndex === index) {
291
+ setActiveIndex(-1);
292
+ } else setActiveIndex(index);
293
+ }}
294
+ >
295
+ {element.title}
296
+ <Icon className="ri-arrow-down-s-line" size="small" />
297
+ </Accordion.Title>
298
+ );
299
+ let overview = cloneDeep(element);
300
+ x.content = (
301
+ <Accordion.Content key={index}>
302
+ <div className="mega-menu-title">
303
+ {/* Inverted right labeled button as a category title - Mobile */}
304
+ {renderMenuItem(
305
+ overview,
306
+ { className: 'ui button inverted icon right labeled' },
307
+ {
308
+ iconPosition: 'right',
309
+ children: (
310
+ <>
311
+ {/* Add word overview to titles */}
312
+ <span> overview</span>
313
+ <Icon className={'arrow right icon'} alt={'Title icon'} />
314
+ </>
315
+ ),
316
+ },
317
+ )}
318
+ </div>
319
+ <FirstLevelContent
320
+ element={element}
321
+ renderMenuItem={renderMenuItem}
322
+ pathName={pathName}
323
+ />
324
+ </Accordion.Content>
325
+ );
326
+ rootPanels.push(x);
327
+ });
328
+
329
+ return <Accordion activeIndex={activeIndex} panels={rootPanels} />;
330
+ };
331
+
332
+ function HeaderMenuPopUp({
333
+ menuItems,
334
+ menuItemsLayouts,
335
+ renderMenuItem,
336
+ pathName,
337
+ onClose,
338
+ triggerRefs,
339
+ activeItem,
340
+ visible,
341
+ }) {
342
+ const nodeRef = React.useRef();
343
+ useClickOutside({ targetRefs: [nodeRef, ...triggerRefs], callback: onClose });
344
+
345
+ const menuItem = menuItems.find(
346
+ (current) => current.url === activeItem || current['@id'] === activeItem,
347
+ );
348
+
349
+ const layout =
350
+ !!menuItemsLayouts &&
351
+ Object.keys(menuItemsLayouts).includes(menuItem?.url) &&
352
+ menuItemsLayouts[menuItem.url];
353
+
354
+ return (
355
+ <Transition visible={visible} animation="slide down" duration={300}>
356
+ <div id="mega-menu" ref={nodeRef}>
357
+ <Container>
358
+ {menuItem && (
359
+ <div className="menu-content tablet hidden mobile hidden">
360
+ {/* Inverted right labeled button as a category title,
361
+ for topics the button goes inside the grid */}
362
+ {menuItem.title && (
363
+ <div className="mega-menu-title">
364
+ {renderMenuItem(
365
+ menuItem,
366
+ { className: 'ui button inverted icon right labeled' },
367
+ {
368
+ iconPosition: 'right',
369
+ children: (
370
+ <>
371
+ {/* Add word overview to titles */}
372
+ <span> overview</span>
373
+ <Icon
374
+ className={'arrow right icon'}
375
+ alt={'Title icon'}
376
+ />
377
+ </>
378
+ ),
379
+ },
380
+ )}
381
+ </div>
382
+ )}
383
+ <StandardMegaMenuGrid
384
+ menuItem={menuItem}
385
+ renderMenuItem={renderMenuItem}
386
+ layout={layout}
387
+ />
388
+ </div>
389
+ )}
390
+ <div className="tablet only mobile only">
391
+ <NestedAccordion
392
+ menuItems={menuItems}
393
+ renderMenuItem={renderMenuItem}
394
+ pathName={pathName}
395
+ />
396
+ </div>
397
+ </Container>
398
+ </div>
399
+ </Transition>
400
+ );
401
+ }
402
+
403
+ export default HeaderMenuPopUp;
@@ -0,0 +1,131 @@
1
+ import React, { useEffect } from 'react';
2
+ import { Container, Input, List } from 'semantic-ui-react';
3
+ import { withRouter, Link } from 'react-router-dom';
4
+ import { useClickOutside } from '@eeacms/volto-eea-design-system/helpers';
5
+ import { handleEnterKeyPress } from '@eeacms/volto-eea-design-system/helpers';
6
+
7
+ const getRandomItems = (arr, max) => {
8
+ return (
9
+ arr?.slice(0, max).map(function () {
10
+ return this.splice(Math.floor(Math.random() * this.length), 1)[0];
11
+ }, arr.slice()) || []
12
+ );
13
+ };
14
+
15
+ function HeaderSearchPopUp({
16
+ history,
17
+ location,
18
+ onClose,
19
+ searchInputRef,
20
+ headerSearchBox,
21
+ triggerRefs = [],
22
+ }) {
23
+ const nodeRef = React.useRef();
24
+ const headerSearchViews = headerSearchBox || [];
25
+ const defaultView = headerSearchViews.filter((v) => v.isDefault);
26
+ const localView = headerSearchViews.filter((v) =>
27
+ location.pathname.match(v.matchpath ? v.matchpath : v.path),
28
+ );
29
+ const activeView = localView.length > 0 ? localView[0] : defaultView[0];
30
+
31
+ const {
32
+ path = '',
33
+ buttonTitle,
34
+ buttonUrl,
35
+ description,
36
+ placeholder = 'Search',
37
+ searchSuggestions,
38
+ } = activeView || {};
39
+ const { suggestionsTitle, suggestions, maxToShow } = searchSuggestions || {};
40
+
41
+ const [visibleSuggestions, setVisibileSuggestions] = React.useState(
42
+ getRandomItems(suggestions, maxToShow),
43
+ );
44
+
45
+ useEffect(() => {
46
+ setVisibileSuggestions(getRandomItems(suggestions, maxToShow));
47
+ }, [maxToShow, suggestions]);
48
+
49
+ useClickOutside({ targetRefs: [nodeRef, ...triggerRefs], callback: onClose });
50
+
51
+ const onSubmit = (event) => {
52
+ const text = searchInputRef?.current?.inputRef?.current?.value;
53
+ history.push(`${path}?q=${text}`);
54
+
55
+ if (window?.searchContext?.resetSearch) {
56
+ window.searchContext.resetSearch({ searchTerm: text });
57
+ }
58
+
59
+ onClose();
60
+ event.preventDefault();
61
+ };
62
+
63
+ const onClickHandler = (suggestion) => {
64
+ if (window?.searchContext?.resetSearch) {
65
+ window.searchContext.resetSearch({ searchTerm: suggestion });
66
+ }
67
+
68
+ onClose();
69
+ };
70
+
71
+ return (
72
+ <div id="search-box" ref={nodeRef}>
73
+ <div className="wrapper">
74
+ <Container>
75
+ <form method="get" onSubmit={onSubmit}>
76
+ <Input
77
+ ref={searchInputRef}
78
+ className="icon search"
79
+ action={{
80
+ className: 'icon ri-search-line',
81
+ 'aria-label': 'Submit search',
82
+ onClick: onSubmit,
83
+ onKeyDown: (event) => {
84
+ handleEnterKeyPress(event, onSubmit);
85
+ },
86
+ }}
87
+ placeholder={placeholder}
88
+ fluid
89
+ />
90
+ </form>
91
+ {searchSuggestions && suggestions.length > 0 && (
92
+ <div className="search-suggestions">
93
+ {suggestionsTitle && <h4>{suggestionsTitle}</h4>}
94
+
95
+ <List>
96
+ {visibleSuggestions.map((item, i) => {
97
+ return (
98
+ <List.Item key={i}>
99
+ <Link
100
+ to={`${path}?q=${item}`}
101
+ onClick={() => onClickHandler(item)}
102
+ >
103
+ {item}
104
+ </Link>
105
+ </List.Item>
106
+ );
107
+ })}
108
+ </List>
109
+ </div>
110
+ )}
111
+ </Container>
112
+ {buttonTitle && (
113
+ <div className="advanced-search">
114
+ <Container>
115
+ <div>{description}</div>
116
+ <a
117
+ href={buttonUrl || defaultView[0].path}
118
+ className="ui button white inverted"
119
+ title="Advanced search"
120
+ >
121
+ {buttonTitle}
122
+ </a>
123
+ </Container>
124
+ </div>
125
+ )}
126
+ </div>
127
+ </div>
128
+ );
129
+ }
130
+
131
+ export default withRouter(HeaderSearchPopUp);
@@ -121,7 +121,7 @@ export const PublishedModifiedInfo = (props) => {
121
121
  export const DocumentsList = (props) => {
122
122
  const { content } = props;
123
123
  const files = content.cca_files;
124
- if (files.length === 0) {
124
+ if (!files || files.length === 0) {
125
125
  return null;
126
126
  }
127
127
 
package/src/index.js CHANGED
@@ -310,5 +310,3 @@ const applyConfig = (config) => {
310
310
  };
311
311
 
312
312
  export default applyConfig;
313
-
314
- // ('emit("_all_"); def clusters_settings = [["name": "News", "values": ["News","Article"]],["name": "Publications", "values": ["Report","Indicator","Briefing","Topic page","Country fact sheet"]],["name": "Maps and charts", "values": ["Figure (chart/map)","Chart (interactive)","Infographic","Dashboard","Map (interactive)"]],["name": "Data", "values": ["Data set"]],["name": "Others", "values": ["Webpage","Organisation","FAQ","Video","Contract opportunity","Glossary term","Collection","File","Adaptation option","Guidance","Research and knowledge project","Information portal","Tool","Case study","External data reference","Publication reference"]]]; def vals = doc[\'objectProvides\']; def clusters = [\'All\']; for (val in vals) { for (cs in clusters_settings) { if (cs.values.contains(val)) { emit(cs.name) } } }');
@@ -126,10 +126,6 @@ body.subsite-mkh {
126
126
  }
127
127
  }
128
128
 
129
- .eea.header .eea-logo {
130
- max-width: 252px;
131
- }
132
-
133
129
  .subfooter .footer-description {
134
130
  margin-top: 3em;
135
131
  margin-bottom: 1em;
@@ -147,12 +143,6 @@ body.subsite-mkh {
147
143
  }
148
144
 
149
145
  .main-menu {
150
- .ui.text.menu {
151
- position: absolute;
152
- right: 80px;
153
- width: 120% !important;
154
- }
155
-
156
146
  .item a {
157
147
  color: @blue-1;
158
148
  }
@@ -171,24 +161,6 @@ body.subsite-mkh {
171
161
  background-color: @green-1;
172
162
  }
173
163
 
174
- .eea.header .subsite-logo {
175
- z-index: 1;
176
- height: 100%;
177
-
178
- .logo {
179
- display: inline-block;
180
- height: 100%;
181
-
182
- img {
183
- max-width: 320px;
184
- }
185
- }
186
-
187
- &:before {
188
- background-color: transparent;
189
- }
190
- }
191
-
192
164
  .eea.banner {
193
165
  background: @green-1;
194
166
  }
@@ -209,12 +181,6 @@ body.subsite-mkh {
209
181
  }
210
182
  }
211
183
 
212
- @media only screen and (min-width: @largestSmallMonitor) {
213
- .eea.header .subsite-logo {
214
- left: 280px !important;
215
- }
216
- }
217
-
218
184
  body.view-viewview .full {
219
185
  position: relative !important;
220
186
  right: 50%;
@@ -319,3 +285,55 @@ body.subsite-root.section-mission {
319
285
  }
320
286
  }
321
287
  }
288
+
289
+ body.subsite {
290
+ .eea.header {
291
+ .logo-wrapper {
292
+ .eea-logo {
293
+ max-width: 252px;
294
+ }
295
+ }
296
+
297
+ .subsite-logo {
298
+ position: relative !important;
299
+ top: auto !important;
300
+ left: auto !important;
301
+ transform: none !important;
302
+
303
+ .logo {
304
+ img {
305
+ max-width: 320px;
306
+ }
307
+ }
308
+
309
+ &:before {
310
+ background-color: transparent !important;
311
+ }
312
+ }
313
+ }
314
+ }
315
+
316
+ #main .main.bar {
317
+ .ui.container {
318
+ .ui.text.menu {
319
+ width: 100%;
320
+ }
321
+ }
322
+ }
323
+
324
+ .main-menu {
325
+ align-items: normal !important;
326
+ column-gap: 1em;
327
+
328
+ > .header-wrapper {
329
+ display: flex;
330
+ width: 100%;
331
+ flex-direction: column;
332
+ justify-content: space-between;
333
+ }
334
+ }
335
+
336
+ .header-actions {
337
+ display: flex;
338
+ margin-right: auto;
339
+ }