@eeacms/volto-eea-design-system 0.4.3 → 0.5.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.
Files changed (49) hide show
  1. package/CHANGELOG.md +129 -0
  2. package/package.json +1 -1
  3. package/src/semantic.less +4 -4
  4. package/src/ui/Divider/Divider.stories.jsx +170 -0
  5. package/src/ui/Footer/Contact.jsx +6 -4
  6. package/src/ui/Footer/FooterHeader.jsx +2 -2
  7. package/src/ui/Footer/FooterSites.jsx +1 -1
  8. package/src/ui/Footer/Social.jsx +2 -2
  9. package/src/ui/Footer/SubFooter.jsx +54 -0
  10. package/src/ui/Header/Header.jsx +64 -34
  11. package/src/ui/Header/HeaderMenuPopUp.js +215 -54
  12. package/src/ui/Header/HeaderSearchPopUp.js +4 -4
  13. package/src/ui/Hero/Hero.jsx +55 -0
  14. package/src/ui/Hero/Hero.stories.jsx +205 -0
  15. package/src/ui/Logo/Logo.jsx +2 -3
  16. package/src/ui/Quote/Testimonial/Testimonial.jsx +3 -4
  17. package/src/ui/index.js +2 -0
  18. package/storybook-static/favicon.ico +0 -0
  19. package/theme/theme.config +0 -1
  20. package/theme/themes/eea/assets/images/forest.png +0 -0
  21. package/theme/themes/eea/assets/logo/EionetFull.svg +10 -0
  22. package/theme/themes/eea/assets/logo/climate-health.svg +53 -0
  23. package/theme/themes/eea/assets/logo/eea.svg +9 -0
  24. package/theme/themes/eea/assets/logo/eionet.svg +10 -0
  25. package/theme/themes/eea/assets/logo/fise.svg +37 -36
  26. package/theme/themes/eea/collections/menu.overrides +5 -5
  27. package/theme/themes/eea/definitions/views/item.less +13 -6
  28. package/theme/themes/eea/elements/container.overrides +1 -2
  29. package/theme/themes/eea/elements/divider.overrides +63 -0
  30. package/theme/themes/eea/elements/divider.variables +10 -1
  31. package/theme/themes/eea/elements/image.overrides +39 -0
  32. package/theme/themes/eea/elements/input.overrides +21 -8
  33. package/theme/themes/eea/extras/footer.less +188 -225
  34. package/theme/themes/eea/extras/footer.variables +49 -77
  35. package/theme/themes/eea/extras/header.less +473 -511
  36. package/theme/themes/eea/extras/header.variables +108 -80
  37. package/theme/themes/eea/extras/hero.less +59 -0
  38. package/theme/themes/eea/extras/hero.variables +23 -0
  39. package/theme/themes/eea/globals/site.overrides +5 -16
  40. package/theme/themes/eea/globals/site.variables +8 -0
  41. package/theme/themes/eea/globals/utilities.less +2 -2
  42. package/theme/themes/eea/modules/accordion.overrides +18 -0
  43. package/theme/themes/eea/modules/tab.overrides +2 -2
  44. package/theme/themes/eea/tokens/fonts.less +4 -3
  45. package/theme/themes/eea/views/card.overrides +7 -5
  46. package/theme/themes/eea/views/item.overrides +18 -3
  47. package/src/ui/Divider/Divider.jsx +0 -7
  48. package/theme/themes/eea/extras/divider.less +0 -14
  49. package/theme/themes/eea/extras/divider.variables +0 -9
@@ -13,6 +13,12 @@ import burgerIcon from '@eeacms/volto-eea-design-system/../theme/themes/eea/asse
13
13
 
14
14
  import HeaderSearchPopUp from './HeaderSearchPopUp';
15
15
  import HeaderMenuPopUp from './HeaderMenuPopUp';
16
+ import PropTypes from 'prop-types';
17
+
18
+ Header.propTypes = {
19
+ transparency: PropTypes.bool,
20
+ inverted: PropTypes.bool,
21
+ };
16
22
 
