Skip to content

Commit

Permalink
feat(mega-menu): add layouts for mega menu - refs #253966 (#375)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
MihaelaCretu11 and ichim-david authored Aug 16, 2023
1 parent 6d41254 commit a675411
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 130 deletions.
4 changes: 4 additions & 0 deletions src/ui/Header/Header.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -143,6 +144,7 @@ const TopDropdownMenu = ({
const Main = ({
logo,
menuItems,
menuItemsLayouts,
renderMenuItem,
renderGlobalMenuItem,
headerSearchBox,
Expand All @@ -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), []);

Expand Down Expand Up @@ -357,6 +360,7 @@ const Main = ({
renderMenuItem={renderMenuItem}
activeItem={activeItem}
menuItems={menuItems}
menuItemsLayouts={itemsLayouts}
pathName={pathname}
onClose={menuOnClickOutside}
triggerRefs={[mobileMenuBurgerRef, desktopMenuRef]}
Expand Down
30 changes: 26 additions & 4 deletions src/ui/Header/Header.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import cx from 'classnames';
export default {
title: 'Layout/Header',
component: Header,
excludeStories: /menuItems$/,
argTypes: {
links: {
table: {
Expand Down Expand Up @@ -98,7 +99,7 @@ const languages = [
{ name: 'Türkçe', code: 'tr' },
];

const menuItems = [
export const menuItems = [
{
'@id': 'Topics',
items: [
Expand Down Expand Up @@ -483,7 +484,7 @@ const menuItems = [
],
review_state: null,
title: 'Topics',
url: '/#',
url: '/en/topics',
},
{
'@id': 'Analysis-and-data',
Expand Down Expand Up @@ -887,7 +888,7 @@ const menuItems = [
],
review_state: null,
title: 'Countries',
url: '/#',
url: '/en/countries',
},
{
'@id': 'Newsroom',
Expand Down Expand Up @@ -1143,7 +1144,7 @@ const menuItems = [
],
review_state: null,
title: 'About Us',
url: '/#',
url: '/en/about',
},
];

Expand Down Expand Up @@ -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();
};
Expand Down Expand Up @@ -1320,6 +1341,7 @@ const Template = (args) => {
pathname={pathname}
logo={<Logo {...logoProps} inverted={args.inverted} />}
menuItems={menuItems}
menuItemsLayouts={menuItemsLayouts}
headerSearchBox={headerSearchBox}
renderMenuItem={(item, options = {}, props) => {
const { onClick } = options;
Expand Down
219 changes: 116 additions & 103 deletions src/ui/Header/HeaderMenuPopUp.js
Original file line number Diff line number Diff line change
@@ -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) => (
<React.Fragment key={index}>
{renderMenuItem(item, {
className: 'item',
Expand All @@ -16,122 +22,131 @@ const createColumns = (item, renderMenuItem, item_id) => {
})}
</React.Fragment>
));
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 ? (
<List aria-labelledby={item_id} style={{ columns: `${columns}` }}>
{item.items.length && !hideChildrenFromNavigation ? (
<List
aria-labelledby={item_id}
className={columns && columns > 1 ? `has--${columns}--columns` : ''}
>
{createColumns(item, renderMenuItem, item_id)}
</List>
) : null}
</>
);
};

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 (
<>
{renderMenuItem(item, {
className: 'sub-title',
id: item_id,
})}
<List className="menu-list" aria-labelledby={item_id}>
{item.items.map((listItem, index) => (
<React.Fragment key={index}>
{renderMenuItem(
listItem,
{
className: 'item',
key: index,
},
{ children: icon && <Icon className={iconName} /> },
)}
</React.Fragment>
))}
</List>
{!hideChildrenFromNavigation && (
<List className="menu-list" aria-labelledby={item_id}>
{item.items.map((listItem, index) => (
<React.Fragment key={index}>
{renderMenuItem(
listItem,
{
className: 'item',
key: index,
},
{ children: icon && <Icon className={iconName} /> },
)}
</React.Fragment>
))}
</List>
)}
</>
);
};

const Topics = ({ menuItem, renderMenuItem }) => (
<Grid>
{menuItem.items.map((section, index) => (
<React.Fragment key={index}>
{section.title === 'At a glance' ? (
<Grid.Column width={3} id="at-a-glance">
<Item item={section} key={index} renderMenuItem={renderMenuItem} />
</Grid.Column>
) : (
<Grid.Column width={9} key={index} id="topics-right-column">
<ItemGrid
sectionTitle={section.title}
item={section}
columns={4}
key={index}
renderMenuItem={renderMenuItem}
/>
</Grid.Column>
)}
</React.Fragment>
))}
</Grid>
);
const RenderItem = ({ layout, section, renderMenuItem, index }) => {
const hideChildrenFromNavigation =
layout.hideChildrenFromNavigation === undefined
? true
: layout.hideChildrenFromNavigation;
return !layout.menuItemChildrenListColumns ||
layout.menuItemChildrenListColumns[index] === 1 ? (
<Item
item={section}
renderMenuItem={renderMenuItem}
hideChildrenFromNavigation={hideChildrenFromNavigation}
/>
) : (
<ItemGrid
item={section}
columns={layout.menuItemChildrenListColumns[index]}
renderMenuItem={renderMenuItem}
hideChildrenFromNavigation={hideChildrenFromNavigation}
/>
);
};

export const StandardMegaMenuGrid = ({ menuItem, renderMenuItem, layout }) => {
const menuItemColumns = layout && layout.menuItemColumns;
const menuItemColumnsLength =
(menuItemColumns && menuItemColumns.length - 1) || 0;

const renderColumnContent = (section, columnIndex) => (
<RenderItem
layout={layout}
section={section}
renderMenuItem={renderMenuItem}
index={columnIndex}
/>
);

const Countries = ({ menuItem, renderMenuItem }) => (
<Grid>
<Grid.Column width={8}>
const renderColumns = () => (
<Grid>
{menuItemColumns.map((section, columnIndex) => (
<div className={layout.menuItemColumns[columnIndex]} key={columnIndex}>
{columnIndex !== menuItemColumnsLength
? renderColumnContent(menuItem.items[columnIndex], columnIndex)
: menuItem.items
.slice(menuItemColumnsLength)
.map((section, _idx) =>
renderColumnContent(section, columnIndex),
)}
</div>
))}
</Grid>
);

const renderDefaultColumns = () => (
<div className={layout?.gridContainerClass || 'ui four column grid'}>
{menuItem.items.map((section, index) => (
<React.Fragment key={index}>
{index === 0 && (
<ItemGrid
sectionTitle={section.title}
item={section}
columns={5}
renderMenuItem={renderMenuItem}
/>
)}
</React.Fragment>
<Grid.Column key={index}>
{renderColumnContent(section, index)}
</Grid.Column>
))}
</Grid.Column>
<Grid.Column width={4}>
<Grid columns={1} className="nested-grid">
{menuItem.items.map((section, index) => (
<React.Fragment key={index}>
{index !== 0 && (
<Grid.Column>
<ItemGrid
sectionTitle={section.title}
item={section}
columns={2}
renderMenuItem={renderMenuItem}
/>
</Grid.Column>
)}
</React.Fragment>
))}
</Grid>
</Grid.Column>
</Grid>
);
</div>
);

const StandardMegaMenuGrid = ({ menuItem, renderMenuItem }) => (
<Grid columns={4}>
{menuItem.items.map((section, index) => (
<Grid.Column key={index}>
<Item item={section} renderMenuItem={renderMenuItem} />
</Grid.Column>
))}
</Grid>
);
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 (
Expand Down Expand Up @@ -308,6 +323,7 @@ const NestedAccordion = ({ menuItems, renderMenuItem, pathName }) => {

function HeaderMenuPopUp({
menuItems,
menuItemsLayouts,
renderMenuItem,
pathName,
onClose,
Expand All @@ -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 (
<Transition visible={visible} animation="slide down" duration={300}>
<div id="mega-menu" ref={nodeRef}>
Expand Down Expand Up @@ -351,19 +372,11 @@ function HeaderMenuPopUp({
)}
</div>
)}
{menuItem.title === 'Topics' ? (
<Topics menuItem={menuItem} renderMenuItem={renderMenuItem} />
) : menuItem.title === 'Countries' ? (
<Countries
menuItem={menuItem}
renderMenuItem={renderMenuItem}
/>
) : (
<StandardMegaMenuGrid
menuItem={menuItem}
renderMenuItem={renderMenuItem}
/>
)}
<StandardMegaMenuGrid
menuItem={menuItem}
renderMenuItem={renderMenuItem}
layout={layout}
/>
</div>
)}
<div className="tablet only mobile only">
Expand Down
Loading

0 comments on commit a675411

Please sign in to comment.