UNPKG

design-comuni-plone-theme

Version:
432 lines (397 loc) 14 kB
/** * Navigation components. * @module components/theme/Navigation/Navigation */ import React, { useState, useEffect } from 'react'; import PropTypes from 'prop-types'; import { map } from 'lodash'; import cx from 'classnames'; import { NavItem, NavLink, UncontrolledDropdown, DropdownToggle, DropdownMenu, Button, Row, Col, LinkList, } from 'design-react-kit'; import { defineMessages, useIntl } from 'react-intl'; import { flattenToAppURL, hasBlocksData, getBlocksFieldname, getBlocksLayoutFieldname, getBaseUrl, } from '@plone/volto/helpers'; import { UniversalLink, ConditionalLink } from '@plone/volto/components'; import { Icon } from 'design-comuni-plone-theme/components/ItaliaTheme'; import config from '@plone/volto/registry'; const messages = defineMessages({ menu_selected: { id: 'Menu selezionato', defaultMessage: 'Menu selezionato', }, view_all: { id: 'Vedi tutto', defaultMessage: 'Vedi tutto', }, closeMenu: { id: 'dropdownmenu-close-menu-button', defaultMessage: 'Close menu', }, }); const MEGAMENU_MAX_ROWS = 8; const MEGAMENU_MAX_COLS = 3; const isActive = (item, pathname) => { const paths = [...(item.navigationRoot || [])]; if (item.showMoreLink?.length > 0) { paths.push(item.showMoreLink[0]); } if (item.linkUrl?.length > 0) { paths.push(item.linkUrl[0]); } return paths.reduce( (acc, path) => acc || flattenToAppURL(pathname).indexOf(flattenToAppURL(path['@id'])) > -1, false, ); }; const isChildActive = (itemUrl, pathname, exact = false) => { if (exact) { return itemUrl === pathname; } return pathname.indexOf(itemUrl) > -1; }; const MegaMenu = ({ item, pathname }) => { const intl = useIntl(); const blocksFieldname = getBlocksFieldname(item); const blocksLayoutFieldname = getBlocksLayoutFieldname(item); const isItemActive = isActive(item, pathname); const [menuStatus, setMenuStatus] = useState(false); const getAnchorTarget = (nodeElement) => { if (nodeElement.nodeName === 'A') { return nodeElement; } else if (nodeElement.parentElement?.nodeName === 'A') { return nodeElement.parentElement; } else { return null; } }; useEffect(() => { const blocksClickListener = (e) => { const dropdownmenuLinks = [ ...document.querySelectorAll('.dropdown-menu.show a'), ]; if ( dropdownmenuLinks?.length === 0 || dropdownmenuLinks?.indexOf(getAnchorTarget(e.target)) < 0 ) { return; } setMenuStatus(false); }; document.body.addEventListener('click', blocksClickListener); return () => document.body.removeEventListener('click', blocksClickListener); }, []); if (item.mode === 'simpleLink') { return item.linkUrl?.length > 0 ? ( <NavItem tag="li" active={isItemActive} role="none"> <NavLink className={isItemActive ? 'focus--mouse' : ''} href={item.linkUrl === '' ? '/' : null} item={item.linkUrl[0]?.['@id'] ? item.linkUrl[0] : '#'} tag={UniversalLink} data-element={item.id_lighthouse} active={isItemActive} role="menuitem" > <span dangerouslySetInnerHTML={{ __html: item.title }}></span> {isItemActive && ( <span className="visually-hidden"> {intl.formatMessage(messages.menu_selected)} </span> )} </NavLink> </NavItem> ) : null; } else { //megamenu let hasBlocks = hasBlocksData(item); if (item?.blocks && Object.keys(item.blocks).length === 1) { let b = item.blocks[Object.keys(item.blocks)[0]]; if (b['@type'] === 'text') { if (!b.text || b.text?.length === 0) { hasBlocks = false; } if (b.text?.blocks?.length > 0) { const empty_blocks = b.text.blocks.filter( (bb) => !bb.text || bb.text?.length === 0, ).length; if (empty_blocks === b.text.blocks.length) { //se sono tutti vuoti hasBlocks = false; } } } if (b['@type'] === 'slate') { if (b.plaintext?.length === 0) { hasBlocks = false; } } } const childrenGroups = []; const items = []; // eslint-disable-next-line no-unused-expressions item.navigationRoot?.forEach((navRoot) => { if (item.navigationRoot.length > 1) { items.push({ ...navRoot, showAsHeader: true }); } // eslint-disable-next-line no-unused-expressions navRoot.items?.forEach((subitem) => { items.push(subitem); }); }); let max_cols = hasBlocks ? 2 : MEGAMENU_MAX_COLS; if (items.length < MEGAMENU_MAX_ROWS) { childrenGroups.push(items); } else { let rows = Math.ceil(items.length / max_cols); rows = rows === 0 ? 1 : rows; let col = 0; let row_counter = 1; let is_last_row = false; let is_last_col = false; for (var i = 0; i < items.length; i++) { is_last_row = row_counter === rows; is_last_col = col === MEGAMENU_MAX_COLS - 1; if (!childrenGroups[col]) { childrenGroups.push([]); } /***************** Manage columns split *************** */ if ( !is_last_col && is_last_row && items[i].showAsHeader && items[i].items?.length > 0 ) { //se è una intestazione, non la mette come ultimo elemento della colonna, ma la mette dirattemente nella colonna successiva if (!childrenGroups[col + 1]) { childrenGroups.push([]); } childrenGroups[col + 1].push(items[i]); row_counter = 2; col++; continue; } if ( row_counter === 1 && !items[i]?.showAsHeader && (!items[i + 1] || items[i + 1]?.showAsHeader) && col > 0 ) { //se l'elemento corrente viene messo da solo nella colonna successiva, lo metto in quella precedente. childrenGroups[col - 1].push(items[i]); continue; } if (!config.settings.siteProperties.splitMegamenuColumns) { if (item.navigationRoot?.length > 1) { //se c'è più di una root navigation if (row_counter === 1 && !items[i].showAsHeader && col > 0) { //se l'elemento corrente non è una intestazione, ed è il primo elemento della colonna, lo metto nella colonna precedente childrenGroups[col - 1].push(items[i]); continue; } } } /***************** end managing columns split *************** */ childrenGroups[col].push(items[i]); row_counter++; if ((i + 1) % rows === 0 && col < MEGAMENU_MAX_COLS - 1) { col++; row_counter = 1; } } } return ( <NavItem tag="li" className={isItemActive ? 'focus--mouse megamenu' : 'megamenu'} active={isItemActive} role="none" > <UncontrolledDropdown nav inNavbar isOpen={menuStatus} tag="div" toggle={() => setMenuStatus(!menuStatus)} > <DropdownToggle role="menuitem" aria-haspopup color="secondary" nav data-element={item.id_lighthouse} > <span dangerouslySetInnerHTML={{ __html: item.title }}></span> <Icon icon="it-expand" className={cx('megamenu-toggle-icon', { open: menuStatus })} /> </DropdownToggle> <DropdownMenu flip tag="div"> <div className="text-end megamenu-close-button"> <Button color="link" onClick={() => setMenuStatus(false)} onKeyDown={(e) => { if (e.keyCode === 13) { setMenuStatus(false); } }} title={intl.formatMessage(messages.closeMenu)} // APG spec: on Tab menu closes, so remove it from focusable elements // https://www.w3.org/WAI/ARIA/apg/patterns/menubar/examples/menubar-navigation/ tabIndex="-1" > <Icon icon="it-close" /> </Button> </div> <Row> <Col lg={hasBlocks ? 6 : 12}> <Row> {childrenGroups.map((group, index) => ( <Col lg={12 / max_cols} key={'group_' + index}> <LinkList className="bordered" role="menu" aria-label={item.title ?? ''} > {group.map((child, idx) => { return ( <li key={child['@id'] + idx} role="none"> {child.showAsHeader ? ( <h3 className={cx('list-item', { active: isChildActive( flattenToAppURL(child['@id']), pathname, ), })} > <ConditionalLink item={child} title={child.title} condition={!!child['@id']} key={child['@id']} onClick={() => setMenuStatus(false)} role="menuitem" aria-current="page" > <span>{child.title}</span> </ConditionalLink> </h3> ) : ( <ConditionalLink item={child} title={child.title} condition={!!child['@id']} key={child['@id']} onClick={() => setMenuStatus(false)} className={cx('list-item', { active: isChildActive( flattenToAppURL(child['@id']), pathname, true, ), })} role="menuitem" > <span>{child.title}</span> </ConditionalLink> )} </li> ); })} </LinkList> </Col> ))} </Row> </Col> {hasBlocks && ( <Col lg={6} className="m-4 m-lg-0 dropdownmenu-blocks-column"> {map(item[blocksLayoutFieldname].items, (block) => { const blockType = item[blocksFieldname]?.[block]?.['@type']; if (['title', 'pageDescription'].indexOf(blockType) > -1) return null; const Block = config.blocks.blocksConfig[blockType]?.['view'] ?? null; return Block !== null ? ( <Block key={block} id={block} properties={item} data={item[blocksFieldname][block]} path={getBaseUrl(pathname || '')} /> ) : ( <div key={block}> {intl.formatMessage(messages.unknownBlock, { block: item[blocksFieldname]?.[block]?.['@type'], })} </div> ); })} </Col> )} </Row> {item.showMoreLink?.length > 0 && ( <div className="it-external bottom-right"> <Row> <Col lg={8} /> <Col lg={4}> <LinkList role="menu" aria-label={item.showMoreText ?? ''}> <li className="it-more text-end" role="none"> <UniversalLink className="list-item medium" item={item.showMoreLink[0]} onClick={() => setMenuStatus(false)} role="menuitem" > <span> {item.showMoreText?.length > 0 ? item.showMoreText : intl.formatMessage(messages.view_all)} </span> <Icon icon="it-arrow-right" /> </UniversalLink> </li> </LinkList> </Col> </Row> </div> )} </DropdownMenu> </UncontrolledDropdown> </NavItem> ); } }; MegaMenu.propTypes = { pathname: PropTypes.string.isRequired, item: PropTypes.shape({ title: PropTypes.string, url: PropTypes.string, items: PropTypes.arrayOf( PropTypes.shape({ title: PropTypes.string, url: PropTypes.string, }), ), }).isRequired, }; export default MegaMenu;