17
23
  function Header({ children }) {
18
24
  return <div className="eea header">{children}</div>;
@@ -39,7 +45,7 @@ const TopDropdownMenu = ({
39
45
  text,
40
46
  viewportWidth,
41
47
  }) => {
42
- const isMobile = viewportWidth < 480;
48
+ const isMobile = viewportWidth < 767;
43
49
 
44
50
  const Component = ({ mobileText }) => (
45
51
  <Dropdown
@@ -57,14 +63,39 @@ const TopDropdownMenu = ({
57
63
  );
58
64
  };
59
65
 
66
+ // disable sticky until it's more stable
67
+ // const useScrollingUp = () => {
68
+ // let prevScroll;
69
+ //
70
+ // if (process.browser) {
71
+ // prevScroll = window.pageYOffset;
72
+ // }
73
+ // const [scrollingUp, setScrollingUp] = React.useState(false);
74
+ // const handleScroll = () => {
75
+ // const currScroll = window.pageYOffset;
76
+ // const isScrolled = prevScroll > currScroll;
77
+ // setScrollingUp(isScrolled);
78
+ // prevScroll = currScroll;
79
+ // };
80
+ // React.useEffect(() => {
81
+ // window.addEventListener('scroll', handleScroll, { passive: true });
82
+ // return () => {
83
+ // window.removeEventListener('scroll', handleScroll, { passive: true });
84
+ // };
85
+ // });
86
+ // return scrollingUp;
87
+ // };
88
+
60
89
  const Main = ({
61
90
  logo,
62
91
  menuItems,
63
92
  renderMenuItem,
64
93
  renderGlobalMenuItem,
65
94
  pathname,
95
+ transparency,
96
+ inverted,
66
97
  }) => {
67
- const [activeItem, setActiveItem] = React.useState('');
98
+ const [activeItem, setActiveItem] = React.useState(pathname);
68
99
  const [menuIsActive, setMenuIsActive] = React.useState(false);
69
100
  const [searchIsActive, setSearchIsActive] = React.useState(false);
70
101
  const [burger, setBurger] = React.useState('');
@@ -73,15 +104,19 @@ const Main = ({
73
104
  setMenuIsActive(false);
74
105
  setSearchIsActive(false);
75
106
  setBurger('');
107
+ // remove active menu when we have no pathname which means we hit logo to go home
108
+ if (!pathname) {
109
+ setActiveItem('');
110
+ }
76
111
  }, [pathname]);
77
112
 
78
113
  const searchOnClick = (e, x) => {
79
114
  if (menuIsActive === true) {
80
115
  setBurger('');
81
116
  setMenuIsActive(false);
117
+ setActiveItem('');
82
118
  }
83
119
  setSearchIsActive(!searchIsActive);
84
- setActiveItem('');
85
120
  };
86
121
 
87
122
  const mobileBurgerOnClick = () => {
@@ -95,49 +130,57 @@ const Main = ({
95
130
  } else {
96
131
  setBurger('');
97
132
  setMenuIsActive(false);
133
+ setActiveItem('');
98
134
  }
99
135
  };
100
136
 
101
- const desktopBurgerOnClick = () => {
102
- setMenuIsActive(false);
103
- setActiveItem('');
104
- };
105
-
106
137
  const menuOnClick = (e, item) => {
107
138
  if (searchIsActive) setSearchIsActive(false);
108
139
  setActiveItem(item['@id'] || item.url);
109
140
  setMenuIsActive(true);
110
141
  };
111
142
 
112
- React.useEffect(() => {
113
- if (searchIsActive || burger === 'open' || menuIsActive) {
114
- document.body.style.overflow = 'hidden';
115
- } else {
116
- document.body.style.overflow = 'unset';
117
- }
118
- }, [searchIsActive, burger, menuIsActive]);
143
+ // React.useEffect(() => {
144
+ // if (searchIsActive || burger === 'open' || menuIsActive) {
145
+ // document.body.style.overflow = 'hidden';
146
+ // } else {
147
+ // document.body.style.overflow = 'unset';
148
+ // }
149
+ // }, [searchIsActive, burger, menuIsActive]);
119
150
 
120
151
  const node = React.useRef();
121
152
  const searchButtonRef = React.useRef();
122
- const desktopMenuBurgerRef = React.useRef();
123
153
  const mobileMenuBurgerRef = React.useRef();
124
154
 
155
+ // disable sticky setting until feature is more stable
156
+ // const isScrollingUp = useScrollingUp();
157
+ // <div
158
+ // className={`main bar ${transparency ? 'transparency' : ''} ${
159
+ // isScrollingUp ? 'sticky' : ''
160
+ // }`}
161
+ // >
125
162
  return (
126
- <div className="main bar" ref={node}>
163
+ <div
164
+ className={`main bar ${transparency ? 'transparency' : ''}`}
165
+ ref={node}
166
+ >
127
167
  <Container>
128
168
  <Grid>
129
169
  <Grid.Column mobile={8} tablet={8} computer={4}>
130
170
  {logo}
131
171
  </Grid.Column>
132
172
  <Grid.Column mobile={4} tablet={4} computer={8}>
133
- <div className="main-menu">
134
- {!menuIsActive && menuItems && (
173
+ <div className={inverted ? 'main-menu inverted' : 'main-menu'}>
174
+ {menuItems && (
135
175
  <Menu className="eea-main-menu tablet or lower hidden" text>
136
176
  {menuItems.map((item) => (
137
177
  <Menu.Item
138
178
  name={item['@id'] || item.url}
139
- active={activeItem === item.key}
140
179
  key={item['@id'] || item.url}
180
+ active={
181
+ activeItem.indexOf(item['@id']) !== -1 ||
182
+ activeItem.indexOf(item.url) !== -1
183
+ }
141
184
  >
142
185
  {renderGlobalMenuItem(item, {
143
186
  onClick: menuOnClick,
@@ -146,16 +189,6 @@ const Main = ({
146
189
  ))}
147
190
  </Menu>
148
191
  )}
149
- {menuIsActive && (
150
- <Header.BurgerAction
151
- className="desktop"
152
- onClick={desktopBurgerOnClick}
153
- ref={desktopMenuBurgerRef}
154
- >
155
- {/* <Icon name="close" /> */}
156
- <Image src={closeIcon} alt="menu close icon" />
157
- </Header.BurgerAction>
158
- )}
159
192
  <div
160
193
  className="search-action"
161
194
  onClick={searchOnClick}
@@ -173,9 +206,6 @@ const Main = ({
173
206
  onClick={mobileBurgerOnClick}
174
207
  ref={mobileMenuBurgerRef}
175
208
  >
176
- {/* <Icon
177
- name={this.state.burger === 'open' ? 'close' : 'bars'}
178
- ></Icon> */}
179
209
  <Image
180
210
  src={burger === 'open' ? `${closeIcon}` : `${burgerIcon}`}
181
211
  alt="menu icon open/close"
@@ -197,7 +227,7 @@ const Main = ({
197
227
  activeItem={activeItem}
198
228
  menuItems={menuItems}
199
229
  onClose={mobileBurgerOnClick}
200
- triggerRefs={[mobileMenuBurgerRef, desktopMenuBurgerRef]}
230
+ triggerRefs={[mobileMenuBurgerRef]}
201
231
  />
202
232
  )}
203
233
  </div>
@@ -1,76 +1,237 @@
1
1
  import React from 'react';
2
- import { Container } from 'semantic-ui-react';
3
- import cx from 'classnames';
2
+ import {
3
+ Container,
4
+ Divider,
5
+ Grid,
6
+ List,
7
+ Icon,
8
+ Accordion,
9
+ } from 'semantic-ui-react';
4
10
 
11
+ import { Link } from 'react-router-dom';
5
12
  import { useClickOutside } from '@eeacms/volto-eea-design-system/helpers';
6
13
 
7
- const levels = ['first', 'second', 'third'];
14
+ const createColumns = (item, length) => {
15
+ let subArrays = [];
16
+ let size = length;
17
+ for (let i = 0; i < item.items.length; i += size) {
18
+ subArrays.push(item.items.slice(i, i + size));
19
+ }
8
20
 
9
- const Item = ({ item, renderMenuItem }) => (
21
+ const column = subArrays.map((subArray, index) => (
22
+ <Grid.Column key={index}>
23
+ <List>
24
+ {subArray.map((arrayItem, idx) => (
25
+ <Link role="listitem" className="item" to={arrayItem.url} key={idx}>
26
+ {arrayItem.title}
27
+ </Link>
28
+ ))}
29
+ </List>
30
+ </Grid.Column>
31
+ ));
32
+
33
+ return column;
34
+ };
35
+
36
+ const ItemGrid = ({ item, columns, length }) => (
37
+ <>
38
+ <Link className="sub-title" to={item.url}>
39
+ {item.title}
40
+ </Link>
41
+ <Grid columns={columns}>{createColumns(item, length)}</Grid>
42
+ </>
43
+ );
44
+
45
+ const Item = ({ item, icon = false, iconName }) => (
10
46
  <>
11
- {item.items.length > 0 && (
12
- <label htmlFor={`drop-${item['@id'] || item.url}`} className="toggle">
13
- {item.title}
14
- </label>
15
- )}
16
- {renderMenuItem(item)}
17
- {item.items.length > 0 && (
18
- <input type="checkbox" id={`drop-${item['@id'] || item.url}`} />
19
- )}
47
+ <Link className="sub-title" to={item.url}>
48
+ {item.title}
49
+ </Link>
50
+ <List className="menu-list">
51
+ {item.items.map((listItem, index) => (
52
+ <Link role="listitem" className="item" to={listItem.url} key={index}>
53
+ {icon && <Icon className={iconName} />}
54
+ {listItem.title}
55
+ </Link>
56
+ ))}
57
+ </List>
20
58
  </>
21
59
  );
22
60
 
23
- const ItemsList = ({ items, renderMenuItem, level = 0, activeItem }) => (
24
- <ul
25
- className={cx(
26
- level === 0 ? 'menu' : 'sub',
27
- levels[level],
28
- `level-${level}`,
29
- )}
30
- >
31
- {items.map((item) => (
32
- <li
33
- key={item['@id'] || item.url}
34
- className={cx({
35
- hasSubMenu: item.items?.length > 0,
36
- active: (item['@id'] || item.url) === activeItem,
37
- })}
38
- >
39
- <Item item={item} renderMenuItem={renderMenuItem} />
40
-
41
- {item.items.length > 0 && (
42
- <ItemsList
43
- items={item.items}
44
- level={level + 1}
45
- renderMenuItem={renderMenuItem}
46
- activeItem={activeItem}
47
- />
61
+ const Topics = ({ menuItem }) => (
62
+ <Grid>
63
+ {menuItem.items.map((section, index) => (
64
+ <React.Fragment key={index}>
65
+ {section.title === 'At a glance' ? (
66
+ <Grid.Column width={3} id="at-a-glance">
67
+ <Item item={section} icon={true} iconName="ri-leaf-line" />
68
+ </Grid.Column>
69
+ ) : (
70
+ <Grid.Column width={9} key={index}>
71
+ <ItemGrid item={section} columns={4} length={10} />
72
+ </Grid.Column>
48
73
  )}
49
- </li>
74
+ </React.Fragment>
75
+ ))}
76
+ </Grid>
77
+ );
78
+
79
+ const Countries = ({ menuItem }) => (
80
+ <Grid>
81
+ <Grid.Column width={8}>
82
+ {menuItem.items.map((section, index) => (
83
+ <React.Fragment key={index}>
84
+ {section.title === 'EEA member countries' && (
85
+ <ItemGrid item={section} columns={5} length={7} />
86
+ )}
87
+ </React.Fragment>
88
+ ))}
89
+ </Grid.Column>
90
+ <Grid.Column width={4}>
91
+ <Grid columns={1} className="nested-grid">
92
+ {menuItem.items.map((section, index) => (
93
+ <React.Fragment key={index}>
94
+ {section.title !== 'EEA member countries' && (
95
+ <Grid.Column>
96
+ <ItemGrid item={section} columns={2} length={3} />
97
+ </Grid.Column>
98
+ )}
99
+ </React.Fragment>
100
+ ))}
101
+ </Grid>
102
+ </Grid.Column>
103
+ </Grid>
104
+ );
105
+
106
+ const StandardMegaMenuGrid = ({ menuItem }) => (
107
+ <Grid columns={4}>
108
+ {menuItem.items.map((section, index) => (
109
+ <Grid.Column key={index}>
110
+ <Item item={section} />
111
+ </Grid.Column>
50
112
  ))}
51
- </ul>
113
+ </Grid>
52
114
  );
53
115
 
54
- function HeaderMenuPopUp({
55
- menuItems,
56
- onClose,
57
- triggerRefs,
58
- renderMenuItem,
59
- activeItem,
60
- }) {
116
+ const FirstLevelContent = ({ element }) => {
117
+ const topics = element.title === 'Topics' ? true : false;
118
+
119
+ const firstLevelPanels = [];
120
+ let content;
121
+ if (!topics) {
122
+ element.items.forEach((item, index) => {
123
+ let x = {};
124
+ x.key = item['@id'];
125
+ x.title = (
126
+ <Accordion.Title key={`title=${index}`}>
127
+ {item.title}
128
+ <Icon className="ri-arrow-down-s-line" size="small" />
129
+ </Accordion.Title>
130
+ );
131
+ x.content = (
132
+ <Accordion.Content key={index}>
133
+ <SecondLevelContent element={item} />
134
+ </Accordion.Content>
135
+ );
136
+ firstLevelPanels.push(x);
137
+ });
138
+ content = <Accordion.Accordion panels={firstLevelPanels} />;
139
+ } else {
140
+ content = <SecondLevelContent element={element} topics={true} />;
141
+ }
142
+ return <>{content}</>;
143
+ };
144
+
145
+ const SecondLevelContent = ({ element, topics = false }) => {
146
+ let content;
147
+ if (topics) {
148
+ const atAGlance = element.items.find(
149
+ (element) => element.title === 'At a glance',
150
+ );
151
+ content = (
152
+ <List>
153
+ {atAGlance.items.map((item, index) => (
154
+ <Link role="listitem" className="item" to={item.url} key={index}>
155
+ {item.title}
156
+ </Link>
157
+ ))}
158
+ <Link
159
+ role="listitem"
160
+ className="item"
161
+ to={element.url}
162
+ key={element['@id']}
163
+ >
164
+ A-Z Topics
165
+ </Link>
166
+ </List>
167
+ );
168
+ } else {
169
+ content = (
170
+ <List>
171
+ {element.items.map((item, index) => (
172
+ <Link role="listitem" className="item" to={item.url} key={index}>
173
+ {item.title}
174
+ </Link>
175
+ ))}
176
+ </List>
177
+ );
178
+ }
179
+
180
+ return <>{content}</>;
181
+ };
182
+
183
+ const NestedAccordion = ({ menuItems }) => {
184
+ const rootPanels = [];
185
+ menuItems.forEach((element, index) => {
186
+ let x = {};
187
+ x.key = index;
188
+ x.title = (
189
+ <Accordion.Title key={`title-${index}`}>
190
+ {element.title}
191
+ <Icon className="ri-arrow-down-s-line" size="small" />
192
+ </Accordion.Title>
193
+ );
194
+ x.content = (
195
+ <Accordion.Content key={index}>
196
+ <FirstLevelContent element={element} />
197
+ </Accordion.Content>
198
+ );
199
+ rootPanels.push(x);
200
+ });
201
+
202
+ return <Accordion panels={rootPanels} />;
203
+ };
204
+
205
+ function HeaderMenuPopUp({ menuItems, onClose, triggerRefs, activeItem }) {
61
206
  const nodeRef = React.useRef();
62
207
  useClickOutside({ targetRefs: [nodeRef, ...triggerRefs], callback: onClose });
63
208
 
209
+ const menuItem = menuItems.find(
210
+ (current) => current.url === activeItem || current['@id'] === activeItem,
211
+ );
212
+
64
213
  return (
65
214
  <div id="mega-menu" ref={nodeRef}>
66
215
  <Container>
67
- <nav>
68
- <ItemsList
69
- items={menuItems}
70
- renderMenuItem={renderMenuItem}
71
- activeItem={activeItem}
72
- />
73
- </nav>
216
+ {menuItem && (
217
+ <div className="menu-content tablet hidden mobile hidden">
218
+ <h3 className="title">
219
+ <Link to={menuItem.url}>{menuItem.title}</Link>
220
+ </h3>
221
+ <Divider fitted />
222
+ {menuItem.title === 'Topics' ? (
223
+ <Topics menuItem={menuItem} />
224
+ ) : menuItem.title === 'Countries' ? (
225
+ <Countries menuItem={menuItem} />
226
+ ) : (
227
+ <StandardMegaMenuGrid menuItem={menuItem} />
228
+ )}
229
+ </div>
230
+ )}
231
+
232
+ <div className="tablet only mobile only">
233
+ <NestedAccordion menuItems={menuItems} />
234
+ </div>
74
235
  </Container>
75
236
  </div>
76
237
  );
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { Container, Input, Button, Icon } from 'semantic-ui-react';
2
+ import { Container, Input } from 'semantic-ui-react';
3
3
 
4
4
  import { useClickOutside } from '@eeacms/volto-eea-design-system/helpers';
5
5
 
@@ -14,16 +14,16 @@ function HeaderSearchPopUp({ onClose, triggerRefs = [] }) {
14
14
  <div className="wrapper">
15
15
  <Input
16
16
  className="search"
17
- icon="search"
17
+ icon={{ className: 'ri-search-line', link: true }}
18
18
  placeholder="Search..."
19
19
  fluid
20
20
  />
21
- <div className="action">
21
+ {/* <div className="action">
22
22
  <Button icon labelPosition="left" className="search">
23
23
  <Icon name="search" />
24
24
  Advanced Search
25
25
  </Button>
26
- </div>
26
+ </div> */}
27
27
  </div>
28
28
  </Container>
29
29
  </div>
@@ -0,0 +1,55 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+
4
+ Hero.propTypes = {
5
+ image: PropTypes.bool,
6
+ fullWidth: PropTypes.bool,
7
+ fullHeight: PropTypes.bool,
8
+ alignContent: PropTypes.string,
9
+ textAlign: PropTypes.string,
10
+ metaAlign: PropTypes.string,
11
+ backgroundVariant: PropTypes.string,
12
+ quoted: PropTypes.bool,
13
+ textVariant: PropTypes.string,
14
+ };
15
+
16
+ function Hero({
17
+ image_url,
18
+ image,
19
+ fullWidth,
20
+ fullHeight,
21
+ alignContent,
22
+ backgroundVariant,
23
+ children,
24
+ }) {
25
+ return (
26
+ // full width prop
27
+ <div
28
+ className={`${
29
+ fullWidth ? 'eea hero-block full-width' : 'eea hero-block'
30
+ } ${fullHeight ? 'full-height' : ''} color-bg-${backgroundVariant}`}
31
+ >
32
+ <div
33
+ className="hero-block-image"
34
+ style={image ? { backgroundImage: `url(${image_url})` } : {}}
35
+ >
36
+ <div
37
+ className={`hero-block-inner-wrapper d-flex ui container flex-items-${alignContent}`}
38
+ >
39
+ <div className="hero-block-body">{children}</div>
40
+ </div>
41
+ </div>
42
+ </div>
43
+ );
44
+ }
45
+
46
+ Hero.Text = ({ quoted, textVariant, textAlign, children }) => (
47
+ <div className={`hero-block-text color-fg-${textVariant} text-${textAlign}`}>
48
+ <h2 className={`${quoted ? 'quoted' : ''}`}>{children}</h2>
49
+ </div>
50
+ );
51
+ Hero.Meta = ({ metaAlign, children }) => (
52
+ <div className={`hero-block-meta text-${metaAlign}`}>{children}</div>
53
+ );
54
+
55
+ export default Hero;