From a67541117b88fb48b531585a8518994ea9bc4562 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cre=C8=9Bu=20Mihaela?= <68827085+MihaelaCretu11@users.noreply.github.com> Date: Wed, 16 Aug 2023 18:35:38 +0300 Subject: [PATCH] feat(mega-menu): add layouts for mega menu - refs #253966 (#375) * feat: add layouts for mega menu - refs #253966 * remove settings * add list behaviour for sub children * modified StandardMegaMenuGrid logic * add hideChildrenFromNavigation * fix typo * feat: add classes for columns from config settings * fix order for css classes * cleaned up overrides that were no longer needed and modified selector of ui grid with something more descriptive of where you want to remove the top margin from * added missing column class from the countries mega menu section * fix warnings * remove unreachable code * change(mega-menu): removed section.title logic since we now set layout from config per url * change(mega-menu): removed itemsEquallySpread logic, there is no need for special country column * change(mega-menu): topics at a glance styling after move to classes from id's - also add a 2rem gap for the list items, without it in topics section Climate change mitigation: reducing emissions would break on next column making it hard to read * change(menu): use has--count--columns classes instead of inline styles for setting columns * change(menu): move layouts to a prop in order to pass it when using component in storybook - also avoid adding the has--number--column if there is less than 2 as it makes no sense to have 1 column as that is the default behaviour * feat(menu): added ability to append any extra menu items to the last column - modified storybook to make it work with the latest refactoring - modified has--value--column to start from 2 to 6 * change(menu): renamed columnsWidth to menuItemsColumns * change(menu): renamed childrenColumns to menuItemListColumns - the columns property is set on the list item where the subchildren are displayed as such we use this new name for better representation of what it does * change(menu): modified urls for topic, countries and about to prepend en - this way the config can be shared with other projects such as volto-eea-website-theme * refactor(menu): standard mega menu grid simplification * change(menu): renamed menuItemListColumns to menuItemChildrenListColumns for greater clarity - the columns are applied to the children items --------- Co-authored-by: David Ichim --- src/ui/Header/Header.jsx | 4 + src/ui/Header/Header.stories.js | 30 +++- src/ui/Header/HeaderMenuPopUp.js | 219 ++++++++++++----------- theme/themes/eea/extras/header.less | 25 +-- theme/themes/eea/extras/header.variables | 2 +- 5 files changed, 150 insertions(+), 130 deletions(-) diff --git a/src/ui/Header/Header.jsx b/src/ui/Header/Header.jsx index fb1eb0eece..cf85df871b 100644 --- a/src/ui/Header/Header.jsx +++ b/src/ui/Header/Header.jsx @@ -17,6 +17,7 @@ import HeaderMenuPopUp from './HeaderMenuPopUp'; import PropTypes from 'prop-types'; import { isInternalURL } from '@plone/volto/helpers'; +import config from '@plone/volto/registry'; Header.propTypes = { transparency: PropTypes.bool, @@ -143,6 +144,7 @@ const TopDropdownMenu = ({ const Main = ({ logo, menuItems, + menuItemsLayouts, renderMenuItem, renderGlobalMenuItem, headerSearchBox, @@ -159,6 +161,7 @@ const Main = ({ const [burger, setBurger] = React.useState(''); const searchInputRef = React.useRef(null); const [isClient, setIsClient] = React.useState(); + const itemsLayouts = menuItemsLayouts || config.settings?.menuItemsLayouts; React.useEffect(() => setIsClient(true), []); @@ -357,6 +360,7 @@ const Main = ({ renderMenuItem={renderMenuItem} activeItem={activeItem} menuItems={menuItems} + menuItemsLayouts={itemsLayouts} pathName={pathname} onClose={menuOnClickOutside} triggerRefs={[mobileMenuBurgerRef, desktopMenuRef]} diff --git a/src/ui/Header/Header.stories.js b/src/ui/Header/Header.stories.js index d7db39d332..7c57618702 100644 --- a/src/ui/Header/Header.stories.js +++ b/src/ui/Header/Header.stories.js @@ -12,6 +12,7 @@ import cx from 'classnames'; export default { title: 'Layout/Header', component: Header, + excludeStories: /menuItems$/, argTypes: { links: { table: { @@ -98,7 +99,7 @@ const languages = [ { name: 'Türkçe', code: 'tr' }, ]; -const menuItems = [ +export const menuItems = [ { '@id': 'Topics', items: [ @@ -483,7 +484,7 @@ const menuItems = [ ], review_state: null, title: 'Topics', - url: '/#', + url: '/en/topics', }, { '@id': 'Analysis-and-data', @@ -887,7 +888,7 @@ const menuItems = [ ], review_state: null, title: 'Countries', - url: '/#', + url: '/en/countries', }, { '@id': 'Newsroom', @@ -1143,7 +1144,7 @@ const menuItems = [ ], review_state: null, title: 'About Us', - url: '/#', + url: '/en/about', }, ]; @@ -1179,6 +1180,26 @@ const debounce = (func) => { }; }; +const menuItemsLayouts = { + '/en/topics': { + menuItemChildrenListColumns: [1, 4], + menuItemColumns: [ + 'at-a-glance three wide column', + 'topics-right-column nine wide column', + ], + hideChildrenFromNavigation: false, + }, + '/en/countries': { + menuItemColumns: ['eight wide column', 'four wide column'], + menuItemChildrenListColumns: [5, 2], + appendExtraMenuItemsToLastColumn: true, + hideChildrenFromNavigation: false, + }, + '/en/about': { + hideChildrenFromNavigation: false, + }, +}; + const handleDropdownClick = (event) => { event.stopPropagation(); }; @@ -1320,6 +1341,7 @@ const Template = (args) => { pathname={pathname} logo={} menuItems={menuItems} + menuItemsLayouts={menuItemsLayouts} headerSearchBox={headerSearchBox} renderMenuItem={(item, options = {}, props) => { const { onClick } = options; diff --git a/src/ui/Header/HeaderMenuPopUp.js b/src/ui/Header/HeaderMenuPopUp.js index 12acd34004..fdc61fb0e8 100644 --- a/src/ui/Header/HeaderMenuPopUp.js +++ b/src/ui/Header/HeaderMenuPopUp.js @@ -1,13 +1,19 @@ -import React, { useState, useEffect } from 'react'; -import { Transition } from 'semantic-ui-react'; -import { Container, Grid, List, Icon, Accordion } from 'semantic-ui-react'; +import React, { useEffect, useState } from 'react'; +import { + Accordion, + Container, + Grid, + Icon, + List, + Transition, +} from 'semantic-ui-react'; import { cloneDeep } from 'lodash'; import { useClickOutside } from '@eeacms/volto-eea-design-system/helpers'; const createColumns = (item, renderMenuItem, item_id) => { - const itemList = item.items.map((item, index) => ( + return item.items.map((item, index) => ( {renderMenuItem(item, { className: 'item', @@ -16,16 +22,23 @@ const createColumns = (item, renderMenuItem, item_id) => { })} )); - return itemList; }; -const ItemGrid = ({ sectionTitle, item, columns, renderMenuItem }) => { +const ItemGrid = ({ + item, + columns, + renderMenuItem, + hideChildrenFromNavigation, +}) => { const item_id = item.title.toLowerCase().replaceAll(' ', '-') + '-sub-title'; return ( <> {renderMenuItem(item, { className: 'sub-title', id: item_id })} - {item.items.length ? ( - + {item.items.length && !hideChildrenFromNavigation ? ( + 1 ? `has--${columns}--columns` : ''} + > {createColumns(item, renderMenuItem, item_id)} ) : null} @@ -33,7 +46,13 @@ const ItemGrid = ({ sectionTitle, item, columns, renderMenuItem }) => { ); }; -const Item = ({ item, icon = false, iconName, renderMenuItem }) => { +const Item = ({ + item, + icon = false, + iconName, + renderMenuItem, + hideChildrenFromNavigation, +}) => { const item_id = item.title.toLowerCase().replaceAll(' ', '-') + '-sub-title'; return ( <> @@ -41,97 +60,93 @@ const Item = ({ item, icon = false, iconName, renderMenuItem }) => { className: 'sub-title', id: item_id, })} - - {item.items.map((listItem, index) => ( - - {renderMenuItem( - listItem, - { - className: 'item', - key: index, - }, - { children: icon && }, - )} - - ))} - + {!hideChildrenFromNavigation && ( + + {item.items.map((listItem, index) => ( + + {renderMenuItem( + listItem, + { + className: 'item', + key: index, + }, + { children: icon && }, + )} + + ))} + + )} ); }; -const Topics = ({ menuItem, renderMenuItem }) => ( - - {menuItem.items.map((section, index) => ( - - {section.title === 'At a glance' ? ( - - - - ) : ( - - - - )} - - ))} - -); +const RenderItem = ({ layout, section, renderMenuItem, index }) => { + const hideChildrenFromNavigation = + layout.hideChildrenFromNavigation === undefined + ? true + : layout.hideChildrenFromNavigation; + return !layout.menuItemChildrenListColumns || + layout.menuItemChildrenListColumns[index] === 1 ? ( + + ) : ( + + ); +}; + +export const StandardMegaMenuGrid = ({ menuItem, renderMenuItem, layout }) => { + const menuItemColumns = layout && layout.menuItemColumns; + const menuItemColumnsLength = + (menuItemColumns && menuItemColumns.length - 1) || 0; + + const renderColumnContent = (section, columnIndex) => ( + + ); -const Countries = ({ menuItem, renderMenuItem }) => ( - - + const renderColumns = () => ( + + {menuItemColumns.map((section, columnIndex) => ( +
+ {columnIndex !== menuItemColumnsLength + ? renderColumnContent(menuItem.items[columnIndex], columnIndex) + : menuItem.items + .slice(menuItemColumnsLength) + .map((section, _idx) => + renderColumnContent(section, columnIndex), + )} +
+ ))} +
+ ); + + const renderDefaultColumns = () => ( +
{menuItem.items.map((section, index) => ( - - {index === 0 && ( - - )} - + + {renderColumnContent(section, index)} + ))} - - - - {menuItem.items.map((section, index) => ( - - {index !== 0 && ( - - - - )} - - ))} - - - -); +
+ ); -const StandardMegaMenuGrid = ({ menuItem, renderMenuItem }) => ( - - {menuItem.items.map((section, index) => ( - - - - ))} - -); + return menuItemColumns ? renderColumns() : renderDefaultColumns(); +}; const FirstLevelContent = ({ element, renderMenuItem, pathName }) => { - const topics = element.title === 'Topics' ? true : false; + const topics = element.title === 'Topics'; let defaultIndex = -1; return ( @@ -308,6 +323,7 @@ const NestedAccordion = ({ menuItems, renderMenuItem, pathName }) => { function HeaderMenuPopUp({ menuItems, + menuItemsLayouts, renderMenuItem, pathName, onClose, @@ -322,6 +338,11 @@ function HeaderMenuPopUp({ (current) => current.url === activeItem || current['@id'] === activeItem, ); + const layout = + !!menuItemsLayouts && + Object.keys(menuItemsLayouts).includes(menuItem?.url) && + menuItemsLayouts[menuItem.url]; + return (
@@ -351,19 +372,11 @@ function HeaderMenuPopUp({ )}
)} - {menuItem.title === 'Topics' ? ( - - ) : menuItem.title === 'Countries' ? ( - - ) : ( - - )} + )}
diff --git a/theme/themes/eea/extras/header.less b/theme/themes/eea/extras/header.less index 13228b3198..74240a1ee2 100644 --- a/theme/themes/eea/extras/header.less +++ b/theme/themes/eea/extras/header.less @@ -747,6 +747,7 @@ #mega-menu .ui.list { margin: 0; + gap: @megaMenuListGap; } #mega-menu .item { @@ -770,7 +771,7 @@ } } -#at-a-glance { +.at-a-glance { .item { margin: @megaMenuGlanceListItemMargin; font-size: @megaMenuGlanceListItemFontSize; @@ -782,30 +783,10 @@ } } -// add negative margin to the first column subtitle from nested grid -// so that the subtitle lines up with the subtitle from the left column -#mega-menu .nested-grid > .column:first-of-type .sub-title { - margin-top: @megaMenuSubTitleCountriesNestedGridMarginTop; -} - -#topics-right-column { +.topics-right-column { padding-left: @topicsRightColumnPaddingLeft; } -.ui.grid > .column > .ui.grid { - margin-top: 0; -} - -#mega-menu .ui.grid { - div.column:first-child { - padding-left: 0.625rem; - } - - div.column:last-child { - padding-right: 0.625rem; - } -} - #mega-menu .active:not(.title-link):not(.button) > span { padding-left: @megaMenuListItemActivePadding; border-left: @megaMenuListItemActiveBorder; diff --git a/theme/themes/eea/extras/header.variables b/theme/themes/eea/extras/header.variables index 81b396e604..91bf981eb8 100644 --- a/theme/themes/eea/extras/header.variables +++ b/theme/themes/eea/extras/header.variables @@ -182,9 +182,9 @@ @megaMenuSubTitleFontWeight: @font-weight-7; @megaMenuSubTitlePadding: 0.5rem 0; @megaMenuSubTitleDisplay: block; -@megaMenuSubTitleCountriesNestedGridMarginTop: -1rem; /* List Item */ +@megaMenuListGap: 0 2rem; @megaMenuListItemFontSize: @font-size-1; @megaMenuListItemFontWeight: @font-weight-4; @megaMenuListItemPadding: @rem-space-2 0;