@eeacms/volto-cca-policy 0.1.44 → 0.1.46
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 +23 -2
- package/package.json +1 -1
- package/src/components/manage/Blocks/C3SIndicatorsGlossaryBlock/C3SIndicatorsGlossaryBlockEdit.jsx +32 -0
- package/src/components/manage/Blocks/C3SIndicatorsGlossaryBlock/C3SIndicatorsGlossaryBlockView.jsx +35 -0
- package/src/components/manage/Blocks/C3SIndicatorsGlossaryBlock/index.js +28 -0
- package/src/components/manage/Blocks/C3SIndicatorsGlossaryBlock/schema.js +14 -0
- package/src/components/manage/Blocks/C3SIndicatorsOverviewBlock/C3SIndicatorsOverviewBlockView.jsx +2 -1
- package/src/components/manage/Blocks/index.js +2 -0
- package/src/components/theme/Views/AdaptationOptionView.jsx +0 -10
- package/src/customizations/@eeacms/volto-eea-design-system/ui/Header/Header.jsx +395 -0
- package/src/customizations/@eeacms/volto-eea-design-system/ui/Header/HeaderMenuPopUp.js +403 -0
- package/src/customizations/@eeacms/volto-eea-design-system/ui/Header/HeaderSearchPopUp.js +131 -0
- package/src/helpers/Utils.jsx +1 -1
- package/src/index.js +0 -2
- package/theme/globals/site.overrides +117 -49
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,29 @@ 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.46](https://github.com/eea/volto-cca-policy/compare/0.1.45...0.1.46) - 6 October 2023
|
|
8
|
+
|
|
9
|
+
#### :rocket: New Features
|
|
10
|
+
|
|
11
|
+
- feat: add icon for external links [kreafox - [`6cfc86b`](https://github.com/eea/volto-cca-policy/commit/6cfc86b1e15773578de3ba6f80467daf5b11b087)]
|
|
12
|
+
|
|
13
|
+
#### :nail_care: Enhancements
|
|
14
|
+
|
|
15
|
+
- change: add padding on listing summary view [kreafox - [`d86e321`](https://github.com/eea/volto-cca-policy/commit/d86e321a905dbb925847b2b0b88c20eea7da0003)]
|
|
16
|
+
|
|
17
|
+
#### :hammer_and_wrench: Others
|
|
18
|
+
|
|
19
|
+
- test: use volto version 16 [kreafox - [`a7ed678`](https://github.com/eea/volto-cca-policy/commit/a7ed678ac981213177d0377d312692b69e36c7d8)]
|
|
20
|
+
- Add style on listing view as well [kreafox - [`b741ca8`](https://github.com/eea/volto-cca-policy/commit/b741ca82a959a1261e8ec54ef0c7ecce9b8bfb15)]
|
|
21
|
+
### [0.1.45](https://github.com/eea/volto-cca-policy/compare/0.1.44...0.1.45) - 25 September 2023
|
|
22
|
+
|
|
23
|
+
#### :hammer_and_wrench: Others
|
|
24
|
+
|
|
25
|
+
- Refs #161483 - Fix name. [GhitaB - [`e614a27`](https://github.com/eea/volto-cca-policy/commit/e614a275681b42d23292f0eb8f371686fefa8d70)]
|
|
26
|
+
- Code cleanup [Tiberiu Ichim - [`c0521db`](https://github.com/eea/volto-cca-policy/commit/c0521dbd4642516c49ec1560d7cc648b2918fddc)]
|
|
27
|
+
- Refs #161483 - Fix jenkins and infinite loop render. [GhitaB - [`2080298`](https://github.com/eea/volto-cca-policy/commit/20802988bbf2bee0759f524c0907da8dd6769284)]
|
|
28
|
+
- Refs #161483 - Add ECDE C3S indicators glossary block. [GhitaB - [`806a768`](https://github.com/eea/volto-cca-policy/commit/806a76888290cb52616dfd6a937a8e49fc6a9ed3)]
|
|
29
|
+
- 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
30
|
### [0.1.44](https://github.com/eea/volto-cca-policy/compare/0.1.43...0.1.44) - 22 September 2023
|
|
8
31
|
|
|
9
32
|
#### :hammer_and_wrench: Others
|
|
@@ -173,7 +196,6 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
|
|
173
196
|
|
|
174
197
|
#### :house: Internal changes
|
|
175
198
|
|
|
176
|
-
- chore: [JENKINS] Remove alpha testing version [valentinab25 - [`ad1ced0`](https://github.com/eea/volto-cca-policy/commit/ad1ced0971ba116c13a3b5fcc039172cc915c919)]
|
|
177
199
|
|
|
178
200
|
#### :hammer_and_wrench: Others
|
|
179
201
|
|
|
@@ -654,7 +676,6 @@ Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
|
|
654
676
|
#### :hammer_and_wrench: Others
|
|
655
677
|
|
|
656
678
|
- 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
679
|
### [0.1.1](https://github.com/eea/volto-cca-policy/compare/0.1.0...0.1.1) - 13 December 2022
|
|
659
680
|
|
|
660
681
|
#### :hammer_and_wrench: Others
|
package/package.json
CHANGED
package/src/components/manage/Blocks/C3SIndicatorsGlossaryBlock/C3SIndicatorsGlossaryBlockEdit.jsx
ADDED
|
@@ -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
|
+
}
|
package/src/components/manage/Blocks/C3SIndicatorsGlossaryBlock/C3SIndicatorsGlossaryBlockView.jsx
ADDED
|
@@ -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
|
+
}
|
package/src/components/manage/Blocks/C3SIndicatorsOverviewBlock/C3SIndicatorsOverviewBlockView.jsx
CHANGED
|
@@ -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);
|
package/src/helpers/Utils.jsx
CHANGED
|
@@ -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) } } }');
|
|
@@ -23,6 +23,38 @@ p.has--clear--both:empty {
|
|
|
23
23
|
display: block;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
// Add icon for external links
|
|
27
|
+
#page-document {
|
|
28
|
+
a[rel='noopener noreferrer']:not(.ui.button),
|
|
29
|
+
a[target='_blank']:not(.ui.button) {
|
|
30
|
+
&:before {
|
|
31
|
+
position: relative;
|
|
32
|
+
top: 2px;
|
|
33
|
+
margin-right: 3px;
|
|
34
|
+
color: inherit;
|
|
35
|
+
content: '\ecaf';
|
|
36
|
+
font-family: remixicon;
|
|
37
|
+
font-size: 1em;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
&:visited {
|
|
41
|
+
&:before {
|
|
42
|
+
// fix visited link icon color
|
|
43
|
+
// doesn't work with color: inherit
|
|
44
|
+
color: @linkVisitedColor;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
&:active {
|
|
49
|
+
&:before {
|
|
50
|
+
// fix active link icon color
|
|
51
|
+
// doesn't work with color: inherit
|
|
52
|
+
color: @linkActiveColor;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
26
58
|
// Adaptation option view styles
|
|
27
59
|
div.adaptation-option-view,
|
|
28
60
|
div.case-study-view {
|
|
@@ -126,10 +158,6 @@ body.subsite-mkh {
|
|
|
126
158
|
}
|
|
127
159
|
}
|
|
128
160
|
|
|
129
|
-
.eea.header .eea-logo {
|
|
130
|
-
max-width: 252px;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
161
|
.subfooter .footer-description {
|
|
134
162
|
margin-top: 3em;
|
|
135
163
|
margin-bottom: 1em;
|
|
@@ -147,12 +175,6 @@ body.subsite-mkh {
|
|
|
147
175
|
}
|
|
148
176
|
|
|
149
177
|
.main-menu {
|
|
150
|
-
.ui.text.menu {
|
|
151
|
-
position: absolute;
|
|
152
|
-
right: 80px;
|
|
153
|
-
width: 120% !important;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
178
|
.item a {
|
|
157
179
|
color: @blue-1;
|
|
158
180
|
}
|
|
@@ -171,24 +193,6 @@ body.subsite-mkh {
|
|
|
171
193
|
background-color: @green-1;
|
|
172
194
|
}
|
|
173
195
|
|
|
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
196
|
.eea.banner {
|
|
193
197
|
background: @green-1;
|
|
194
198
|
}
|
|
@@ -209,12 +213,6 @@ body.subsite-mkh {
|
|
|
209
213
|
}
|
|
210
214
|
}
|
|
211
215
|
|
|
212
|
-
@media only screen and (min-width: @largestSmallMonitor) {
|
|
213
|
-
.eea.header .subsite-logo {
|
|
214
|
-
left: 280px !important;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
216
|
body.view-viewview .full {
|
|
219
217
|
position: relative !important;
|
|
220
218
|
right: 50%;
|
|
@@ -294,28 +292,98 @@ body.view-viewview.has-toolbar:not(.has-sidebar):not(.has-sidebar-collapsed) {
|
|
|
294
292
|
/* Mission: Latest news updates, Latest events */
|
|
295
293
|
body.subsite-root.section-mission {
|
|
296
294
|
div.columns-view {
|
|
297
|
-
div.block.listing.default
|
|
295
|
+
div.block.listing.default,
|
|
296
|
+
div.block.listing.summary {
|
|
298
297
|
div.items {
|
|
299
298
|
div.listing-item {
|
|
300
|
-
h4 {
|
|
301
|
-
padding-left: 1.5em;
|
|
302
|
-
margin-bottom: 0.5em;
|
|
303
|
-
|
|
304
|
-
&:before {
|
|
305
|
-
position: absolute;
|
|
306
|
-
left: 0;
|
|
307
|
-
content: '\ea6c';
|
|
308
|
-
font-family: remixicon !important;
|
|
309
|
-
font-style: normal;
|
|
310
|
-
font-weight: normal;
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
padding-bottom: 0.5em;
|
|
314
299
|
border: none;
|
|
315
300
|
margin-bottom: 0px !important;
|
|
316
301
|
}
|
|
302
|
+
|
|
303
|
+
.listing-body {
|
|
304
|
+
position: relative;
|
|
305
|
+
padding-left: 2em;
|
|
306
|
+
|
|
307
|
+
&:before {
|
|
308
|
+
position: absolute;
|
|
309
|
+
top: -5px;
|
|
310
|
+
left: 0;
|
|
311
|
+
color: inherit;
|
|
312
|
+
content: '\ea6c';
|
|
313
|
+
font-family: remixicon !important;
|
|
314
|
+
font-size: 1.5em;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
317
|
margin-top: 1.5em;
|
|
318
318
|
}
|
|
319
319
|
}
|
|
320
|
+
|
|
321
|
+
div.block.listing.summary {
|
|
322
|
+
.u-item {
|
|
323
|
+
padding: 0 0 0.8em 0;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
div.block.listing.default {
|
|
328
|
+
div.listing-item {
|
|
329
|
+
padding-bottom: 0.5em;
|
|
330
|
+
|
|
331
|
+
h4 {
|
|
332
|
+
margin-bottom: 0.5em;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
320
336
|
}
|
|
321
337
|
}
|
|
338
|
+
|
|
339
|
+
body.subsite {
|
|
340
|
+
.eea.header {
|
|
341
|
+
.logo-wrapper {
|
|
342
|
+
.eea-logo {
|
|
343
|
+
max-width: 252px;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.subsite-logo {
|
|
348
|
+
position: relative !important;
|
|
349
|
+
top: auto !important;
|
|
350
|
+
left: auto !important;
|
|
351
|
+
transform: none !important;
|
|
352
|
+
|
|
353
|
+
.logo {
|
|
354
|
+
img {
|
|
355
|
+
max-width: 320px;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
&:before {
|
|
360
|
+
background-color: transparent !important;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
#main .main.bar {
|
|
367
|
+
.ui.container {
|
|
368
|
+
.ui.text.menu {
|
|
369
|
+
width: 100%;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
.main-menu {
|
|
375
|
+
align-items: normal !important;
|
|
376
|
+
column-gap: 1em;
|
|
377
|
+
|
|
378
|
+
> .header-wrapper {
|
|
379
|
+
display: flex;
|
|
380
|
+
width: 100%;
|
|
381
|
+
flex-direction: column;
|
|
382
|
+
justify-content: space-between;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
.header-actions {
|
|
387
|
+
display: flex;
|
|
388
|
+
margin-right: auto;
|
|
389
|
+
}
|